@rubytech/create-maxy-code 0.1.477 → 0.1.478

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 (151) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +673 -0
  3. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +177 -0
  4. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +97 -0
  5. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +44 -0
  6. package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
  7. package/payload/platform/lib/storage-broker/dist/audit.d.ts +56 -0
  8. package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
  9. package/payload/platform/lib/storage-broker/dist/audit.js +34 -0
  10. package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
  11. package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +70 -1
  12. package/payload/platform/lib/storage-broker/src/audit.ts +111 -0
  13. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  14. package/payload/platform/plugins/admin/PLUGIN.md +7 -1
  15. package/payload/platform/plugins/admin/mcp/dist/index.js +72 -0
  16. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts +94 -0
  18. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts.map +1 -0
  19. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js +229 -0
  20. package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js.map +1 -0
  21. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  22. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  23. package/payload/platform/plugins/email/PLUGIN.md +2 -1
  24. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts +2 -0
  25. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts.map +1 -0
  26. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js +92 -0
  27. package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js.map +1 -0
  28. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +112 -12
  29. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  30. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts +2 -0
  31. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js +54 -0
  33. package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js.map +1 -0
  34. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +170 -1
  35. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts +2 -0
  37. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts.map +1 -0
  38. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js +86 -0
  39. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js.map +1 -0
  40. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts +2 -0
  41. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts.map +1 -0
  42. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js +164 -0
  43. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js.map +1 -0
  44. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts +2 -0
  45. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js +82 -0
  47. package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js.map +1 -0
  48. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +95 -0
  49. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +223 -0
  51. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts +18 -0
  53. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts.map +1 -0
  54. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js +59 -0
  55. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js.map +1 -0
  56. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts +48 -0
  57. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts.map +1 -0
  58. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js +79 -0
  59. package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js.map +1 -0
  60. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts +44 -0
  61. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts.map +1 -0
  62. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js +63 -0
  63. package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js.map +1 -0
  64. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts +2 -0
  65. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts.map +1 -0
  66. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js +55 -0
  67. package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js.map +1 -0
  68. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  69. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +64 -13
  70. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  71. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
  72. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +11 -1
  73. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
  74. package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
  75. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +16 -3
  76. package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
  77. package/payload/platform/plugins/email/references/email-reference.md +36 -0
  78. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts +2 -0
  79. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts.map +1 -0
  80. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js +18 -0
  81. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js.map +1 -0
  82. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts +13 -0
  83. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts.map +1 -0
  84. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js +10 -0
  85. package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js.map +1 -0
  86. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +53 -0
  87. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  88. package/payload/platform/scripts/cpu-triage-run.sh +117 -0
  89. package/payload/platform/scripts/cpu-triage.sh +23 -4
  90. package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts +40 -0
  91. package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts.map +1 -0
  92. package/payload/platform/services/claude-session-manager/dist/activity-range.js +68 -0
  93. package/payload/platform/services/claude-session-manager/dist/activity-range.js.map +1 -0
  94. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +40 -8
  95. package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/activity-rows.js +74 -7
  97. package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
  98. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  99. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  100. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  101. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  102. package/payload/platform/services/claude-session-manager/dist/http-server.js +9 -0
  103. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  104. package/payload/server/{chunk-56WJMBQQ.js → chunk-3XLLTG6R.js} +14 -0
  105. package/payload/server/{chunk-JECAP3Z2.js → chunk-XGNRSM57.js} +3 -0
  106. package/payload/server/maxy-edge.js +1 -1
  107. package/payload/server/public/activity.html +6 -6
  108. package/payload/server/public/assets/{AdminLoginScreens-mEKP4pDi.js → AdminLoginScreens-DR5eLF-a.js} +1 -1
  109. package/payload/server/public/assets/AdminShell-D9zaUxPd.js +2 -0
  110. package/payload/server/public/assets/{Checkbox-CN4uQ80w.js → Checkbox-DcbbAIe5.js} +1 -1
  111. package/payload/server/public/assets/activity-C9P5NjEZ.js +1 -0
  112. package/payload/server/public/assets/{admin-BVhgfgs1.js → admin-D2KLZnQW.js} +1 -1
  113. package/payload/server/public/assets/{browser-D-HMmHcM.js → browser-CDWqEiCD.js} +1 -1
  114. package/payload/server/public/assets/{calendar-tGWvFyFn.js → calendar-BG7fOpbB.js} +1 -1
  115. package/payload/server/public/assets/chat-RyH-WYQC.js +1 -0
  116. package/payload/server/public/assets/chevron-left-DmXCr6nB.js +1 -0
  117. package/payload/server/public/assets/data-BaY_LkLR.js +1 -0
  118. package/payload/server/public/assets/{graph-zEw610xK.js → graph-DRloNic4.js} +1 -1
  119. package/payload/server/public/assets/{graph-labels-qnBleOE6.js → graph-labels-DD-GqS4v.js} +1 -1
  120. package/payload/server/public/assets/{maximize-2-BlTjXT_Y.js → maximize-2-CzDaBugs.js} +1 -1
  121. package/payload/server/public/assets/{operator-C7oIw2PG.js → operator-BZeeyait.js} +1 -1
  122. package/payload/server/public/assets/{page-DL6Zsdvk.js → page-B0obcg3b.js} +1 -1
  123. package/payload/server/public/assets/{page-CTP7OFZa.js → page-ef-6rRPL.js} +1 -1
  124. package/payload/server/public/assets/{public-r1A9dqR_.js → public-9D3MMOkC.js} +1 -1
  125. package/payload/server/public/assets/{rotate-ccw-CzkkKx4-.js → rotate-ccw-DXEKUhXP.js} +1 -1
  126. package/payload/server/public/assets/tasks-BB0L7e5a.js +1 -0
  127. package/payload/server/public/assets/{time-entry-format-j669DgXj.js → time-entry-format-CsYAjplS.js} +1 -1
  128. package/payload/server/public/assets/{triangle-alert-BPgCgIef.js → triangle-alert-0lSBIUg-.js} +1 -1
  129. package/payload/server/public/assets/{useCopyFeedback-BmLa2aes.js → useCopyFeedback-jTBQ2h0V.js} +1 -1
  130. package/payload/server/public/assets/{useSelectionMode-DnnM7A8-.js → useSelectionMode-BooArySZ.js} +1 -1
  131. package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +1 -0
  132. package/payload/server/public/assets/{useVoiceRecorder-u2dwZfen.js → useVoiceRecorder-PUT8q_sm.js} +1 -1
  133. package/payload/server/public/browser.html +5 -5
  134. package/payload/server/public/calendar.html +6 -6
  135. package/payload/server/public/chat.html +12 -12
  136. package/payload/server/public/data.html +11 -11
  137. package/payload/server/public/graph.html +11 -11
  138. package/payload/server/public/index.html +14 -14
  139. package/payload/server/public/operator.html +14 -14
  140. package/payload/server/public/public.html +12 -12
  141. package/payload/server/public/tasks.html +5 -5
  142. package/payload/server/server.js +153 -18
  143. package/payload/server/{src-JYPKMWJR.js → src-XQ63FPRE.js} +3 -1
  144. package/payload/server/public/assets/AdminShell-BwSaZ88d.js +0 -2
  145. package/payload/server/public/assets/activity-CIQ8ozNZ.js +0 -1
  146. package/payload/server/public/assets/chat-DkttVxAz.js +0 -1
  147. package/payload/server/public/assets/chevron-left-CZ4ez9G5.js +0 -1
  148. package/payload/server/public/assets/data-P-mcmnNi.js +0 -1
  149. package/payload/server/public/assets/tasks-fIJwYFWG.js +0 -1
  150. package/payload/server/public/assets/useSubAccountSwitcher-C_E8h07P.css +0 -1
  151. /package/payload/server/public/assets/{useSubAccountSwitcher-CFa6ZAT1.js → useSubAccountSwitcher-BZnzc5C3.js} +0 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Standing reconciliation of sends against copies actually present in \Sent.
