apple-mail-mcp 2.12.0 → 2.13.1
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 +29 -3
- package/build/index.js +94 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1430,9 +1430,35 @@ Three more honesty rules:
|
|
|
1430
1430
|
than list positions — and `expected` stays comparable with the mailbox instead
|
|
1431
1431
|
of double-counting a duplicate into a false `over`.
|
|
1432
1432
|
|
|
1433
|
-
`imap:` ids are not reconciled
|
|
1434
|
-
one mailbox, so the mis-targeting class this exists for cannot
|
|
1435
|
-
batch of only `imap:` ids returns no `countDelta` rather than a
|
|
1433
|
+
`imap:` ids are not reconciled by `countDelta`. An IMAP UID names exactly one
|
|
1434
|
+
message in exactly one mailbox, so the mis-targeting class this exists for cannot
|
|
1435
|
+
occur there; a batch of only `imap:` ids returns no `countDelta` rather than a
|
|
1436
|
+
fabricated one.
|
|
1437
|
+
|
|
1438
|
+
They carry their own post-condition check instead. `delete-message` and
|
|
1439
|
+
`move-message` on an `imap:` id return a **`verification`** object in
|
|
1440
|
+
`structuredContent`:
|
|
1441
|
+
|
|
1442
|
+
```json
|
|
1443
|
+
{
|
|
1444
|
+
"verification": {
|
|
1445
|
+
"verdict": "verified",
|
|
1446
|
+
"how": "COPYUID: UID 5 arrived in \"Archive\" as UID 91"
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
| `verdict` | Meaning |
|
|
1452
|
+
|---|---|
|
|
1453
|
+
| `verified` | The effect was **observed** — either the server's UIDPLUS `COPYUID` named the message's new UID in the destination, or the UID is no longer in the source mailbox. |
|
|
1454
|
+
| `unverified` | The server **accepted** the command and nothing could confirm the effect. Populates `why`. |
|
|
1455
|
+
|
|
1456
|
+
`unverified` is **not a failure** and must not be rendered as one — it means
|
|
1457
|
+
"accepted, no observation either way". It is reported rather than hidden because
|
|
1458
|
+
an absent verification must never read as a successful one, the same rule the
|
|
1459
|
+
collateral diff follows. A message that is still in the source mailbox after an
|
|
1460
|
+
accepted move is reported `unverified` rather than failed, because a Gmail label
|
|
1461
|
+
store can legitimately keep a message visible in an all-mail view after a move.
|
|
1436
1462
|
|
|
1437
1463
|
### `APPLE_MAIL_MCP_AUDIT_LOG` — opt-in forensic log
|
|
1438
1464
|
|
package/build/index.js
CHANGED
|
@@ -79503,6 +79503,35 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79503
79503
|
end if
|
|
79504
79504
|
set _sLo to _sHi + 1
|
|
79505
79505
|
end repeat
|
|
79506
|
+
-- #179: the loop above is bounded by the count Mail JUST reported,
|
|
79507
|
+
-- and that count can lag the mailbox (#155). Positions past the bound
|
|
79508
|
+
-- are never requested, so \u2014 unlike a slice that failed \u2014 they leave
|
|
79509
|
+
-- no trace in _sMiss, and the record would claim a complete
|
|
79510
|
+
-- observation while every message past the bound looks like it
|
|
79511
|
+
-- disappeared. That is a FABRICATED finding with names attached,
|
|
79512
|
+
-- which is worse than the gap it papers over.
|
|
79513
|
+
--
|
|
79514
|
+
-- Probe exactly ONE position past the bound. One, not a slice: an
|
|
79515
|
+
-- out-of-range RANGE raises as a whole, so an over-requested slice
|
|
79516
|
+
-- could not distinguish "nothing there" from "count was low by more
|
|
79517
|
+
-- than a chunk". If a message is there, the count was low and the
|
|
79518
|
+
-- unread tail is recorded as a hole, which makes this snapshot
|
|
79519
|
+
-- PARTIAL under the existing rules and withholds the halves a
|
|
79520
|
+
-- truncation would poison.
|
|
79521
|
+
try
|
|
79522
|
+
set _sOverId to ((id of message (${countVar} + 1) of ${mbVar}) as string)
|
|
79523
|
+
-- A specifier that CLAMPS rather than raising hands back the LAST
|
|
79524
|
+
-- message instead of failing. That is not evidence of a truncation,
|
|
79525
|
+
-- so only an id this enumeration did not already record counts.
|
|
79526
|
+
set _sSeen to false
|
|
79527
|
+
repeat with _sP in _sPairs
|
|
79528
|
+
if (contents of _sP) starts with (_sOverId & "${SNAP_PAIR}") then set _sSeen to true
|
|
79529
|
+
end repeat
|
|
79530
|
+
if not _sSeen then
|
|
79531
|
+
if _sMiss is not "" then set _sMiss to _sMiss & ","
|
|
79532
|
+
set _sMiss to _sMiss & ((${countVar} + 1) as string) & "-end"
|
|
79533
|
+
end if
|
|
79534
|
+
end try
|
|
79506
79535
|
if _sMiss is not "" then
|
|
79507
79536
|
if (count of _sPairs) is 0 then
|
|
79508
79537
|
set _sStatus to "unavailable"
|
|
@@ -83842,6 +83871,27 @@ function assertMutated(result, what) {
|
|
|
83842
83871
|
if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
|
|
83843
83872
|
return result;
|
|
83844
83873
|
}
|
|
83874
|
+
async function verifyMoved(client, moved, uid, srcPath, destPath) {
|
|
83875
|
+
const newUid = moved.uidMap?.get(uid);
|
|
83876
|
+
if (newUid !== void 0) {
|
|
83877
|
+
return {
|
|
83878
|
+
verdict: "verified",
|
|
83879
|
+
how: `COPYUID: UID ${uid} arrived in "${destPath}" as UID ${newUid}`
|
|
83880
|
+
};
|
|
83881
|
+
}
|
|
83882
|
+
try {
|
|
83883
|
+
const stillThere = await client.fetchOne(String(uid), { uid: true }, { uid: true });
|
|
83884
|
+
if (!stillThere) {
|
|
83885
|
+
return { verdict: "verified", how: `UID ${uid} is no longer present in "${srcPath}"` };
|
|
83886
|
+
}
|
|
83887
|
+
return {
|
|
83888
|
+
verdict: "unverified",
|
|
83889
|
+
why: `the server accepted the MOVE, but UID ${uid} is still present in "${srcPath}" and this server does not advertise UIDPLUS, so arrival in "${destPath}" could not be confirmed. A Gmail label store can legitimately keep a message in an all-mail view after a move, so this is not reported as a failure`
|
|
83890
|
+
};
|
|
83891
|
+
} catch (e) {
|
|
83892
|
+
return { verdict: "unverified", why: `the post-move check could not run: ${errText(e)}` };
|
|
83893
|
+
}
|
|
83894
|
+
}
|
|
83845
83895
|
var poolConnect = defaultConnect;
|
|
83846
83896
|
var pools = /* @__PURE__ */ new Map();
|
|
83847
83897
|
function poolKey(cfg) {
|
|
@@ -84180,11 +84230,16 @@ async function imapMoveMessageById(id, destMailbox, deps = {}) {
|
|
|
84180
84230
|
const destPath = dest.kind === "found" ? dest.path : resolveMailboxPath(destMailbox, "list");
|
|
84181
84231
|
const lock = await client.getMailboxLock(ref.path);
|
|
84182
84232
|
try {
|
|
84183
|
-
assertMutated(
|
|
84233
|
+
const moved = assertMutated(
|
|
84184
84234
|
await client.messageMove([ref.uid], destPath, { uid: true }),
|
|
84185
84235
|
`IMAP move of UID ${ref.uid} to "${destPath}"`
|
|
84186
84236
|
);
|
|
84187
|
-
|
|
84237
|
+
const verification = await verifyMoved(client, moved, ref.uid, ref.path, destPath);
|
|
84238
|
+
return {
|
|
84239
|
+
success: true,
|
|
84240
|
+
info: verification.verdict === "verified" ? `Moved UID ${ref.uid} to "${destPath}" via IMAP (verified: ${verification.how}).` : `Moved UID ${ref.uid} to "${destPath}" via IMAP \u2014 UNVERIFIED: ${verification.why}.`,
|
|
84241
|
+
verification
|
|
84242
|
+
};
|
|
84188
84243
|
} catch (e) {
|
|
84189
84244
|
return {
|
|
84190
84245
|
success: false,
|
|
@@ -84226,21 +84281,38 @@ async function trashUids(client, uids, srcPath) {
|
|
|
84226
84281
|
);
|
|
84227
84282
|
return { dest, expunged: true };
|
|
84228
84283
|
}
|
|
84229
|
-
assertMutated(
|
|
84284
|
+
const moved = assertMutated(
|
|
84230
84285
|
await client.messageMove(uids, dest, { uid: true }),
|
|
84231
84286
|
`IMAP move of ${uids.length} message(s) from "${srcPath}" to "${dest}"`
|
|
84232
84287
|
);
|
|
84233
|
-
return { dest, expunged: false };
|
|
84288
|
+
return { dest, expunged: false, moved };
|
|
84289
|
+
}
|
|
84290
|
+
async function verifyExpunged(client, uid, path) {
|
|
84291
|
+
try {
|
|
84292
|
+
const stillThere = await client.fetchOne(String(uid), { uid: true }, { uid: true });
|
|
84293
|
+
if (!stillThere) {
|
|
84294
|
+
return { verdict: "verified", how: `UID ${uid} is no longer present in "${path}"` };
|
|
84295
|
+
}
|
|
84296
|
+
return {
|
|
84297
|
+
verdict: "unverified",
|
|
84298
|
+
why: `the server accepted the EXPUNGE but UID ${uid} is still present in "${path}"`
|
|
84299
|
+
};
|
|
84300
|
+
} catch (e) {
|
|
84301
|
+
return { verdict: "unverified", why: `the post-delete check could not run: ${errText(e)}` };
|
|
84302
|
+
}
|
|
84234
84303
|
}
|
|
84235
84304
|
async function imapDeleteMessageById(id, deps = {}) {
|
|
84236
84305
|
const ref = decodeImapId(id);
|
|
84237
84306
|
if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
|
|
84238
84307
|
return withMailbox(ref.path, depsForMessageRef(ref, deps), async (client) => {
|
|
84239
84308
|
try {
|
|
84240
|
-
const { dest, expunged } = await trashUids(client, [ref.uid], ref.path);
|
|
84309
|
+
const { dest, expunged, moved } = await trashUids(client, [ref.uid], ref.path);
|
|
84310
|
+
const verification = expunged || !moved ? await verifyExpunged(client, ref.uid, ref.path) : await verifyMoved(client, moved, ref.uid, ref.path, dest);
|
|
84311
|
+
const what = expunged ? `Permanently deleted UID ${ref.uid} from Trash ("${ref.path}") via IMAP` : `Moved UID ${ref.uid} to Trash ("${dest}") via IMAP`;
|
|
84241
84312
|
return {
|
|
84242
84313
|
success: true,
|
|
84243
|
-
info:
|
|
84314
|
+
info: verification.verdict === "verified" ? `${what} (verified: ${verification.how}).` : `${what} \u2014 UNVERIFIED: ${verification.why}.`,
|
|
84315
|
+
verification
|
|
84244
84316
|
};
|
|
84245
84317
|
} catch (e) {
|
|
84246
84318
|
return { success: false, error: `IMAP delete failed for UID ${ref.uid}: ${errText(e)}` };
|
|
@@ -84820,10 +84892,12 @@ function formatMergedRows(rows, showReadState = true) {
|
|
|
84820
84892
|
async function routeMessage(id, opts) {
|
|
84821
84893
|
if (decodeImapId(id)) {
|
|
84822
84894
|
const r = await opts.imap();
|
|
84823
|
-
|
|
84895
|
+
if (!r.success) return errorResponse(r.error ?? opts.fail);
|
|
84896
|
+
const structured = opts.structuredFromResult ? opts.structuredFromResult(r) : opts.structured;
|
|
84897
|
+
return successResponse(
|
|
84824
84898
|
r.info ?? opts.ok,
|
|
84825
|
-
|
|
84826
|
-
)
|
|
84899
|
+
r.verification && structured ? { ...structured, verification: r.verification } : structured
|
|
84900
|
+
);
|
|
84827
84901
|
}
|
|
84828
84902
|
return opts.apple();
|
|
84829
84903
|
}
|
|
@@ -85387,6 +85461,13 @@ var COUNT_DELTA_OUTPUT_SCHEMA = external_exports.array(
|
|
|
85387
85461
|
note: external_exports.string().optional()
|
|
85388
85462
|
})
|
|
85389
85463
|
).optional();
|
|
85464
|
+
var VERIFICATION_OUTPUT_SCHEMA = external_exports.object({
|
|
85465
|
+
verdict: external_exports.enum(["verified", "unverified"]),
|
|
85466
|
+
/** Present on `verified`: what was observed. */
|
|
85467
|
+
how: external_exports.string().optional(),
|
|
85468
|
+
/** Present on `unverified`: why no observation was possible. */
|
|
85469
|
+
why: external_exports.string().optional()
|
|
85470
|
+
}).optional();
|
|
85390
85471
|
var CHECK_ITEM_SCHEMA = external_exports.object({}).passthrough();
|
|
85391
85472
|
var require2 = createRequire(import.meta.url);
|
|
85392
85473
|
var { version: version2 } = require2("../package.json");
|
|
@@ -86239,7 +86320,8 @@ registerTool(
|
|
|
86239
86320
|
outputSchema: {
|
|
86240
86321
|
ok: external_exports.boolean().optional(),
|
|
86241
86322
|
id: external_exports.string().optional(),
|
|
86242
|
-
countDelta: COUNT_DELTA_OUTPUT_SCHEMA
|
|
86323
|
+
countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
|
|
86324
|
+
verification: VERIFICATION_OUTPUT_SCHEMA
|
|
86243
86325
|
}
|
|
86244
86326
|
},
|
|
86245
86327
|
withErrorHandling(
|
|
@@ -86279,7 +86361,8 @@ registerTool(
|
|
|
86279
86361
|
ok: external_exports.boolean().optional(),
|
|
86280
86362
|
id: external_exports.string().optional(),
|
|
86281
86363
|
mailbox: external_exports.string().optional(),
|
|
86282
|
-
countDelta: COUNT_DELTA_OUTPUT_SCHEMA
|
|
86364
|
+
countDelta: COUNT_DELTA_OUTPUT_SCHEMA,
|
|
86365
|
+
verification: VERIFICATION_OUTPUT_SCHEMA
|
|
86283
86366
|
}
|
|
86284
86367
|
},
|
|
86285
86368
|
withErrorHandling(
|
package/package.json
CHANGED