@rubytech/create-maxy-code 0.1.476 → 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.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +673 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +177 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +97 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js +44 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/audit.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.d.ts +56 -0
- package/payload/platform/lib/storage-broker/dist/audit.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/audit.js +34 -0
- package/payload/platform/lib/storage-broker/dist/audit.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/audit.test.ts +70 -1
- package/payload/platform/lib/storage-broker/src/audit.ts +111 -0
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +7 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +72 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts +94 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js +229 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/cpu-triage.js.map +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
- package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +2 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js +92 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/confirm-sent-copy.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +112 -12
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js +54 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/email-send-sent-copy.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +170 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js +86 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-note.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js +164 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-copy-sweep.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js +82 -0
- package/payload/platform/plugins/email/mcp/dist/__tests__/sent-ledger.test.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +95 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js +223 -0
- package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts +18 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js +59 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-note.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts +48 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js +79 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-copy-sweep.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts +44 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js +63 -0
- package/payload/platform/plugins/email/mcp/dist/lib/sent-ledger.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts +2 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.d.ts.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js +55 -0
- package/payload/platform/plugins/email/mcp/dist/scripts/sent-copy-sweep.js.map +1 -0
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +64 -13
- package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +11 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js +16 -3
- package/payload/platform/plugins/email/mcp/dist/tools/email-send.js.map +1 -1
- package/payload/platform/plugins/email/references/email-reference.md +36 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js +18 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js +150 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block-sqlite.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js +248 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-block.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +90 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +105 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts +13 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js +10 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/sent-sweep-throttle.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +53 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +88 -10
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/scripts/cpu-triage-run.sh +117 -0
- package/payload/platform/scripts/cpu-triage.sh +332 -0
- package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts +40 -0
- package/payload/platform/services/claude-session-manager/dist/activity-range.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/activity-range.js +68 -0
- package/payload/platform/services/claude-session-manager/dist/activity-range.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts +40 -8
- package/payload/platform/services/claude-session-manager/dist/activity-rows.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js +74 -7
- package/payload/platform/services/claude-session-manager/dist/activity-rows.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +9 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/server/{chunk-56WJMBQQ.js → chunk-3XLLTG6R.js} +14 -0
- package/payload/server/{chunk-LBCMFD4O.js → chunk-XGNRSM57.js} +129 -37
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-DximwPlS.js → AdminLoginScreens-DR5eLF-a.js} +1 -1
- package/payload/server/public/assets/AdminShell-D9zaUxPd.js +2 -0
- package/payload/server/public/assets/{Checkbox-DM-eHqOS.js → Checkbox-DcbbAIe5.js} +1 -1
- package/payload/server/public/assets/activity-C9P5NjEZ.js +1 -0
- package/payload/server/public/assets/{admin-CAkDnGdk.js → admin-D2KLZnQW.js} +1 -1
- package/payload/server/public/assets/{browser-DXl8hOoi.js → browser-CDWqEiCD.js} +1 -1
- package/payload/server/public/assets/{calendar-DP6hn4-6.js → calendar-BG7fOpbB.js} +1 -1
- package/payload/server/public/assets/chat-RyH-WYQC.js +1 -0
- package/payload/server/public/assets/chevron-left-DmXCr6nB.js +1 -0
- package/payload/server/public/assets/data-BaY_LkLR.js +1 -0
- package/payload/server/public/assets/{graph-CHcYoEJ5.js → graph-DRloNic4.js} +1 -1
- package/payload/server/public/assets/{graph-labels-CZykslZM.js → graph-labels-DD-GqS4v.js} +1 -1
- package/payload/server/public/assets/{maximize-2-31AZEcMS.js → maximize-2-CzDaBugs.js} +1 -1
- package/payload/server/public/assets/{operator-BGGpdsO_.js → operator-BZeeyait.js} +1 -1
- package/payload/server/public/assets/{page-BloC6ygA.js → page-B0obcg3b.js} +1 -1
- package/payload/server/public/assets/{page-DOeiiqbR.js → page-ef-6rRPL.js} +1 -1
- package/payload/server/public/assets/{public-CGgO6IZv.js → public-9D3MMOkC.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-BlAi6iFg.js → rotate-ccw-DXEKUhXP.js} +1 -1
- package/payload/server/public/assets/tasks-BB0L7e5a.js +1 -0
- package/payload/server/public/assets/{time-entry-format-CiUUQdq7.js → time-entry-format-CsYAjplS.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-CVqKuLoa.js → triangle-alert-0lSBIUg-.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-ksyUe_g1.js → useCopyFeedback-jTBQ2h0V.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-BPrBkdTd.js → useSelectionMode-BooArySZ.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-CTXP8oTW.js → useVoiceRecorder-PUT8q_sm.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +6 -6
- package/payload/server/public/chat.html +12 -12
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +11 -11
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +12 -12
- package/payload/server/public/tasks.html +5 -5
- package/payload/server/server.js +325 -77
- package/payload/server/{src-JYPKMWJR.js → src-XQ63FPRE.js} +3 -1
- package/payload/server/public/assets/AdminShell-BehbH-Oa.js +0 -2
- package/payload/server/public/assets/activity-hDwUeQd6.js +0 -1
- package/payload/server/public/assets/chat-DhOfVGYM.js +0 -1
- package/payload/server/public/assets/chevron-left-DYNBoLii.js +0 -1
- package/payload/server/public/assets/data-bq8fTo_s.js +0 -1
- package/payload/server/public/assets/tasks-C1giFRRL.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-K4kYeN6i.css +0 -1
- /package/payload/server/public/assets/{useSubAccountSwitcher-DzgzTRRn.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 @@
|
|
|
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":"
|
|
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,
|
|
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:
|
|
36
|
-
// already went out
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
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
|
-
|
|
44
|
+
filing = await fileDraftToSent(config, password, params.uid, messageId);
|
|
42
45
|
}
|
|
43
|
-
catch {
|
|
44
|
-
|
|
46
|
+
catch (err) {
|
|
47
|
+
filingError = err instanceof Error ? err.message : String(err);
|
|
45
48
|
}
|
|
46
49
|
const idNote = messageId ? ` (Message-ID: ${messageId})` : "";
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,
|
|
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":"
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;
|
|
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":"
|
|
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
|
-
|
|
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;
|
|
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.
|
package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.d.ts.map
ADDED
|
@@ -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
|
package/payload/platform/plugins/scheduling/mcp/dist/__tests__/sent-sweep-throttle.test.js.map
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"booking-block-sqlite.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/booking-block-sqlite.test.ts"],"names":[],"mappings":""}
|