apple-mail-mcp 2.10.33 → 2.11.0
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 +61 -12
- package/build/index.js +29 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1319,14 +1319,26 @@ returns the comparison in `structuredContent`:
|
|
|
1319
1319
|
}
|
|
1320
1320
|
```
|
|
1321
1321
|
|
|
1322
|
-
`status` is deliberately
|
|
1322
|
+
`status` is deliberately not a pass/fail flag:
|
|
1323
1323
|
|
|
1324
1324
|
| `status` | Meaning | Warns? |
|
|
1325
1325
|
|----------|---------|--------|
|
|
1326
1326
|
| `match` | Exactly as many messages left the mailbox as the operation acted on. | No |
|
|
1327
1327
|
| `over` | **More** left than were operated on. Messages are unaccounted for. | **Yes** |
|
|
1328
|
-
| `
|
|
1329
|
-
|
|
1328
|
+
| `unknown` | No comparison this server is willing to assert. `unknownReason` says which of four situations produced it. | No |
|
|
1329
|
+
|
|
1330
|
+
`unknownReason` distinguishes four cases that are *not* interchangeable:
|
|
1331
|
+
|
|
1332
|
+
| `unknownReason` | Meaning |
|
|
1333
|
+
|-----------------|---------|
|
|
1334
|
+
| `count-unreadable` | Mail would not report a count at all (`before`/`after` null). |
|
|
1335
|
+
| `no-expectation` | No expectation is predictable, so no comparison exists — a move whose destination **is** the source mailbox. |
|
|
1336
|
+
| `count-did-not-move` | The count did not move. On a store that flags deletions instead of removing them this is the **ordinary, correct** reading for an operation that fully succeeded. |
|
|
1337
|
+
| `count-partial` | The count moved, but by less than the operation accounted for. A flag-only store cannot produce this, which is why it is worth telling apart. |
|
|
1338
|
+
|
|
1339
|
+
> **`under` was removed in 2.11.0.** It used to mean "fewer left than expected"
|
|
1340
|
+
> and was documented as routine. Field evidence retired it — see
|
|
1341
|
+
> [Why `observed` is a lower bound](#why-observed-is-a-lower-bound-155).
|
|
1330
1342
|
|
|
1331
1343
|
#### What an `over` warning does and does not tell you
|
|
1332
1344
|
|
|
@@ -1346,7 +1358,7 @@ trusted:
|
|
|
1346
1358
|
**Concurrent departure is the benign cause to rule out first**, and the warning
|
|
1347
1359
|
text says so. What the asymmetry argument actually buys is the other half:
|
|
1348
1360
|
concurrent *arrivals* cannot produce `over`, because a message arriving
|
|
1349
|
-
mid-operation *raises* the after-count and biases the reading
|
|
1361
|
+
mid-operation *raises* the after-count and biases the reading short.
|
|
1350
1362
|
That is why `over` is the interesting direction — a strong signal, not a proof.
|
|
1351
1363
|
|
|
1352
1364
|
Setting `APPLE_MAIL_MCP_AUDIT_LOG` is what settles which one you have: the
|
|
@@ -1354,12 +1366,42 @@ collateral diff below **names** the messages that disappeared, and "the
|
|
|
1354
1366
|
newsletter my rule files every morning" is a very different report from a message
|
|
1355
1367
|
nothing should have touched.
|
|
1356
1368
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1369
|
+
#### Why `observed` is a lower bound (#155)
|
|
1370
|
+
|
|
1371
|
+
**`observed` is the movement of Mail's count. It is a lower bound on how many
|
|
1372
|
+
messages left, not a count of how many left.**
|
|
1373
|
+
|
|
1374
|
+
On iCloud, @scottstern0325 ran the check that settled this: for two batches
|
|
1375
|
+
reporting `observed: 0`, the messages were located **in Trash**, matched by
|
|
1376
|
+
`date received` + sender against the audit log's pre-image. The deletes had
|
|
1377
|
+
happened. Mail's count had not caught up. Across four readings — 0 of 4, 0 of 1
|
|
1378
|
+
(a *single-id* delete), 15 of 16, and 14 of 15 — the shortfall bore no relation
|
|
1379
|
+
to batch size, which is what a lagging count looks like and not what a
|
|
1380
|
+
store-behaviour rule looks like.
|
|
1381
|
+
|
|
1382
|
+
So a short reading is not evidence about your operation. **The per-id outcomes
|
|
1383
|
+
are what report success; this number is not. Do not retry on the strength of
|
|
1384
|
+
it** — that is how a message gets deleted twice.
|
|
1385
|
+
|
|
1386
|
+
Two things follow:
|
|
1387
|
+
|
|
1388
|
+
- A count that does not move at all is still the ordinary reading on a store that
|
|
1389
|
+
flags deletions instead of removing them (Gmail label mailboxes, IMAP accounts
|
|
1390
|
+
with "move deleted messages to Trash" off). It reports
|
|
1391
|
+
`unknownReason: "count-did-not-move"` and says so, and it is never warned about
|
|
1392
|
+
— a warning that fires on every ordinary Gmail delete would be ignored exactly
|
|
1393
|
+
when it matters.
|
|
1394
|
+
- **To confirm where messages went, match them at the destination by `date
|
|
1395
|
+
received` plus sender — not by the numeric ids you passed.** Ids are renumbered
|
|
1396
|
+
by the move and do not survive it.
|
|
1397
|
+
|
|
1398
|
+
**Removed in 2.11.0: the "reported success with no observed effect" warning.**
|
|
1399
|
+
Shipped in 2.10.30, it fired when the count was flat, the snapshot read cleanly
|
|
1400
|
+
and nothing had disappeared. Its premise was that the snapshot corroborated the
|
|
1401
|
+
count — but both are read back-to-back in the same script, and the record that
|
|
1402
|
+
prompted it turns out to have had *both* instruments stale at once. It therefore
|
|
1403
|
+
fired on stores that had done exactly what they were asked. It is gone rather
|
|
1404
|
+
than narrowed; `over` is the only surviving assertion.
|
|
1363
1405
|
|
|
1364
1406
|
Three more honesty rules:
|
|
1365
1407
|
|
|
@@ -1489,8 +1531,15 @@ a hole, because a wrong name here is worse than a missing one:
|
|
|
1489
1531
|
| both | withheld | withheld |
|
|
1490
1532
|
|
|
1491
1533
|
**An absent field means "not computable", never "empty".** Check
|
|
1492
|
-
`"snapshot": "ok"` before reading `disappeared` as a clean bill of health
|
|
1493
|
-
|
|
1534
|
+
`"snapshot": "ok"` before reading `disappeared` as a clean bill of health.
|
|
1535
|
+
|
|
1536
|
+
> ⚠️ **`"ok"` is necessary, not sufficient.** The enumeration's range is bounded
|
|
1537
|
+
> by Mail's own message count, and #155 established that count can lag the
|
|
1538
|
+
> mutation. A count reading **low** truncates the enumeration silently — the
|
|
1539
|
+
> unread tail is never requested, so it never registers as a failed slice and the
|
|
1540
|
+
> status still says `"ok"` — and messages past that bound would then look like
|
|
1541
|
+
> they disappeared. Until that is fixed, treat a `disappeared` entry as a lead to
|
|
1542
|
+
> check, not a proof.
|
|
1494
1543
|
|
|
1495
1544
|
### Privacy, and what the file costs you
|
|
1496
1545
|
|
package/build/index.js
CHANGED
|
@@ -78854,17 +78854,8 @@ function countDeltaWarning(d) {
|
|
|
78854
78854
|
const where = d.account ? `"${d.mailbox}" in account "${d.account}"` : `"${d.mailbox}"`;
|
|
78855
78855
|
return `\u26A0\uFE0F Effect mismatch in ${where}: ${d.observed} message(s) left the mailbox but only ${d.expected} were operated on (count ${d.before} \u2192 ${d.after}). ${extra} message(s) are unaccounted for. Anything else removing mail from this mailbox at the same moment \u2014 a Mail rule, a server-side filter, another client, an IMAP expunge \u2014 reads the same way, so rule that out first. If nothing else was touching it, this is the signature of https://github.com/sweetrb/apple-mail-mcp/issues/155 \u2014 please report it there, and set ${AUDIT_LOG_ENV}=/path/to/audit.ndjson to capture which messages disappeared.`;
|
|
78856
78856
|
}
|
|
78857
|
-
function falseOkWarning(d, collateral) {
|
|
78858
|
-
if (d.status !== "under" || d.expected === null) return null;
|
|
78859
|
-
if (d.observed !== 0 || d.expected <= 0) return null;
|
|
78860
|
-
if (!collateral || collateral.snapshot !== "ok") return null;
|
|
78861
|
-
if ((collateral.disappeared?.length ?? 0) !== 0) return null;
|
|
78862
|
-
const where = d.account ? `"${d.mailbox}" in account "${d.account}"` : `"${d.mailbox}"`;
|
|
78863
|
-
return `\u26A0\uFE0F Reported success with no observed effect in ${where}: ${d.expected} message(s) were operated on and reported ok, but the mailbox count did not move (${d.before} \u2192 ${d.after}) and the collateral snapshot \u2014 which read this mailbox successfully \u2014 shows no message left it. Either the operation silently did nothing, or Mail's count and listing are both stale and the messages did move. Check the destination (Trash for a delete) before assuming either: if they are there, this is a measurement-timing bug; if they are still in the source mailbox minutes later, the operation failed while reporting success. Please report at https://github.com/sweetrb/apple-mail-mcp/issues/155 with ${AUDIT_LOG_ENV}=/path/to/audit.ndjson set.`;
|
|
78864
|
-
}
|
|
78865
78857
|
function reconciliationWarnings(report) {
|
|
78866
|
-
|
|
78867
|
-
return report.countDeltas.flatMap((d) => [countDeltaWarning(d), falseOkWarning(d, collateralFor(d))]).filter((w) => w !== null);
|
|
78858
|
+
return report.countDeltas.map((d) => countDeltaWarning(d)).filter((w) => w !== null);
|
|
78868
78859
|
}
|
|
78869
78860
|
|
|
78870
78861
|
// src/services/appleMailManager.ts
|
|
@@ -79142,6 +79133,8 @@ function canonicalNumericId(raw) {
|
|
|
79142
79133
|
return Number.isFinite(n) ? String(n) : trimmed;
|
|
79143
79134
|
}
|
|
79144
79135
|
var SELF_MOVE_NOTE = "Destination is the source mailbox, so no message should leave it. What Mail does to the count when a message is re-filed into the mailbox it already occupies is unspecified, so there is no expected delta to compare against: this mailbox is reported without a comparison and is never warned about.";
|
|
79136
|
+
var COUNT_UNMOVED_NOTE = `Mail's count did not move. This is the ordinary reading on a store that flags deletions instead of removing them (Gmail label mailboxes and IMAP accounts with "move deleted messages to Trash" off), where the message stays put and the operation still fully succeeded. It can also mean Mail's count simply had not caught up yet. The per-id outcomes are what report success; this number is not, so do not retry on the strength of it.`;
|
|
79137
|
+
var COUNT_PARTIAL_NOTE = `Mail's count moved by less than this operation accounted for. That is a LOWER BOUND on what left, not a count of what left: Mail has been observed reporting a stale count for a delete it had already performed (issue #155), and new mail arriving mid-operation reads the same way. No claim is made either way. To confirm where the messages went, match them at the destination by "date received" plus sender \u2014 NOT by the numeric ids you passed, which are renumbered by the move and do not survive it.`;
|
|
79145
79138
|
function snapshotKey(entry) {
|
|
79146
79139
|
return `${entry.id}\0${entry.messageId}`;
|
|
79147
79140
|
}
|
|
@@ -79679,10 +79672,24 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79679
79672
|
const observed = readable ? m.before - m.after : null;
|
|
79680
79673
|
const note = noteFor(m.account, m.mailbox);
|
|
79681
79674
|
let status;
|
|
79682
|
-
|
|
79683
|
-
|
|
79684
|
-
|
|
79685
|
-
|
|
79675
|
+
let unknownReason;
|
|
79676
|
+
if (!readable) {
|
|
79677
|
+
status = "unknown";
|
|
79678
|
+
unknownReason = "count-unreadable";
|
|
79679
|
+
} else if (m.expected === null) {
|
|
79680
|
+
status = "unknown";
|
|
79681
|
+
unknownReason = "no-expectation";
|
|
79682
|
+
} else if (observed === m.expected) {
|
|
79683
|
+
status = "match";
|
|
79684
|
+
} else if ((observed ?? 0) > m.expected) {
|
|
79685
|
+
status = "over";
|
|
79686
|
+
} else if (observed === 0) {
|
|
79687
|
+
status = "unknown";
|
|
79688
|
+
unknownReason = "count-did-not-move";
|
|
79689
|
+
} else {
|
|
79690
|
+
status = "unknown";
|
|
79691
|
+
unknownReason = "count-partial";
|
|
79692
|
+
}
|
|
79686
79693
|
return {
|
|
79687
79694
|
account: m.account,
|
|
79688
79695
|
mailbox: m.mailbox,
|
|
@@ -79691,11 +79698,11 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79691
79698
|
expected: m.expected,
|
|
79692
79699
|
observed,
|
|
79693
79700
|
status,
|
|
79701
|
+
...unknownReason ? { unknownReason } : {},
|
|
79694
79702
|
...note ? { note } : {},
|
|
79695
|
-
...
|
|
79696
|
-
...
|
|
79697
|
-
|
|
79698
|
-
} : {}
|
|
79703
|
+
...unknownReason === "count-unreadable" ? { note: note ?? "Mail did not report a message count for this mailbox" } : {},
|
|
79704
|
+
...unknownReason === "count-did-not-move" && !note ? { note: COUNT_UNMOVED_NOTE } : {},
|
|
79705
|
+
...unknownReason === "count-partial" && !note ? { note: COUNT_PARTIAL_NOTE } : {}
|
|
79699
79706
|
};
|
|
79700
79707
|
});
|
|
79701
79708
|
const preImages = [];
|
|
@@ -85303,7 +85310,10 @@ var COUNT_DELTA_OUTPUT_SCHEMA = external_exports.array(
|
|
|
85303
85310
|
// `status: "unknown"`, and never with a warning.
|
|
85304
85311
|
expected: external_exports.number().nullable().optional(),
|
|
85305
85312
|
observed: external_exports.number().nullable().optional(),
|
|
85306
|
-
status: external_exports.enum(["match", "over", "
|
|
85313
|
+
status: external_exports.enum(["match", "over", "unknown"]).optional(),
|
|
85314
|
+
// Declared explicitly: the SDK stamps additionalProperties:false on a bare
|
|
85315
|
+
// zod shape, so an undeclared key makes the CLIENT reject the result.
|
|
85316
|
+
unknownReason: external_exports.enum(["count-unreadable", "no-expectation", "count-did-not-move", "count-partial"]).optional(),
|
|
85307
85317
|
note: external_exports.string().optional()
|
|
85308
85318
|
})
|
|
85309
85319
|
).optional();
|
package/package.json
CHANGED