apple-mail-mcp 1.6.5 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -335,35 +335,48 @@ export declare class AppleMailManager {
|
|
|
335
335
|
private moveMessageInternal;
|
|
336
336
|
moveMessage(id: string, mailbox: string, account?: string): boolean;
|
|
337
337
|
/**
|
|
338
|
-
*
|
|
338
|
+
* Run one operation over many message IDs in a SINGLE osascript invocation.
|
|
339
339
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
340
|
+
* Previously each batch method looped and called the per-id method, so a
|
|
341
|
+
* 100-id batch spawned 100 osascript processes — each one re-resolving
|
|
342
|
+
* accounts and walking the whole account→mailbox tree — all serialized
|
|
343
|
+
* through the gate (issue #31). This walks the tree exactly once: for each
|
|
344
|
+
* mailbox it probes the still-pending IDs with `whose id is` (indexed, so
|
|
345
|
+
* effectively free) and applies `operation` to any match, tracking found IDs
|
|
346
|
+
* so it can stop early once all are accounted for. Per-id outcomes come back
|
|
347
|
+
* as control-char-delimited `id<FS>status` records (status: `ok`,
|
|
348
|
+
* `notfound`, or `error:<msg>`), and results are returned in input order.
|
|
349
|
+
*
|
|
350
|
+
* `setup` runs once before the walk (used by move to resolve the destination);
|
|
351
|
+
* it may bail the whole batch by returning a `BATCH_FATAL`-prefixed string.
|
|
352
|
+
*/
|
|
353
|
+
private runBatchOperation;
|
|
354
|
+
/**
|
|
355
|
+
* Delete multiple messages at once (single tree walk — see runBatchOperation).
|
|
342
356
|
*/
|
|
343
357
|
batchDeleteMessages(ids: string[]): BatchOperationResult[];
|
|
344
358
|
/**
|
|
345
|
-
* Move multiple messages to a mailbox at once.
|
|
359
|
+
* Move multiple messages to a mailbox at once (single tree walk).
|
|
346
360
|
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* @returns Array of results for each message
|
|
361
|
+
* The destination is resolved once (account-scoped, ambiguity-aware — a name
|
|
362
|
+
* matching more than one mailbox fails the whole batch rather than guessing),
|
|
363
|
+
* then every matched message is moved in the same walk.
|
|
351
364
|
*/
|
|
352
365
|
batchMoveMessages(ids: string[], mailbox: string, account?: string): BatchOperationResult[];
|
|
353
366
|
/**
|
|
354
|
-
* Mark multiple messages as read at once.
|
|
367
|
+
* Mark multiple messages as read at once (single tree walk).
|
|
355
368
|
*/
|
|
356
369
|
batchMarkAsRead(ids: string[]): BatchOperationResult[];
|
|
357
370
|
/**
|
|
358
|
-
* Mark multiple messages as unread at once.
|
|
371
|
+
* Mark multiple messages as unread at once (single tree walk).
|
|
359
372
|
*/
|
|
360
373
|
batchMarkAsUnread(ids: string[]): BatchOperationResult[];
|
|
361
374
|
/**
|
|
362
|
-
* Flag multiple messages at once.
|
|
375
|
+
* Flag multiple messages at once (single tree walk).
|
|
363
376
|
*/
|
|
364
377
|
batchFlagMessages(ids: string[]): BatchOperationResult[];
|
|
365
378
|
/**
|
|
366
|
-
* Unflag multiple messages at once.
|
|
379
|
+
* Unflag multiple messages at once (single tree walk).
|
|
367
380
|
*/
|
|
368
381
|
batchUnflagMessages(ids: string[]): BatchOperationResult[];
|
|
369
382
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,OAAO,EACP,OAAO,EACP,UAAU,EACV,iBAAiB,EACjB,SAAS,EAET,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACb,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -70,6 +70,7 @@ const DIAG_FIELD_SEP = "\x1dF\x1d"; // between diagnostics fields
|
|
|
70
70
|
const DIAG_ITEM_SEP = "\x1dM\x1d"; // between diagnostics list items
|
|
71
71
|
const CONTENT_MARKER = "\x1dCONTENT\x1d"; // subject/plain-text boundary
|
|
72
72
|
const HTML_MARKER = "\x1dHTML\x1d"; // plain-text/source boundary
|
|
73
|
+
const BATCH_FATAL = "\x1dFATAL\x1d"; // prefix for a whole-batch failure (e.g. bad destination)
|
|
73
74
|
/**
|
|
74
75
|
* Merge a per-account SearchDiagnostics into an aggregate (all-accounts) one.
|
|
75
76
|
*
|
|
@@ -1466,90 +1467,174 @@ export class AppleMailManager {
|
|
|
1466
1467
|
// Batch Operations
|
|
1467
1468
|
// ===========================================================================
|
|
1468
1469
|
/**
|
|
1469
|
-
*
|
|
1470
|
+
* Run one operation over many message IDs in a SINGLE osascript invocation.
|
|
1470
1471
|
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1472
|
+
* Previously each batch method looped and called the per-id method, so a
|
|
1473
|
+
* 100-id batch spawned 100 osascript processes — each one re-resolving
|
|
1474
|
+
* accounts and walking the whole account→mailbox tree — all serialized
|
|
1475
|
+
* through the gate (issue #31). This walks the tree exactly once: for each
|
|
1476
|
+
* mailbox it probes the still-pending IDs with `whose id is` (indexed, so
|
|
1477
|
+
* effectively free) and applies `operation` to any match, tracking found IDs
|
|
1478
|
+
* so it can stop early once all are accounted for. Per-id outcomes come back
|
|
1479
|
+
* as control-char-delimited `id<FS>status` records (status: `ok`,
|
|
1480
|
+
* `notfound`, or `error:<msg>`), and results are returned in input order.
|
|
1481
|
+
*
|
|
1482
|
+
* `setup` runs once before the walk (used by move to resolve the destination);
|
|
1483
|
+
* it may bail the whole batch by returning a `BATCH_FATAL`-prefixed string.
|
|
1473
1484
|
*/
|
|
1474
|
-
|
|
1475
|
-
|
|
1485
|
+
runBatchOperation(ids, operation, setup = "") {
|
|
1486
|
+
// Keep the numeric IDs paired with their original string form and 1-based
|
|
1487
|
+
// position. The AppleScript reports outcomes by POSITION, not by id: a Mail
|
|
1488
|
+
// id large enough to exceed AppleScript's 2^29 integer range coerces to
|
|
1489
|
+
// scientific notation under `as string` (999999999 -> "9.99999999E+8"), so
|
|
1490
|
+
// echoing the id back can't be matched to the input. Positions are always
|
|
1491
|
+
// small integers, so they round-trip cleanly.
|
|
1492
|
+
const valid = [];
|
|
1476
1493
|
for (const id of ids) {
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
id,
|
|
1480
|
-
success,
|
|
1481
|
-
error: success ? undefined : "Failed to delete message",
|
|
1482
|
-
});
|
|
1494
|
+
const num = Number(id);
|
|
1495
|
+
if (Number.isFinite(num))
|
|
1496
|
+
valid.push({ id, num });
|
|
1483
1497
|
}
|
|
1484
|
-
|
|
1498
|
+
if (valid.length === 0) {
|
|
1499
|
+
return ids.map((id) => ({ id, success: false, error: "Invalid message ID" }));
|
|
1500
|
+
}
|
|
1501
|
+
const script = buildAppLevelScript(`
|
|
1502
|
+
try
|
|
1503
|
+
${setup}
|
|
1504
|
+
set _out to ""
|
|
1505
|
+
set _done to {}
|
|
1506
|
+
set _ids to {${valid.map((v) => v.num).join(", ")}}
|
|
1507
|
+
set _total to count of _ids
|
|
1508
|
+
repeat with acct in accounts
|
|
1509
|
+
if (count of _done) is _total then exit repeat
|
|
1510
|
+
repeat with mb in (mailboxes of acct)
|
|
1511
|
+
if (count of _done) is _total then exit repeat
|
|
1512
|
+
repeat with _idx from 1 to _total
|
|
1513
|
+
if _idx is not in _done then
|
|
1514
|
+
set _theId to item _idx of _ids
|
|
1515
|
+
try
|
|
1516
|
+
set _m to (messages of mb whose id is _theId)
|
|
1517
|
+
if (count of _m) > 0 then
|
|
1518
|
+
set _msg to item 1 of _m
|
|
1519
|
+
${operation}
|
|
1520
|
+
set end of _done to _idx
|
|
1521
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}ok${RECORD_SEP}"
|
|
1522
|
+
end if
|
|
1523
|
+
on error _e
|
|
1524
|
+
set end of _done to _idx
|
|
1525
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}error:" & _e & "${RECORD_SEP}"
|
|
1526
|
+
end try
|
|
1527
|
+
end if
|
|
1528
|
+
end repeat
|
|
1529
|
+
end repeat
|
|
1530
|
+
end repeat
|
|
1531
|
+
repeat with _idx from 1 to _total
|
|
1532
|
+
if _idx is not in _done then set _out to _out & (_idx as string) & "${FIELD_SEP}notfound${RECORD_SEP}"
|
|
1533
|
+
end repeat
|
|
1534
|
+
return _out
|
|
1535
|
+
on error errMsg
|
|
1536
|
+
return "${BATCH_FATAL}" & errMsg
|
|
1537
|
+
end try
|
|
1538
|
+
`);
|
|
1539
|
+
// Generous timeout: one walk over the tree with indexed id probes. Scale a
|
|
1540
|
+
// little with batch size, capped.
|
|
1541
|
+
const timeoutMs = Math.min(180000, 60000 + valid.length * 500);
|
|
1542
|
+
const result = executeAppleScript(script, { timeoutMs });
|
|
1543
|
+
if (!result.success) {
|
|
1544
|
+
const err = result.error || "Batch operation failed";
|
|
1545
|
+
return ids.map((id) => ({ id, success: false, error: err }));
|
|
1546
|
+
}
|
|
1547
|
+
if (result.output.startsWith(BATCH_FATAL)) {
|
|
1548
|
+
const err = result.output.slice(BATCH_FATAL.length);
|
|
1549
|
+
return ids.map((id) => ({ id, success: false, error: err }));
|
|
1550
|
+
}
|
|
1551
|
+
// Map by-position outcomes back to the original id strings.
|
|
1552
|
+
const byId = new Map();
|
|
1553
|
+
for (const rec of result.output.split(RECORD_SEP)) {
|
|
1554
|
+
if (!rec)
|
|
1555
|
+
continue;
|
|
1556
|
+
const sep = rec.indexOf(FIELD_SEP);
|
|
1557
|
+
if (sep < 0)
|
|
1558
|
+
continue;
|
|
1559
|
+
const pos = Number(rec.slice(0, sep));
|
|
1560
|
+
const status = rec.slice(sep + FIELD_SEP.length);
|
|
1561
|
+
const entry = valid[pos - 1];
|
|
1562
|
+
if (!entry)
|
|
1563
|
+
continue;
|
|
1564
|
+
const id = entry.id;
|
|
1565
|
+
if (status === "ok") {
|
|
1566
|
+
byId.set(id, { id, success: true });
|
|
1567
|
+
}
|
|
1568
|
+
else if (status === "notfound") {
|
|
1569
|
+
byId.set(id, { id, success: false, error: "Message not found" });
|
|
1570
|
+
}
|
|
1571
|
+
else if (status.startsWith("error:")) {
|
|
1572
|
+
byId.set(id, { id, success: false, error: status.slice("error:".length) });
|
|
1573
|
+
}
|
|
1574
|
+
else {
|
|
1575
|
+
byId.set(id, { id, success: false, error: status || "Unknown error" });
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return ids.map((id) => byId.get(id) ??
|
|
1579
|
+
(Number.isFinite(Number(id))
|
|
1580
|
+
? { id, success: false, error: "No result returned" }
|
|
1581
|
+
: { id, success: false, error: "Invalid message ID" }));
|
|
1485
1582
|
}
|
|
1486
1583
|
/**
|
|
1487
|
-
*
|
|
1584
|
+
* Delete multiple messages at once (single tree walk — see runBatchOperation).
|
|
1585
|
+
*/
|
|
1586
|
+
batchDeleteMessages(ids) {
|
|
1587
|
+
return this.runBatchOperation(ids, "delete _msg");
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Move multiple messages to a mailbox at once (single tree walk).
|
|
1488
1591
|
*
|
|
1489
|
-
*
|
|
1490
|
-
*
|
|
1491
|
-
*
|
|
1492
|
-
* @returns Array of results for each message
|
|
1592
|
+
* The destination is resolved once (account-scoped, ambiguity-aware — a name
|
|
1593
|
+
* matching more than one mailbox fails the whole batch rather than guessing),
|
|
1594
|
+
* then every matched message is moved in the same walk.
|
|
1493
1595
|
*/
|
|
1494
1596
|
batchMoveMessages(ids, mailbox, account) {
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1597
|
+
const targetAccount = this.resolveAccount(account);
|
|
1598
|
+
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
1599
|
+
const safeMailbox = escapeForAppleScript(targetMailbox);
|
|
1600
|
+
const safeAccount = escapeForAppleScript(targetAccount);
|
|
1601
|
+
// Resolved once, before the walk. `mailboxes of account` is already flat
|
|
1602
|
+
// (includes nested mailboxes by path), so we match by exact name and use the
|
|
1603
|
+
// reference directly. A bad/ambiguous destination fails the whole batch.
|
|
1604
|
+
const setup = `
|
|
1605
|
+
set destName to "${safeMailbox}"
|
|
1606
|
+
set destMatches to {}
|
|
1607
|
+
repeat with _dmb in (mailboxes of account "${safeAccount}")
|
|
1608
|
+
if (name of _dmb) is destName then set end of destMatches to _dmb
|
|
1609
|
+
end repeat
|
|
1610
|
+
if (count of destMatches) is 0 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" not found in account \\"${safeAccount}\\""
|
|
1611
|
+
if (count of destMatches) > 1 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" is ambiguous (" & (count of destMatches) & " matches) in account \\"${safeAccount}\\"; move by full path"
|
|
1612
|
+
set destMailbox to item 1 of destMatches`;
|
|
1613
|
+
return this.runBatchOperation(ids, "move _msg to destMailbox", setup);
|
|
1505
1614
|
}
|
|
1506
1615
|
/**
|
|
1507
|
-
* Mark multiple messages as read at once.
|
|
1616
|
+
* Mark multiple messages as read at once (single tree walk).
|
|
1508
1617
|
*/
|
|
1509
1618
|
batchMarkAsRead(ids) {
|
|
1510
|
-
|
|
1511
|
-
for (const id of ids) {
|
|
1512
|
-
const success = this.markAsRead(id);
|
|
1513
|
-
results.push({ id, success, error: success ? undefined : "Failed to mark message as read" });
|
|
1514
|
-
}
|
|
1515
|
-
return results;
|
|
1619
|
+
return this.runBatchOperation(ids, "set read status of _msg to true");
|
|
1516
1620
|
}
|
|
1517
1621
|
/**
|
|
1518
|
-
* Mark multiple messages as unread at once.
|
|
1622
|
+
* Mark multiple messages as unread at once (single tree walk).
|
|
1519
1623
|
*/
|
|
1520
1624
|
batchMarkAsUnread(ids) {
|
|
1521
|
-
|
|
1522
|
-
for (const id of ids) {
|
|
1523
|
-
const success = this.markAsUnread(id);
|
|
1524
|
-
results.push({
|
|
1525
|
-
id,
|
|
1526
|
-
success,
|
|
1527
|
-
error: success ? undefined : "Failed to mark message as unread",
|
|
1528
|
-
});
|
|
1529
|
-
}
|
|
1530
|
-
return results;
|
|
1625
|
+
return this.runBatchOperation(ids, "set read status of _msg to false");
|
|
1531
1626
|
}
|
|
1532
1627
|
/**
|
|
1533
|
-
* Flag multiple messages at once.
|
|
1628
|
+
* Flag multiple messages at once (single tree walk).
|
|
1534
1629
|
*/
|
|
1535
1630
|
batchFlagMessages(ids) {
|
|
1536
|
-
|
|
1537
|
-
for (const id of ids) {
|
|
1538
|
-
const success = this.flagMessage(id);
|
|
1539
|
-
results.push({ id, success, error: success ? undefined : "Failed to flag message" });
|
|
1540
|
-
}
|
|
1541
|
-
return results;
|
|
1631
|
+
return this.runBatchOperation(ids, "set flagged status of _msg to true");
|
|
1542
1632
|
}
|
|
1543
1633
|
/**
|
|
1544
|
-
* Unflag multiple messages at once.
|
|
1634
|
+
* Unflag multiple messages at once (single tree walk).
|
|
1545
1635
|
*/
|
|
1546
1636
|
batchUnflagMessages(ids) {
|
|
1547
|
-
|
|
1548
|
-
for (const id of ids) {
|
|
1549
|
-
const success = this.unflagMessage(id);
|
|
1550
|
-
results.push({ id, success, error: success ? undefined : "Failed to unflag message" });
|
|
1551
|
-
}
|
|
1552
|
-
return results;
|
|
1637
|
+
return this.runBatchOperation(ids, "set flagged status of _msg to false");
|
|
1553
1638
|
}
|
|
1554
1639
|
/**
|
|
1555
1640
|
* List attachments for a message.
|