3
+ *
4
+ * Post-condition reporting on each tool only covers sends the agent was
5
+ * present for. A mailbox that silently stops filing copies emits nothing at
6
+ * all — that no-event blind spot is what let four days pass before an operator
7
+ * noticed by hand (Task 1822).
8
+ *
9
+ * The copy status comes from reading \Sent, not from the ledger's own recorded
10
+ * outcome. Trusting the recorded flag would only replay what the tool already
11
+ * said; re-reading the folder is what makes this independent evidence, and it
12
+ * is the only way a copy that was filed and later removed shows up.
13
+ *
14
+ * Presence is tested per Message-ID, never by comparing bulk counts. \Sent
15
+ * holds everything the mailbox owner sends from any client, so a total count
16
+ * is dominated by messages this plugin never sent: a mailbox filing none of
17
+ * our sends still reads as healthy the moment its owner sends a few from their
18
+ * phone. Bulk comparison would report clean on precisely the incident this
19
+ * task exists to catch.
20
+ */
21
+ export const SWEEP_WINDOW_MS = 24 * 60 * 60 * 1000;
22
+ export function formatSweepLine(r) {
23
+ const base = `[email-sent-sweep] op=reconcile account=${r.accountId} mailbox=${r.mailbox} ` +
24
+ `sends=${r.sends} copies=${r.copies} shortfall=${r.shortfall} status=${r.status}`;
25
+ return r.detail ? `${base} detail=${r.detail}` : base;
26
+ }
27
+ export async function sweepAccount(accountId, nowMs, deps) {
28
+ const sinceMs = nowMs - SWEEP_WINDOW_MS;
29
+ const configs = await deps.listConfigs(accountId);
30
+ const ledger = deps.readLedger(sinceMs);
31
+ const results = [];
32
+ for (const config of configs) {
33
+ const messageIds = ledger.filter((e) => e.mailbox === config.email).map((e) => e.messageId);
34
+ const sends = messageIds.length;
35
+ // A mailbox that sent nothing needs no connection to report clean. Opening
36
+ // one per idle mailbox every hour is cost with no finding behind it.
37
+ if (sends === 0) {
38
+ results.push({
39
+ accountId,
40
+ mailbox: config.email,
41
+ sends: 0,
42
+ copies: 0,
43
+ shortfall: 0,
44
+ status: "clean",
45
+ });
46
+ continue;
47
+ }
48
+ // Each mailbox is reported independently: one unreachable server must not
49
+ // suppress the result for every other mailbox on the account.
50
+ try {
51
+ const password = await deps.loadPassword(config.email);
52
+ const { missing } = await deps.findMissingInSent(config, password, messageIds);
53
+ const shortfall = missing.length;
54
+ results.push({
55
+ accountId,
56
+ mailbox: config.email,
57
+ sends,
58
+ copies: sends - shortfall,
59
+ shortfall,
60
+ status: shortfall > 0 ? "shortfall" : "clean",
61
+ });
62
+ }
63
+ catch (err) {
64
+ // A mailbox that could not be read is not a mailbox losing copies.
65
+ // Reporting a shortfall here would invent a finding out of an outage.
66
+ results.push({
67
+ accountId,
68
+ mailbox: config.email,
69
+ sends,
70
+ copies: 0,
71
+ shortfall: 0,
72
+ status: "check-failed",
73
+ detail: err instanceof Error ? err.message : String(err),
74
+ });
75
+ }
76
+ }
77
+ return results;
78
+ }
79
+ //# sourceMappingURL=sent-copy-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-copy-sweep.js","sourceRoot":"","sources":["../../src/lib/sent-copy-sweep.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAwBnD,MAAM,UAAU,eAAe,CAAC,CAAqB;IACnD,MAAM,IAAI,GACR,2CAA2C,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,OAAO,GAAG;QAC9E,SAAS,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,MAAM,cAAc,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC;IACpF,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,SAAiB,EACjB,KAAa,EACb,IAAe;IAEf,MAAM,OAAO,GAAG,KAAK,GAAG,eAAe,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5F,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;QAEhC,2EAA2E;QAC3E,qEAAqE;QACrE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC;gBACX,SAAS;gBACT,OAAO,EAAE,MAAM,CAAC,KAAK;gBACrB,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,CAAC;gBACZ,MAAM,EAAE,OAAO;aAChB,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,0EAA0E;QAC1E,8DAA8D;QAC9D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC/E,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;YACjC,OAAO,CAAC,IAAI,CAAC;gBACX,SAAS;gBACT,OAAO,EAAE,MAAM,CAAC,KAAK;gBACrB,KAAK;gBACL,MAAM,EAAE,KAAK,GAAG,SAAS;gBACzB,SAAS;gBACT,MAAM,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO;aAC9C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,mEAAmE;YACnE,sEAAsE;YACtE,OAAO,CAAC,IAAI,CAAC;gBACX,SAAS;gBACT,OAAO,EAAE,MAAM,CAAC,KAAK;gBACrB,KAAK;gBACL,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,CAAC;gBACZ,MAAM,EAAE,cAAc;gBACtB,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACzD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Append-only record of what each send actually achieved, written after the
3
+ * \Sent read-back has returned.
4
+ *
5
+ * This is deliberately not an intent line. "Filed to Sent" written before the
6
+ * read-back would be the same unverified claim the model was already making,
7
+ * relocated into a log where it is harder to challenge (Task 1822). Every row
8
+ * here states an observed outcome.
9
+ *
10
+ * It exists because nothing else records that a send happened at all — SMTP
11
+ * diags only on failure — so the standing sweep had no left-hand side to
12
+ * reconcile \Sent against.
13
+ */
14
+ export interface SentLedgerEntry {
15
+ at: string;
16
+ accountId: string;
17
+ mailbox: string;
18
+ messageId: string;
19
+ copy: "found" | "not-in-sent" | "no-sent-folder" | "check-failed";
20
+ sentFolder?: string;
21
+ sentUid?: number;
22
+ }
23
+ export declare function ledgerPath(): string;
24
+ /**
25
+ * Append one row. Pure append, never read-modify-write.
26
+ *
27
+ * Two sends completing together would otherwise interleave read/read/write/
28
+ * write and silently drop a row, which would show up later as a phantom
29
+ * shortfall. O_APPEND keeps concurrent single-line writes from tearing.
30
+ * Trimming is the sweep's job — see pruneSentLedger.
31
+ */
32
+ export declare function appendSentLedger(entry: SentLedgerEntry): void;
33
+ /**
34
+ * Drop rows past the retention window.
35
+ *
36
+ * Called once per sweep run, from the sweep's own single process, rather than
37
+ * on every send: rewriting the file is the one operation that can lose a
38
+ * concurrent append, so it happens as rarely as possible and never on the send
39
+ * path. A send landing inside the rewrite window loses at most its own
40
+ * observability row, never the mail.
41
+ */
42
+ export declare function pruneSentLedger(): void;
43
+ export declare function readSentLedger(sinceMs: number): SentLedgerEntry[];
44
+ //# sourceMappingURL=sent-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-ledger.d.ts","sourceRoot":"","sources":["../../src/lib/sent-ledger.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,gBAAgB,GAAG,cAAc,CAAC;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAgBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAE7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAYtC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAIjE"}
@@ -0,0 +1,63 @@
1
+ import { existsSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ /** Retained span. The sweep window is 24h; 48h leaves a full window of margin. */
4
+ const RETENTION_MS = 48 * 60 * 60 * 1000;
5
+ export function ledgerPath() {
6
+ const root = process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../..");
7
+ return resolve(root, "config/email-sent-ledger.jsonl");
8
+ }
9
+ function parseLines(raw) {
10
+ const out = [];
11
+ for (const line of raw.split("\n")) {
12
+ if (!line.trim())
13
+ continue;
14
+ try {
15
+ out.push(JSON.parse(line));
16
+ }
17
+ catch {
18
+ // A malformed line is skipped, never thrown on: this file is
19
+ // observability, and a parse error must not take out a send.
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+ /**
25
+ * Append one row. Pure append, never read-modify-write.
26
+ *
27
+ * Two sends completing together would otherwise interleave read/read/write/
28
+ * write and silently drop a row, which would show up later as a phantom
29
+ * shortfall. O_APPEND keeps concurrent single-line writes from tearing.
30
+ * Trimming is the sweep's job — see pruneSentLedger.
31
+ */
32
+ export function appendSentLedger(entry) {
33
+ appendFileSync(ledgerPath(), JSON.stringify(entry) + "\n", { mode: 0o600 });
34
+ }
35
+ /**
36
+ * Drop rows past the retention window.
37
+ *
38
+ * Called once per sweep run, from the sweep's own single process, rather than
39
+ * on every send: rewriting the file is the one operation that can lose a
40
+ * concurrent append, so it happens as rarely as possible and never on the send
41
+ * path. A send landing inside the rewrite window loses at most its own
42
+ * observability row, never the mail.
43
+ */
44
+ export function pruneSentLedger() {
45
+ const path = ledgerPath();
46
+ if (!existsSync(path))
47
+ return;
48
+ const cutoff = Date.now() - RETENTION_MS;
49
+ const all = parseLines(readFileSync(path, "utf8"));
50
+ const kept = all.filter((e) => Date.parse(e.at) >= cutoff);
51
+ if (kept.length === all.length)
52
+ return;
53
+ writeFileSync(path, kept.map((e) => JSON.stringify(e)).join("\n") + (kept.length ? "\n" : ""), {
54
+ mode: 0o600,
55
+ });
56
+ }
57
+ export function readSentLedger(sinceMs) {
58
+ const path = ledgerPath();
59
+ if (!existsSync(path))
60
+ return [];
61
+ return parseLines(readFileSync(path, "utf8")).filter((e) => Date.parse(e.at) >= sinceMs);
62
+ }
63
+ //# sourceMappingURL=sent-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-ledger.js","sourceRoot":"","sources":["../../src/lib/sent-ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyBpC,kFAAkF;AAClF,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzC,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACtF,OAAO,OAAO,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,MAAM,GAAG,GAAsB,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC;YACH,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;YAC7D,6DAA6D;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAsB;IACrD,cAAc,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO;IAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;IACzC,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC;IAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM;QAAE,OAAO;IAEvC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QAC7F,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sent-copy-sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-copy-sweep.d.ts","sourceRoot":"","sources":["../../src/scripts/sent-copy-sweep.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Standing sent-copy reconciliation, one pass over every local account.
3
+ *
4
+ * Spawned by the scheduling heartbeat on an hourly throttle rather than
5
+ * imported by it: the scheduling plugin builds with its own rootDir and does
6
+ * not carry imapflow, so the IMAP work stays on this side of the boundary
7
+ * (Task 1822).
8
+ *
9
+ * Exit code is 0 whenever the sweep ran, including when it found a shortfall —
10
+ * a shortfall is a reported finding, not a failure of the check. A non-zero
11
+ * exit means the sweep itself could not run, which is what keeps "failing" and
12
+ * "did not run" distinguishable (Task 1817).
13
+ */
14
+ import { existsSync } from "node:fs";
15
+ import { resolve } from "node:path";
16
+ import { enumerateValidAccountIds } from "../../../../../lib/account-enumeration/dist/index.js";
17
+ import { listConfigs, readPassword } from "../lib/credentials.js";
18
+ import { readSentLedger, pruneSentLedger } from "../lib/sent-ledger.js";
19
+ import { findMissingInSent } from "../lib/imap.js";
20
+ import { sweepAccount, formatSweepLine } from "../lib/sent-copy-sweep.js";
21
+ async function main() {
22
+ const platformRoot = process.env.PLATFORM_ROOT;
23
+ if (!platformRoot)
24
+ throw new Error("PLATFORM_ROOT is not set");
25
+ const accountsDir = resolve(platformRoot, "..", "data/accounts");
26
+ const accountIds = existsSync(accountsDir) ? enumerateValidAccountIds(accountsDir) : [];
27
+ const now = Date.now();
28
+ let mailboxes = 0;
29
+ for (const accountId of accountIds) {
30
+ const results = await sweepAccount(accountId, now, {
31
+ listConfigs,
32
+ // The password lives in the on-disk store, so this needs no second graph
33
+ // round-trip on top of the one listConfigs already made.
34
+ loadPassword: async (email) => readPassword(email),
35
+ readLedger: readSentLedger,
36
+ findMissingInSent,
37
+ });
38
+ for (const r of results) {
39
+ console.error(formatSweepLine(r));
40
+ mailboxes++;
41
+ }
42
+ }
43
+ // Trim here rather than on the send path: rewriting the ledger is the one
44
+ // operation that can lose a concurrent append, so it runs once an hour in
45
+ // this single process instead of on every send.
46
+ pruneSentLedger();
47
+ console.error(`[email-sent-sweep] done: ${accountIds.length} accounts, ${mailboxes} mailboxes checked`);
48
+ }
49
+ main()
50
+ .then(() => process.exit(0))
51
+ .catch((err) => {
52
+ console.error(`[email-sent-sweep] FAILED ${err instanceof Error ? err.message : String(err)}`);
53
+ process.exit(1);
54
+ });
55
+ //# sourceMappingURL=sent-copy-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-copy-sweep.js","sourceRoot":"","sources":["../../src/scripts/sent-copy-sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,sDAAsD,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE1E,KAAK,UAAU,IAAI;IACjB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC/C,IAAI,CAAC,YAAY;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAE/D,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACxF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,GAAG,EAAE;YACjD,WAAW;YACX,yEAAyE;YACzE,yDAAyD;YACzD,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC;YAClD,UAAU,EAAE,cAAc;YAC1B,iBAAiB;SAClB,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,SAAS,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,0EAA0E;IAC1E,0EAA0E;IAC1E,gDAAgD;IAChD,eAAe,EAAE,CAAC;IAElB,OAAO,CAAC,KAAK,CACX,4BAA4B,UAAU,CAAC,MAAM,cAAc,SAAS,oBAAoB,CACzF,CAAC;AACJ,CAAC;AAED,IAAI,EAAE;KACH,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KAC3B,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"email-draft-send.d.ts","sourceRoot":"","sources":["../../src/tools/email-draft-send.ts"],"names":[],"mappings":"AAIA,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAqD7E"}
1
+ {"version":3,"file":"email-draft-send.d.ts","sourceRoot":"","sources":["../../src/tools/email-draft-send.ts"],"names":[],"mappings":"AAOA,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAiG7E"}
@@ -1,6 +1,7 @@
1
1
  import { loadCredentials } from "../lib/credentials.js";
2
2
  import { sendRaw } from "../lib/smtp.js";
3
- import { fetchDraftSource, deleteDraft } from "../lib/imap.js";
3
+ import { fetchDraftSource, fileDraftToSent } from "../lib/imap.js";
4
+ import { appendSentLedger } from "../lib/sent-ledger.js";
4
5
  export async function emailDraftSend(params) {
5
6
  const result = await loadCredentials(params.accountId, params.mailbox);
6
7
  if ("error" in result)
@@ -32,21 +33,71 @@ export async function emailDraftSend(params) {
32
33
  }
33
34
  throw err;
34
35
  }
35
- // Only after SMTP accepts: remove the draft. A failure here means the message
36
- // already went out but the draft remains — report a qualified success rather
37
- // than letting the removal error masquerade as a send failure (which would
38
- // invite a duplicate re-send).
39
- let deleted = false;
36
+ // Only after SMTP accepts: relocate the draft into \Sent. A failure here means
37
+ // the message already went out, so report a qualified success rather than
38
+ // letting the filing error masquerade as a send failure (which would invite a
39
+ // duplicate re-send). The draft is never expunged — on every failure path it
40
+ // survives where it is, and the note below says so (Task 1821).
41
+ let filing = null;
42
+ let filingError = null;
40
43
  try {
41
- ({ deleted } = await deleteDraft(config, password, params.uid));
44
+ filing = await fileDraftToSent(config, password, params.uid, messageId);
42
45
  }
43
- catch {
44
- deleted = false;
46
+ catch (err) {
47
+ filingError = err instanceof Error ? err.message : String(err);
45
48
  }
46
49
  const idNote = messageId ? ` (Message-ID: ${messageId})` : "";
47
- const cleanupNote = deleted
48
- ? `removed from ${draft.folder}`
49
- : `sent, but draft UID ${params.uid} could not be confirmed removed from ${draft.folder}`;
50
- return `Draft UID ${params.uid} sent to ${recipients.join(", ")}${idNote}; ${cleanupNote}.`;
50
+ let filedNote;
51
+ if (filing === null) {
52
+ filedNote = `the message went out but filing the copy failed, so it is still in ${draft.folder} as UID ${params.uid} (${filingError})`;
53
+ }
54
+ else if (filing.filed) {
55
+ filedNote = `copy confirmed in ${filing.sentFolder} (UID ${filing.sentUid})`;
56
+ }
57
+ else if (filing.reason === "no-sent-folder") {
58
+ filedNote = `no copy was filed — this server advertises no \\Sent folder, so the copy is still in ${filing.draftFolder} as UID ${params.uid}`;
59
+ }
60
+ else if (filing.reason === "move-did-nothing") {
61
+ filedNote = `no copy was filed — the server moved nothing, so the copy is still in ${filing.draftFolder} as UID ${params.uid}`;
62
+ }
63
+ else if (filing.reason === "moved-but-not-in-sent") {
64
+ filedNote = `the message was moved out of ${filing.draftFolder} but no copy was found in the Sent folder`;
65
+ }
66
+ else {
67
+ filedNote = `no copy was filed — draft UID ${params.uid} was no longer in ${filing.draftFolder}`;
68
+ }
69
+ // No Message-ID means there is no key to reconcile against later, so the row
70
+ // would be uncountable. Skip it rather than writing an empty key.
71
+ if (messageId) {
72
+ // Each filing outcome keeps its own ledger value. Recording a thrown
73
+ // filing or a server with no \Sent folder as "not-in-sent" would assert
74
+ // the folder was searched and found empty — the same conflation this task
75
+ // removes from the tool result, relocated into the ledger the sweep reads.
76
+ let copy;
77
+ if (filing === null)
78
+ copy = "check-failed";
79
+ else if (filing.filed)
80
+ copy = "found";
81
+ else if (filing.reason === "no-sent-folder")
82
+ copy = "no-sent-folder";
83
+ else
84
+ copy = "not-in-sent";
85
+ try {
86
+ appendSentLedger({
87
+ at: new Date().toISOString(),
88
+ accountId: params.accountId,
89
+ mailbox: config.email,
90
+ messageId,
91
+ copy,
92
+ ...(filing !== null && filing.filed
93
+ ? { sentFolder: filing.sentFolder, sentUid: filing.sentUid }
94
+ : {}),
95
+ });
96
+ }
97
+ catch {
98
+ // Observability only — never turn a completed send into an error.
99
+ }
100
+ }
101
+ return `Draft UID ${params.uid} sent to ${recipients.join(", ")}${idNote}; ${filedNote}.`;
51
102
  }
52
103
  //# sourceMappingURL=email-draft-send.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-draft-send.js","sourceRoot":"","sources":["../../src/tools/email-draft-send.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-draft-send.js","sourceRoot":"","sources":["../../src/tools/email-draft-send.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"email-reply.d.ts","sourceRoot":"","sources":["../../src/tools/email-reply.ts"],"names":[],"mappings":"AAKA,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA2ErE"}
1
+ {"version":3,"file":"email-reply.d.ts","sourceRoot":"","sources":["../../src/tools/email-reply.ts"],"names":[],"mappings":"AAMA,UAAU,WAAW;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAoFrE"}
@@ -2,6 +2,7 @@ import { loadCredentials } from "../lib/credentials.js";
2
2
  import { sendEmail, formatCopyNote, replySubject } from "../lib/smtp.js";
3
3
  import { fetchEnvelopeByMessageId } from "../lib/imap.js";
4
4
  import { resolveOutboundAttachments, formatAttachNote, accountDirFor } from "../lib/attachment-resolve.js";
5
+ import { verifyAndRecordSentCopy } from "../lib/sent-copy-note.js";
5
6
  export async function emailReply(params) {
6
7
  const result = await loadCredentials(params.accountId, params.mailbox);
7
8
  if ("error" in result)
@@ -66,6 +67,15 @@ export async function emailReply(params) {
66
67
  throw smtpErr;
67
68
  }
68
69
  const replyType = params.replyAll ? "Reply-all" : "Reply";
69
- return `${replyType} sent to ${to}${formatCopyNote(params.cc, params.bcc)}${formatAttachNote(attachments)} (Message-ID: ${sentMessageId})`;
70
+ // SMTP acceptance is not evidence the provider filed a copy. Read \Sent back
71
+ // and state what is actually there, as a fact separate from the send itself
72
+ // (Task 1822).
73
+ const sentCopyNote = await verifyAndRecordSentCopy({
74
+ config,
75
+ password,
76
+ messageId: sentMessageId,
77
+ accountId: params.accountId,
78
+ });
79
+ return `${replyType} sent to ${to}${formatCopyNote(params.cc, params.bcc)}${formatAttachNote(attachments)} (Message-ID: ${sentMessageId}); ${sentCopyNote}.`;
70
80
  }
71
81
  //# sourceMappingURL=email-reply.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-reply.js","sourceRoot":"","sources":["../../src/tools/email-reply.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-reply.js","sourceRoot":"","sources":["../../src/tools/email-reply.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"email-send.d.ts","sourceRoot":"","sources":["../../src/tools/email-send.ts"],"names":[],"mappings":"AAIA,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkCnE"}
1
+ {"version":3,"file":"email-send.d.ts","sourceRoot":"","sources":["../../src/tools/email-send.ts"],"names":[],"mappings":"AAKA,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CnE"}
@@ -1,6 +1,7 @@
1
1
  import { loadCredentials } from "../lib/credentials.js";
2
2
  import { sendEmail, formatCopyNote } from "../lib/smtp.js";
3
3
  import { resolveOutboundAttachments, formatAttachNote, accountDirFor } from "../lib/attachment-resolve.js";
4
+ import { verifyAndRecordSentCopy } from "../lib/sent-copy-note.js";
4
5
  export async function emailSend(params) {
5
6
  const result = await loadCredentials(params.accountId, params.mailbox);
6
7
  if ("error" in result)
@@ -13,8 +14,11 @@ export async function emailSend(params) {
13
14
  // just loaded), not an ambient ACCOUNT_DIR. Falls back to the resolver's env
14
15
  // path when undefined.
15
16
  const attachments = resolveOutboundAttachments(accountDirFor(params.accountId), params.attachments);
17
+ // The catch wraps the SMTP call alone. Once SMTP has accepted, the mail is
18
+ // out, and nothing after this point may surface as a send failure.
19
+ let messageId;
16
20
  try {
17
- const { messageId } = await sendEmail(config, password, {
21
+ ({ messageId } = await sendEmail(config, password, {
18
22
  to: params.to,
19
23
  subject: params.subject,
20
24
  body: params.body,
@@ -22,8 +26,7 @@ export async function emailSend(params) {
22
26
  cc: params.cc,
23
27
  bcc: params.bcc,
24
28
  attachments,
25
- });
26
- return `Email sent to ${params.to}${formatCopyNote(params.cc, params.bcc)}${formatAttachNote(attachments)} (Message-ID: ${messageId})`;
29
+ }));
27
30
  }
28
31
  catch (err) {
29
32
  const msg = err instanceof Error ? err.message : String(err);
@@ -32,5 +35,15 @@ export async function emailSend(params) {
32
35
  }
33
36
  throw err;
34
37
  }
38
+ // SMTP acceptance is not evidence the provider filed a copy. Read \Sent back
39
+ // and state what is actually there, as a fact separate from the send itself
40
+ // (Task 1822).
41
+ const sentCopyNote = await verifyAndRecordSentCopy({
42
+ config,
43
+ password,
44
+ messageId,
45
+ accountId: params.accountId,
46
+ });
47
+ return `Email sent to ${params.to}${formatCopyNote(params.cc, params.bcc)}${formatAttachNote(attachments)} (Message-ID: ${messageId}); ${sentCopyNote}.`;
35
48
  }
36
49
  //# sourceMappingURL=email-send.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-send.js","sourceRoot":"","sources":["../../src/tools/email-send.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAc3G,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAkB;IAChD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACvE,IAAI,OAAO,IAAI,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAErD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;IAEhD,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,6EAA6E;IAC7E,uBAAuB;IACvB,MAAM,WAAW,GAAG,0BAA0B,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAEpG,IAAI,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE;YACtD,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,WAAW;SACZ,CAAC,CAAC;QACH,OAAO,iBAAiB,MAAM,CAAC,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,WAAW,CAAC,iBAAiB,SAAS,GAAG,CAAC;IACzI,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,uBAAuB,MAAM,CAAC,YAAY,sBAAsB,MAAM,CAAC,KAAK,iGAAiG,CACpL,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"email-send.js","sourceRoot":"","sources":["../../src/tools/email-send.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC3G,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAcnE,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAkB;IAChD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACvE,IAAI,OAAO,IAAI,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAErD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC;IAEhD,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,6EAA6E;IAC7E,uBAAuB;IACvB,MAAM,WAAW,GAAG,0BAA0B,CAAC,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAEpG,2EAA2E;IAC3E,mEAAmE;IACnE,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE;YACjD,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,EAAE,MAAM,CAAC,GAAG;YACf,WAAW;SACZ,CAAC,CAAC,CAAC;IACN,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,uBAAuB,MAAM,CAAC,YAAY,sBAAsB,MAAM,CAAC,KAAK,iGAAiG,CACpL,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,6EAA6E;IAC7E,4EAA4E;IAC5E,eAAe;IACf,MAAM,YAAY,GAAG,MAAM,uBAAuB,CAAC;QACjD,MAAM;QACN,QAAQ;QACR,SAAS;QACT,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;IACH,OAAO,iBAAiB,MAAM,CAAC,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,WAAW,CAAC,iBAAiB,SAAS,MAAM,YAAY,GAAG,CAAC;AAC3J,CAAC"}
@@ -103,6 +103,42 @@ Use `email-reply` when responding to a received email. Use `email-send` for new
103
103
 
104
104
  Both `email-send` and `email-reply` accept optional `cc` and `bcc` fields. Each is an array of email addresses (one element or many); every address is validated individually. CC recipients appear in the message header and are visible to all recipients. BCC recipients receive the message but do not appear in any header — the other recipients cannot see them. Omitting both sends exactly as before, with no CC/BCC headers. The success message names the CC recipients so the operator can confirm the copy went out, and reports the BCC count without naming them, preserving the blind semantics.
105
105
 
106
+ ## The Sent copy is verified, not assumed
107
+
108
+ SMTP accepting a message is not evidence that the provider filed a copy anywhere. `email-send`, `email-reply` and `email-draft-send` therefore each read the mailbox's `\Sent` folder back after the send and report what is actually there, as a fact separate from the send itself. The search is by Message-ID, retried briefly because providers do not file the copy at the instant SMTP returns.
109
+
110
+ Four outcomes are reported, and they are deliberately distinct:
111
+
112
+ | Result clause | Means |
113
+ |---|---|
114
+ | `copy confirmed in <folder> (UID n)` | The copy was found by search. A folder and UID are only ever named when they were read back. |
115
+ | `no copy was found in <folder>` | The folder was searched and the message is not in it. |
116
+ | `no copy was filed — this server advertises no \Sent folder` | There is nowhere to file to. Not the same as a missing copy. |
117
+ | `the Sent folder could not be checked (<error>), so the copy is unconfirmed` | The check itself failed. Not evidence either way. |
118
+
119
+ A send whose copy cannot be located is still a successful send, and the result says both things plainly. Collapsing them into one word is what allowed a sent-copy claim to go unchallenged for four days when no copy was being filed at all.
120
+
121
+ Every send also appends one row to `config/email-sent-ledger.jsonl` recording the observed outcome. That ledger is the left-hand side of the hourly sweep below; it is written after the read-back, so it records what happened, not what was intended.
122
+
123
+ ## The hourly sent-copy sweep
124
+
125
+ Per-tool reporting only covers sends the agent was present for. A mailbox that silently stops filing copies emits nothing at all, so nothing surfaces until someone checks by hand.
126
+
127
+ The install heartbeat therefore spawns `email/mcp/dist/scripts/sent-copy-sweep.js` once an hour. Per account, per mailbox, it takes the Message-IDs this plugin sent in the trailing 24 hours and asks `\Sent` whether each one is actually there, then reports:
128
+
129
+ ```
130
+ [email-sent-sweep] op=reconcile account=<id> mailbox=<email> sends=N copies=M shortfall=K status=clean|shortfall|check-failed
131
+ ```
132
+
133
+ It reports on mailboxes with zero sends too, so a silent check is distinguishable from an absent one. A mailbox that could not be read is `check-failed`, never a shortfall — an unreadable mailbox and a mailbox losing copies are different findings.
134
+
135
+ Two properties of that design are load-bearing:
136
+
137
+ - **Presence is asked per Message-ID, never by comparing totals.** `\Sent` holds everything the mailbox owner sends from any client. A bulk count is dominated by messages this plugin never sent, so a mailbox filing none of our sends would still look healthy the moment its owner sends a few from their phone — which is exactly the incident this check exists to catch.
138
+ - **The answer comes from the folder, not from the ledger's own recorded outcome.** Re-reading is what makes the sweep independent evidence rather than a replay of what the tool already said, and it is the only way a copy that was filed and later removed shows up.
139
+
140
+ The sweep also trims the ledger past its 48-hour retention. That rewrite happens here, once an hour in a single process, rather than on the send path where it could lose a concurrent row.
141
+
106
142
  ## Attachments
107
143
 
108
144
  Both `email-send` and `email-reply` accept an optional `attachments` array of absolute file paths. Each path must point to a regular file inside the account directory and be 25 MB or smaller; the path is validated before the message is sent, so an invalid, missing, oversized, or out-of-account path fails the call with a clear error and no email dispatched. Validated files ride the message as real SMTP attachments, and the success message names each attached file.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sent-sweep-throttle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-sweep-throttle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sent-sweep-throttle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,18 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { shouldRunSentSweep } from "../lib/sent-sweep-throttle.js";
3
+ const HOUR = 60 * 60 * 1000;
4
+ describe("shouldRunSentSweep", () => {
5
+ it("runs when there is no prior state", () => {
6
+ expect(shouldRunSentSweep(undefined, 1_000_000, HOUR)).toBe(true);
7
+ });
8
+ it("does not run again inside the interval", () => {
9
+ expect(shouldRunSentSweep({ lastRunMs: 1_000_000 }, 1_000_000 + HOUR / 2, HOUR)).toBe(false);
10
+ });
11
+ it("runs once the interval has elapsed", () => {
12
+ expect(shouldRunSentSweep({ lastRunMs: 1_000_000 }, 1_000_000 + HOUR, HOUR)).toBe(true);
13
+ });
14
+ it("runs when the clock stepped backwards", () => {
15
+ expect(shouldRunSentSweep({ lastRunMs: 5_000_000 }, 1_000_000, HOUR)).toBe(true);
16
+ });
17
+ });
18
+ //# sourceMappingURL=sent-sweep-throttle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-sweep-throttle.test.js","sourceRoot":"","sources":["../../src/__tests__/sent-sweep-throttle.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE5B,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,IAAI,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hourly gate for the email sent-copy sweep inside the 60-second heartbeat.
3
+ *
4
+ * The sweep opens an IMAP connection per mailbox, which is affordable hourly
5
+ * and not per minute. Mirrors the provisioning-heartbeat gate in
6
+ * connector-sync-provision.ts rather than inventing a second pattern.
7
+ */
8
+ export interface SentSweepState {
9
+ lastRunMs: number;
10
+ }
11
+ export declare const SENT_SWEEP_INTERVAL_MS: number;
12
+ export declare function shouldRunSentSweep(prev: SentSweepState | undefined, nowMs: number, intervalMs: number): boolean;
13
+ //# sourceMappingURL=sent-sweep-throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-sweep-throttle.d.ts","sourceRoot":"","sources":["../../src/lib/sent-sweep-throttle.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,sBAAsB,QAAiB,CAAC;AAErD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,cAAc,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAMT"}
@@ -0,0 +1,10 @@
1
+ export const SENT_SWEEP_INTERVAL_MS = 60 * 60 * 1000;
2
+ export function shouldRunSentSweep(prev, nowMs, intervalMs) {
3
+ if (!prev)
4
+ return true;
5
+ const elapsed = nowMs - prev.lastRunMs;
6
+ // A negative elapsed means the clock stepped backwards (an NTP correction on
7
+ // a Pi with no battery-backed RTC). Running is the safe side.
8
+ return elapsed < 0 || elapsed >= intervalMs;
9
+ }
10
+ //# sourceMappingURL=sent-sweep-throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-sweep-throttle.js","sourceRoot":"","sources":["../../src/lib/sent-sweep-throttle.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAErD,MAAM,UAAU,kBAAkB,CAChC,IAAgC,EAChC,KAAa,EACb,UAAkB;IAElB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;IACvC,6EAA6E;IAC7E,8DAA8D;IAC9D,OAAO,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,UAAU,CAAC;AAC9C,CAAC"}