chainlesschain 0.166.0 → 0.166.2
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/src/assets/web-panel/assets/{AIOps-D5_pwQ7M.js → AIOps-BnZ24WRk.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DgoYO32D.js → ActionButton-ivXY7MGb.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BIpoZ4QH.js → Analytics-BQS4OD-k.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BJ3XrCEe.js → AppLayout-wOBuOKTO.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DUFs_izZ.js → Artifacts-BYuC4nw-.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BZdPwwfL.js → Audit-BIeVfBt6.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DK9Fn-8B.js → BackgroundAgents-hIuyhmJ7.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Bd6bOhV_.js → Backup-B6ohdgPQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CRnWlXrd.js → BaseInput-BzgYCw6h.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D1G_yEl6.js → Chat-CwahvZU1.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDAlEtm-.js → ChatBubbleRenderer-B-1IZ_k8.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CP35SH7R.js → Checkbox-26EDP2x0.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DugeL4pj.js → Codegen-DYLw1Zvd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DPuJsvKg.js → Col-DH_zAvD-.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CZqNjNNp.js → Community-DURrTXJu.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-eeyIPEAb.js → Compact-CQyBqN0p.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-MGzTw87k.js → Compliance-a0DfgKHD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D3goVIA5.js → Cowork-C6fY_5Ke.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-9HA8hLLG.js → Cron-DOwmpbTe.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DCSOX3l6.js → Crosschain-DEPOIEUs.js} +1 -1
- package/src/assets/web-panel/assets/{DID-ji3lN5Bc.js → DID-DaKc-CEV.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DlZvF1E5.js → Dashboard-C-0KLXq5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-2FyZcow9.js → Dropdown-CnsDukzr.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-qH2bSpSY.js → EmailListRenderer-Bi_ACD4N.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bfx47wZj.js → FamilyGuardDashboard-BZASpnx3.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DhGcZbIv.js → Federation-43oTtzyp.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BMgi9_YQ.js → FormItemContext-oyVadnkZ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DugMac-4.js → GenericCardRenderer-Cwvfd6TZ.js} +1 -1
- package/src/assets/web-panel/assets/{Git-D943BpH-.js → Git-Cgi2wvU0.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DZ66a8Aa.js → Governance-l8TWDKZ0.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B00Mp2tN.js → Inference-Dc8j6TaO.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BdwXXO2Q.js → KnowledgeGraph-DgcO-Fwz.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-_MVXyYWc.js → Logs-DjiZpiwa.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-hOTeNyi7.js → MarkdownRenderer-B5MFCi0N.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CxeHRsnO.js → Marketplace-BeDn1hFc.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BOieTxZ4.js → McpTools-PGdVfWK6.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BQD26CM3.js → Memory-e76lY3a1.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CPJ8ZlvR.js → MobileBridge-DKlQqot_.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D7qRVu0y.js → MobileProjects-C4g6WOEr.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BOyndGuk.js → Mtc-1j3NpRQq.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CXQ_8ZoR.js → MtcAudit-DgwiVd1C.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-32Oh9KbH.js → Multisig-Dzg5lZZY.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CFAd00CX.js → NLProgramming-D7GYhst-.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BBCY_qJA.js → Notes-Blcfvaic.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-uaQ0PdmW.js → NotificationSettings-Dc4Y6vfr.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-dgM87XIn.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CmFn7Oeo.js → Organization-DBXq024L.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-R_avat3a.js → Overflow-CFPbdx1Y.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BIhLFmkY.js → P2P-w7z61NR5.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DTJjk-_i.js → PdhVaultBrowser-B7eGGZOa.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CkzYv8Yb.js → Permissions-Df5Cl50D.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub--0QvN2Nz.js → PersonalDataHub-C-We7RF6.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Bfw5gQCH.js → Pipeline-B6fHPnKy.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DicjKfax.js → Privacy-B85BdiIc.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DUl_ykBp.js → ProjectInit-CcOyazSe.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CPYDvN8a.js → ProjectSettings-BE53KfuU.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DnZ9_fAr.js → Projects-DOvMOoe_.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BX1jP31w.js → Providers-5l7sT9lq.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-COab5qUl.js → QrScannerModal-DE37RXy4.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CdKQ_JZ0.js → QuickAsk-BGmGCo7j.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BPFHg8pK.js → Recommend-BFdx8Nrm.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-CqrfTJvO.js → RemoteSession-C1w-j_hB.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DM-l8Pb3.js → Reputation-BOvn-10y.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CKGBTeCw.js → Row-db-a1YO9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Wb0YHIkc.js → RssFeed-DeS43ZGM.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DiBjxKv-.js → Search-BaQYNfeb.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BAVEmbWP.js → Security-yt1ESHDg.js} +4 -4
- package/src/assets/web-panel/assets/{Services-A_a3TIRn.js → Services-BcNqPUsS.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CloHJ-I7.js → Skeleton-X3ceUQWf.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CkvV5hKa.js → Skills-DmYeax6C.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BYozxxsV.js → Sla-C_fdfbuJ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CWianiZF.js → SpeechSettings-CDqohXXQ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BR4G75do.js → SyncSettings-B25maYEY.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D6gyUVrM.js → Tasks-3ILwauYw.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DbfYIRzT.js → Templates-DUH0iuc4.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BHutvukT.js → Tenant-DrvhHf9T.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYEiy2wJ.js → Terminal-C2lJVYHA.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D9s1AxfE.js → TimelineRenderer-C1Hf_jYn.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-ij9vxnoy.js → Tokens-DniS1NpS.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dq_DCVeQ.js → Trigger-4jrcb8oA.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bs6wdfoH.js → Trust-DiBte55w.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CLazyr5t.js → UkeySign-CZRtc4VB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-viWotIHA.js → VideoEditing-BtYGJDmg.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DlZ-A78d.js → Wallet-D1nrTm7H.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DSmOND3Y.js → WebAuthn-DI91acpx.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BempLOJi.js → WorkflowEditor-BtQjI50d.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CyJzf4JI.js → chat-I4A-f4FC.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C62q7WrL.js → colors-QZkDkFVt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-7dYM1KW3.js → compact-item--FtCdzpr.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CYTy7z2Q.js → createContext-CaIKIKxP.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-D7qVrkrO.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DHoclUqm.js → hasIn-DLYUII6-.js} +1 -1
- package/src/assets/web-panel/assets/{index-CW86F2Zw.js → index-0DYBQ-6e.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2efftJb.js → index-21I79XSf.js} +1 -1
- package/src/assets/web-panel/assets/{index-4gWv-Ubg.js → index-3rVtgqa2.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXtrdrub.js → index-B65QxJaG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMxvWauP.js → index-BGWSmE3G.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dyq9tK5u.js → index-BLGJmgmx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYKwGrAn.js → index-BcgtvJbC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMRmxFU_.js → index-BebTT7iR.js} +1 -1
- package/src/assets/web-panel/assets/{index-XrsAHHQN.js → index-BkJgWW_L.js} +1 -1
- package/src/assets/web-panel/assets/{index-2C8Xx2UO.js → index-Bs0qPPFb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnEZ4ZHj.js → index-BvdwNnHC.js} +3 -3
- package/src/assets/web-panel/assets/{index-DkczsbiP.js → index-BxPoPofq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvEtHpLc.js → index-C3PnIEOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-DK84lAS0.js → index-CBtII-nl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKdv8cju.js → index-CL4c5D6G.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjeViaU-.js → index-CQ7OBHka.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhJjifZe.js → index-CXaeZCRP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BghdJ87W.js → index-CgMm85BY.js} +1 -1
- package/src/assets/web-panel/assets/{index-lHfNxiB5.js → index-Ch0IFpGy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJzZaPlb.js → index-CisYjy88.js} +1 -1
- package/src/assets/web-panel/assets/{index-DghnbMlY.js → index-D2xETCkj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5YWYlW9.js → index-D4CXFU-w.js} +1 -1
- package/src/assets/web-panel/assets/index-D9Y_ZwZn.js +1 -0
- package/src/assets/web-panel/assets/{index-LK1MZoBj.js → index-DA_TPdzX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOFAwpUK.js → index-DAuf8r5o.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA-_807C.js → index-DGLHSCHM.js} +1 -1
- package/src/assets/web-panel/assets/{index-9uR7rc9a.js → index-DJrd_CR3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVTnruDt.js → index-DK2r6Bv7.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZXYp76IB.js → index-DVitI3UQ.js} +1 -1
- package/src/assets/web-panel/assets/index-D_cPes4G.js +1 -0
- package/src/assets/web-panel/assets/{index-Bzz0aJ86.js → index-Deg2S9ZJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHgSRimK.js → index-Du2wUavy.js} +1 -1
- package/src/assets/web-panel/assets/{index-94ktimZG.js → index-OQPIS_am.js} +1 -1
- package/src/assets/web-panel/assets/{index-3pI7AfmD.js → index-Ooo_5jHo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNLD3nZY.js → index-Ygi2rYL1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZRBvN6i.js → index-eTqL91aV.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6BC4mmz.js → index-hfiZ-i2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAYanuSk.js → index-vfiDxdN2.js} +1 -1
- package/src/assets/web-panel/assets/{index-NuNCstRO.js → index-yayaayEA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CwCl5sFQ.js → initDefaultProps-DDkTvw8k.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BFJFy70N.js → motion-fkr96tN5.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByrtVGq8.js → move-CGBV0SV2.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BFiH1mT3.js → mtc-parser-DAsCliFt.js} +1 -1
- package/src/assets/web-panel/assets/{omit-bdvgpivj.js → omit-BM8PMmKN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BlfuuE3s.js → pickAttrs-C6SdWoTK.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-t2SC3DMy.js → placementArrow-BG0EVu-d.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CoEXv2Li.js → responsiveObserve-L10IvfYC.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BfXCf0Fu.js → slide-TddIkbrn.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DNsdyOuS.js → statusUtils-FlveF2Yn.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-9aHukmoG.js → styleChecker-BrXRyebg.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-dTkPxzHN.js → useFlexGapSupport-B7_oiDXZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DINwfItP.js → useFs-CQYeRYWb.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Bj_RriBV.js → usePersonalDataHub-DspIkhVi.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BzRAZrTc.js → vnode-BCwmwMAW.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CamPjkeK.js → zoom-B0cEGluV.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/team.js +74 -2
- package/src/data/changelog.json +26 -0
- package/src/generated/cc-agent-protocol.schema.json +49 -0
- package/src/lib/agent-team/team-mailbox.js +508 -2
- package/src/lib/agent-team/team-message-bridge.js +644 -0
- package/src/lib/agent-team/team-message-tools.js +198 -0
- package/src/lib/agent-team/team-runner.js +57 -3
- package/src/lib/graph-kernel/compiler.js +548 -16
- package/src/lib/graph-kernel/runtime.js +1158 -30
- package/src/lib/graph-kernel/trace-reducer.js +101 -7
- package/src/lib/subagent-inheritance.js +5 -3
- package/src/runtime/agent-core.js +38 -0
- package/src/runtime/headless-runner.js +36 -3
- package/src/assets/web-panel/assets/OrderTableRenderer-kpBxJ-SG.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BdTg_AMF.js +0 -1
- package/src/assets/web-panel/assets/index-B6WvFV4N.js +0 -1
- package/src/assets/web-panel/assets/index-BKgfptzA.js +0 -1
|
@@ -22,19 +22,56 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { Buffer } from "node:buffer";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
25
26
|
|
|
26
27
|
export const TEAM_MAILBOX_ERROR_CODES = Object.freeze({
|
|
27
28
|
MESSAGE_INVALID: "TEAM_MAILBOX_MESSAGE_INVALID",
|
|
28
29
|
MESSAGE_TOO_LARGE: "TEAM_MAILBOX_MESSAGE_TOO_LARGE",
|
|
29
30
|
CAPACITY_EXCEEDED: "TEAM_MAILBOX_CAPACITY_EXCEEDED",
|
|
31
|
+
ACK_INVALID: "TEAM_MAILBOX_ACK_INVALID",
|
|
32
|
+
ACK_CONFLICT: "TEAM_MAILBOX_ACK_CONFLICT",
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
export const DEFAULT_TEAM_MAILBOX_LIMITS = Object.freeze({
|
|
33
36
|
maxMessageBytes: 64 * 1024,
|
|
34
37
|
maxMessages: 1000,
|
|
35
38
|
maxTotalBytes: 4 * 1024 * 1024,
|
|
39
|
+
maxReceiptHistory: 5000,
|
|
40
|
+
maxIdempotencyHistory: 256,
|
|
36
41
|
});
|
|
37
42
|
|
|
43
|
+
const RECEIPT_STATUSES = new Set([
|
|
44
|
+
"delivered",
|
|
45
|
+
"read",
|
|
46
|
+
"processed",
|
|
47
|
+
"dead_letter",
|
|
48
|
+
]);
|
|
49
|
+
const ACK_STATUSES = new Set(["read", "processed", "dead_letter"]);
|
|
50
|
+
|
|
51
|
+
function digestValue(value) {
|
|
52
|
+
return `sha256:${createHash("sha256")
|
|
53
|
+
.update(JSON.stringify(value), "utf8")
|
|
54
|
+
.digest("hex")}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function boundedString(value, label, maximum = 128) {
|
|
58
|
+
const text = String(value || "").trim();
|
|
59
|
+
if (
|
|
60
|
+
!text ||
|
|
61
|
+
text.length > maximum ||
|
|
62
|
+
Array.from(text).some((character) => {
|
|
63
|
+
const code = character.charCodeAt(0);
|
|
64
|
+
return code <= 31 || code === 127;
|
|
65
|
+
})
|
|
66
|
+
) {
|
|
67
|
+
throw createMailboxError(
|
|
68
|
+
TEAM_MAILBOX_ERROR_CODES.MESSAGE_INVALID,
|
|
69
|
+
`${label} must be a non-empty bounded string without control characters`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
|
|
38
75
|
function positiveInteger(value, fallback) {
|
|
39
76
|
const number = Number(value);
|
|
40
77
|
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
@@ -64,6 +101,8 @@ export class TeamMailbox {
|
|
|
64
101
|
maxMessageBytes = DEFAULT_TEAM_MAILBOX_LIMITS.maxMessageBytes,
|
|
65
102
|
maxMessages = DEFAULT_TEAM_MAILBOX_LIMITS.maxMessages,
|
|
66
103
|
maxTotalBytes = DEFAULT_TEAM_MAILBOX_LIMITS.maxTotalBytes,
|
|
104
|
+
maxReceiptHistory = DEFAULT_TEAM_MAILBOX_LIMITS.maxReceiptHistory,
|
|
105
|
+
maxIdempotencyHistory = DEFAULT_TEAM_MAILBOX_LIMITS.maxIdempotencyHistory,
|
|
67
106
|
recipients = [],
|
|
68
107
|
} = {}) {
|
|
69
108
|
this._now = typeof now === "function" ? now : () => now;
|
|
@@ -80,6 +119,14 @@ export class TeamMailbox {
|
|
|
80
119
|
maxTotalBytes,
|
|
81
120
|
DEFAULT_TEAM_MAILBOX_LIMITS.maxTotalBytes,
|
|
82
121
|
),
|
|
122
|
+
maxReceiptHistory: positiveInteger(
|
|
123
|
+
maxReceiptHistory,
|
|
124
|
+
DEFAULT_TEAM_MAILBOX_LIMITS.maxReceiptHistory,
|
|
125
|
+
),
|
|
126
|
+
maxIdempotencyHistory: positiveInteger(
|
|
127
|
+
maxIdempotencyHistory,
|
|
128
|
+
DEFAULT_TEAM_MAILBOX_LIMITS.maxIdempotencyHistory,
|
|
129
|
+
),
|
|
83
130
|
};
|
|
84
131
|
this._log = []; // ordered [{ id, from, to, subject, body, ts }]
|
|
85
132
|
this._entryBytes = new Map(); // id → serialized UTF-8 bytes
|
|
@@ -87,6 +134,8 @@ export class TeamMailbox {
|
|
|
87
134
|
this._seq = 0; // monotonic message id source
|
|
88
135
|
this._delivered = new Map(); // recipient → highest message id already drained
|
|
89
136
|
this._recipients = new Set(); // authoritative compaction audience
|
|
137
|
+
this._receipts = new Map(); // recipient\0message id → delivery/processing receipt
|
|
138
|
+
this._idempotency = new Map(); // sender\0key → admitted message digest + id
|
|
90
139
|
this._counters = {
|
|
91
140
|
acceptedMessages: 0,
|
|
92
141
|
acceptedBytes: 0,
|
|
@@ -95,6 +144,10 @@ export class TeamMailbox {
|
|
|
95
144
|
compactionRuns: 0,
|
|
96
145
|
compactedMessages: 0,
|
|
97
146
|
compactedBytes: 0,
|
|
147
|
+
deliveryAttempts: 0,
|
|
148
|
+
processedMessages: 0,
|
|
149
|
+
deadLetteredMessages: 0,
|
|
150
|
+
idempotentReplays: 0,
|
|
98
151
|
};
|
|
99
152
|
this.registerRecipients(recipients);
|
|
100
153
|
}
|
|
@@ -131,7 +184,17 @@ export class TeamMailbox {
|
|
|
131
184
|
* Post a message. `to` is a teammate id or "*" (broadcast). Returns the stored
|
|
132
185
|
* message (with its assigned id).
|
|
133
186
|
*/
|
|
134
|
-
send({
|
|
187
|
+
send({
|
|
188
|
+
from = null,
|
|
189
|
+
to,
|
|
190
|
+
subject = null,
|
|
191
|
+
body = null,
|
|
192
|
+
mode = "send",
|
|
193
|
+
idempotencyKey = null,
|
|
194
|
+
causationId = null,
|
|
195
|
+
correlationId = null,
|
|
196
|
+
senderAttempt = null,
|
|
197
|
+
} = {}) {
|
|
135
198
|
if (!to || typeof to !== "string") {
|
|
136
199
|
this._recordRejection(0);
|
|
137
200
|
throw createMailboxError(
|
|
@@ -139,6 +202,52 @@ export class TeamMailbox {
|
|
|
139
202
|
"TeamMailbox.send: `to` recipient (or '*') is required",
|
|
140
203
|
);
|
|
141
204
|
}
|
|
205
|
+
const normalizedMode = mode === "followup" ? "followup" : "send";
|
|
206
|
+
const normalizedIdempotencyKey =
|
|
207
|
+
idempotencyKey == null
|
|
208
|
+
? null
|
|
209
|
+
: boundedString(idempotencyKey, "idempotencyKey");
|
|
210
|
+
const senderTaskKey =
|
|
211
|
+
senderAttempt && typeof senderAttempt.taskKey === "string"
|
|
212
|
+
? senderAttempt.taskKey
|
|
213
|
+
: "";
|
|
214
|
+
const dedupKey = normalizedIdempotencyKey
|
|
215
|
+
? `${String(from || "")}\0${senderTaskKey}\0${normalizedIdempotencyKey}`
|
|
216
|
+
: null;
|
|
217
|
+
let payloadDigest;
|
|
218
|
+
try {
|
|
219
|
+
payloadDigest = digestValue({
|
|
220
|
+
from,
|
|
221
|
+
to,
|
|
222
|
+
subject,
|
|
223
|
+
body,
|
|
224
|
+
mode: normalizedMode,
|
|
225
|
+
causationId,
|
|
226
|
+
correlationId,
|
|
227
|
+
});
|
|
228
|
+
} catch (cause) {
|
|
229
|
+
this._recordRejection(0);
|
|
230
|
+
throw createMailboxError(
|
|
231
|
+
TEAM_MAILBOX_ERROR_CODES.MESSAGE_INVALID,
|
|
232
|
+
"TeamMailbox.send: message must be JSON-serializable",
|
|
233
|
+
{ cause },
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (dedupKey && this._idempotency.has(dedupKey)) {
|
|
237
|
+
const prior = this._idempotency.get(dedupKey);
|
|
238
|
+
if (prior.payloadDigest !== payloadDigest) {
|
|
239
|
+
this._recordRejection(0);
|
|
240
|
+
throw createMailboxError(
|
|
241
|
+
TEAM_MAILBOX_ERROR_CODES.MESSAGE_INVALID,
|
|
242
|
+
"TeamMailbox.send: idempotency key was reused with different content",
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
this._counters.idempotentReplays += 1;
|
|
246
|
+
const retained = this._log.find((entry) => entry.id === prior.messageId);
|
|
247
|
+
return retained
|
|
248
|
+
? this._cloneMessage(retained)
|
|
249
|
+
: { ...this._cloneMessage(prior.message), idempotentReplay: true };
|
|
250
|
+
}
|
|
142
251
|
let msg = {
|
|
143
252
|
id: this._seq + 1,
|
|
144
253
|
from,
|
|
@@ -146,6 +255,14 @@ export class TeamMailbox {
|
|
|
146
255
|
subject,
|
|
147
256
|
body,
|
|
148
257
|
ts: this._now(),
|
|
258
|
+
...(normalizedMode === "followup" ? { mode: normalizedMode } : {}),
|
|
259
|
+
...(normalizedIdempotencyKey
|
|
260
|
+
? { idempotencyKey: normalizedIdempotencyKey }
|
|
261
|
+
: {}),
|
|
262
|
+
...(causationId != null ? { causationId } : {}),
|
|
263
|
+
...(correlationId != null ? { correlationId } : {}),
|
|
264
|
+
...(senderAttempt != null ? { senderAttempt } : {}),
|
|
265
|
+
payloadDigest,
|
|
149
266
|
};
|
|
150
267
|
let messageBytes;
|
|
151
268
|
try {
|
|
@@ -203,9 +320,60 @@ export class TeamMailbox {
|
|
|
203
320
|
this._totalBytes += messageBytes;
|
|
204
321
|
this._counters.acceptedMessages += 1;
|
|
205
322
|
this._counters.acceptedBytes += messageBytes;
|
|
323
|
+
if (dedupKey) {
|
|
324
|
+
this._idempotency.set(dedupKey, {
|
|
325
|
+
messageId: msg.id,
|
|
326
|
+
payloadDigest,
|
|
327
|
+
message: this._cloneMessage(msg),
|
|
328
|
+
});
|
|
329
|
+
this._trimIdempotencyHistory();
|
|
330
|
+
}
|
|
206
331
|
return this._cloneMessage(msg);
|
|
207
332
|
}
|
|
208
333
|
|
|
334
|
+
_trimIdempotencyHistory() {
|
|
335
|
+
while (this._idempotency.size > this._limits.maxIdempotencyHistory) {
|
|
336
|
+
const oldest = this._idempotency.keys().next().value;
|
|
337
|
+
this._idempotency.delete(oldest);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_receiptKey(recipient, messageId) {
|
|
342
|
+
return `${recipient}\0${messageId}`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
_trimReceiptHistory(targetSize = this._limits.maxReceiptHistory) {
|
|
346
|
+
if (this._receipts.size <= targetSize) return;
|
|
347
|
+
const liveIds = new Set(this._log.map((message) => message.id));
|
|
348
|
+
for (const [key, receipt] of this._receipts) {
|
|
349
|
+
if (this._receipts.size <= targetSize) break;
|
|
350
|
+
const cursor = this._delivered.get(receipt.recipient) || 0;
|
|
351
|
+
if (!liveIds.has(receipt.messageId) || receipt.messageId <= cursor) {
|
|
352
|
+
this._receipts.delete(key);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
_ensureReceiptCapacity(additionalReceipts) {
|
|
358
|
+
this._trimReceiptHistory(
|
|
359
|
+
Math.max(0, this._limits.maxReceiptHistory - additionalReceipts),
|
|
360
|
+
);
|
|
361
|
+
if (
|
|
362
|
+
this._receipts.size + additionalReceipts >
|
|
363
|
+
this._limits.maxReceiptHistory
|
|
364
|
+
) {
|
|
365
|
+
throw createMailboxError(
|
|
366
|
+
TEAM_MAILBOX_ERROR_CODES.CAPACITY_EXCEEDED,
|
|
367
|
+
"TeamMailbox: receipt history capacity exceeded; unsettled receipts were preserved",
|
|
368
|
+
{
|
|
369
|
+
currentReceipts: this._receipts.size,
|
|
370
|
+
additionalReceipts,
|
|
371
|
+
maxReceiptHistory: this._limits.maxReceiptHistory,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
209
377
|
_serializedBytes(msg) {
|
|
210
378
|
return Buffer.byteLength(JSON.stringify(msg), "utf8");
|
|
211
379
|
}
|
|
@@ -285,6 +453,265 @@ export class TeamMailbox {
|
|
|
285
453
|
.map((m) => this._cloneMessage(m));
|
|
286
454
|
}
|
|
287
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Deliver an at-least-once batch without advancing the processed cursor.
|
|
458
|
+
* Repeated calls return the same unacknowledged messages with a monotonically
|
|
459
|
+
* increasing delivery count. `acknowledge()` is the only real-time path that
|
|
460
|
+
* retires them.
|
|
461
|
+
*/
|
|
462
|
+
receive(recipient, { limit = 100, markRead = false } = {}) {
|
|
463
|
+
const boundedLimit = Math.max(
|
|
464
|
+
1,
|
|
465
|
+
Math.min(100, positiveInteger(limit, 100)),
|
|
466
|
+
);
|
|
467
|
+
const now = this._now();
|
|
468
|
+
const messages = this.peek(recipient)
|
|
469
|
+
.filter((message) => {
|
|
470
|
+
const receipt = this._receipts.get(
|
|
471
|
+
this._receiptKey(recipient, message.id),
|
|
472
|
+
);
|
|
473
|
+
return (
|
|
474
|
+
!receipt || !["processed", "dead_letter"].includes(receipt.status)
|
|
475
|
+
);
|
|
476
|
+
})
|
|
477
|
+
.slice(0, boundedLimit);
|
|
478
|
+
const newReceiptCount = messages.reduce(
|
|
479
|
+
(count, message) =>
|
|
480
|
+
count +
|
|
481
|
+
(this._receipts.has(this._receiptKey(recipient, message.id)) ? 0 : 1),
|
|
482
|
+
0,
|
|
483
|
+
);
|
|
484
|
+
this._ensureReceiptCapacity(newReceiptCount);
|
|
485
|
+
return messages.map((message) => {
|
|
486
|
+
const key = this._receiptKey(recipient, message.id);
|
|
487
|
+
const existing = this._receipts.get(key);
|
|
488
|
+
const receipt = existing || {
|
|
489
|
+
recipient,
|
|
490
|
+
messageId: message.id,
|
|
491
|
+
status: "delivered",
|
|
492
|
+
deliveryCount: 0,
|
|
493
|
+
deliveredAt: null,
|
|
494
|
+
readAt: null,
|
|
495
|
+
processedAt: null,
|
|
496
|
+
deadLetteredAt: null,
|
|
497
|
+
consumerKey: null,
|
|
498
|
+
reason: null,
|
|
499
|
+
recipientAttempt: null,
|
|
500
|
+
};
|
|
501
|
+
if (!["processed", "dead_letter"].includes(receipt.status)) {
|
|
502
|
+
receipt.deliveryCount += 1;
|
|
503
|
+
receipt.deliveredAt ||= now;
|
|
504
|
+
if (markRead) {
|
|
505
|
+
receipt.status = "read";
|
|
506
|
+
receipt.readAt ||= now;
|
|
507
|
+
} else if (receipt.status !== "read") {
|
|
508
|
+
receipt.status = "delivered";
|
|
509
|
+
}
|
|
510
|
+
this._receipts.set(key, receipt);
|
|
511
|
+
this._counters.deliveryAttempts += 1;
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
...message,
|
|
515
|
+
delivery: this._cloneMessage(receipt),
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Persist read/processed/dead-letter receipts for explicit message ids.
|
|
522
|
+
* Processing is idempotent for the same consumer key and conflicts closed
|
|
523
|
+
* when another consumer key tries to claim the same message.
|
|
524
|
+
*/
|
|
525
|
+
acknowledge(
|
|
526
|
+
recipient,
|
|
527
|
+
{
|
|
528
|
+
messageIds = [],
|
|
529
|
+
consumerKey,
|
|
530
|
+
status = "processed",
|
|
531
|
+
reason = null,
|
|
532
|
+
recipientAttempt = null,
|
|
533
|
+
} = {},
|
|
534
|
+
) {
|
|
535
|
+
if (
|
|
536
|
+
!Array.isArray(messageIds) ||
|
|
537
|
+
messageIds.length === 0 ||
|
|
538
|
+
messageIds.length > 100 ||
|
|
539
|
+
messageIds.some((id) => !Number.isSafeInteger(id) || id <= 0)
|
|
540
|
+
) {
|
|
541
|
+
throw createMailboxError(
|
|
542
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
543
|
+
"TeamMailbox.acknowledge: messageIds must contain 1-100 positive integer ids",
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
const uniqueIds = [...new Set(messageIds)];
|
|
547
|
+
if (uniqueIds.length !== messageIds.length) {
|
|
548
|
+
throw createMailboxError(
|
|
549
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
550
|
+
"TeamMailbox.acknowledge: duplicate message ids are not allowed",
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
if (!ACK_STATUSES.has(status)) {
|
|
554
|
+
throw createMailboxError(
|
|
555
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
556
|
+
"TeamMailbox.acknowledge: status is invalid",
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const normalizedConsumerKey = boundedString(
|
|
560
|
+
consumerKey,
|
|
561
|
+
"consumerKey",
|
|
562
|
+
256,
|
|
563
|
+
);
|
|
564
|
+
let normalizedRecipientAttempt = null;
|
|
565
|
+
if (recipientAttempt != null) {
|
|
566
|
+
try {
|
|
567
|
+
normalizedRecipientAttempt = this._cloneMessage(recipientAttempt);
|
|
568
|
+
} catch (cause) {
|
|
569
|
+
throw createMailboxError(
|
|
570
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
571
|
+
"TeamMailbox.acknowledge: recipientAttempt must be JSON-serializable",
|
|
572
|
+
{ cause },
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const normalizedReason = String(reason || "poison_message").slice(0, 1024);
|
|
577
|
+
const byId = new Map(this._log.map((message) => [message.id, message]));
|
|
578
|
+
const plans = uniqueIds.map((id) => {
|
|
579
|
+
const message = byId.get(id);
|
|
580
|
+
const key = this._receiptKey(recipient, id);
|
|
581
|
+
const existing = this._receipts.get(key);
|
|
582
|
+
if (message && !existing && id <= (this._delivered.get(recipient) || 0)) {
|
|
583
|
+
throw createMailboxError(
|
|
584
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
585
|
+
`TeamMailbox.acknowledge: message ${id} is outside the retained receipt window`,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
if (
|
|
589
|
+
!message &&
|
|
590
|
+
existing &&
|
|
591
|
+
["processed", "dead_letter"].includes(existing.status)
|
|
592
|
+
) {
|
|
593
|
+
if (existing.consumerKey !== normalizedConsumerKey) {
|
|
594
|
+
throw createMailboxError(
|
|
595
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_CONFLICT,
|
|
596
|
+
`TeamMailbox.acknowledge: message ${id} was settled by another consumer`,
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
if (existing.status !== status) {
|
|
600
|
+
throw createMailboxError(
|
|
601
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_CONFLICT,
|
|
602
|
+
`TeamMailbox.acknowledge: message ${id} already has terminal status ${existing.status}`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return { id, key, message: null, existing, replay: true };
|
|
606
|
+
}
|
|
607
|
+
if (
|
|
608
|
+
!message ||
|
|
609
|
+
message.from === recipient ||
|
|
610
|
+
!this._isFor(message, recipient)
|
|
611
|
+
) {
|
|
612
|
+
throw createMailboxError(
|
|
613
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_INVALID,
|
|
614
|
+
`TeamMailbox.acknowledge: message ${id} is not addressed to ${recipient}`,
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
if (
|
|
618
|
+
existing &&
|
|
619
|
+
["processed", "dead_letter"].includes(existing.status) &&
|
|
620
|
+
existing.consumerKey !== normalizedConsumerKey
|
|
621
|
+
) {
|
|
622
|
+
throw createMailboxError(
|
|
623
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_CONFLICT,
|
|
624
|
+
`TeamMailbox.acknowledge: message ${id} was settled by another consumer`,
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
if (
|
|
628
|
+
existing &&
|
|
629
|
+
["processed", "dead_letter"].includes(existing.status) &&
|
|
630
|
+
existing.status !== status
|
|
631
|
+
) {
|
|
632
|
+
throw createMailboxError(
|
|
633
|
+
TEAM_MAILBOX_ERROR_CODES.ACK_CONFLICT,
|
|
634
|
+
`TeamMailbox.acknowledge: message ${id} already has terminal status ${existing.status}`,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
id,
|
|
639
|
+
key,
|
|
640
|
+
message,
|
|
641
|
+
existing,
|
|
642
|
+
replay:
|
|
643
|
+
existing != null &&
|
|
644
|
+
["processed", "dead_letter"].includes(existing.status),
|
|
645
|
+
};
|
|
646
|
+
});
|
|
647
|
+
this._ensureReceiptCapacity(
|
|
648
|
+
plans.reduce((count, plan) => count + (plan.existing ? 0 : 1), 0),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const now = this._now();
|
|
652
|
+
const receipts = [];
|
|
653
|
+
for (const plan of plans) {
|
|
654
|
+
if (plan.replay) {
|
|
655
|
+
receipts.push(this._cloneMessage(plan.existing));
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const receipt = plan.existing || {
|
|
659
|
+
recipient,
|
|
660
|
+
messageId: plan.id,
|
|
661
|
+
status: "delivered",
|
|
662
|
+
deliveryCount: 1,
|
|
663
|
+
deliveredAt: now,
|
|
664
|
+
readAt: null,
|
|
665
|
+
processedAt: null,
|
|
666
|
+
deadLetteredAt: null,
|
|
667
|
+
consumerKey: null,
|
|
668
|
+
reason: null,
|
|
669
|
+
recipientAttempt: null,
|
|
670
|
+
};
|
|
671
|
+
const wasTerminal = ["processed", "dead_letter"].includes(receipt.status);
|
|
672
|
+
receipt.status = status;
|
|
673
|
+
receipt.consumerKey = normalizedConsumerKey;
|
|
674
|
+
receipt.recipientAttempt = normalizedRecipientAttempt;
|
|
675
|
+
if (status === "read") receipt.readAt ||= now;
|
|
676
|
+
if (status === "processed") {
|
|
677
|
+
receipt.readAt ||= now;
|
|
678
|
+
receipt.processedAt ||= now;
|
|
679
|
+
if (!wasTerminal) this._counters.processedMessages += 1;
|
|
680
|
+
}
|
|
681
|
+
if (status === "dead_letter") {
|
|
682
|
+
receipt.deadLetteredAt ||= now;
|
|
683
|
+
receipt.reason = normalizedReason;
|
|
684
|
+
if (!wasTerminal) this._counters.deadLetteredMessages += 1;
|
|
685
|
+
}
|
|
686
|
+
this._receipts.set(plan.key, receipt);
|
|
687
|
+
receipts.push(this._cloneMessage(receipt));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Advance only across the contiguous addressed prefix that has a terminal
|
|
691
|
+
// receipt. Out-of-order ACKs remain durable but cannot skip an earlier
|
|
692
|
+
// unprocessed message.
|
|
693
|
+
let cursor = this._delivered.get(recipient) || 0;
|
|
694
|
+
for (const message of this._log) {
|
|
695
|
+
if (
|
|
696
|
+
message.id <= cursor ||
|
|
697
|
+
message.from === recipient ||
|
|
698
|
+
!this._isFor(message, recipient)
|
|
699
|
+
) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const receipt = this._receipts.get(
|
|
703
|
+
this._receiptKey(recipient, message.id),
|
|
704
|
+
);
|
|
705
|
+
if (!receipt || !["processed", "dead_letter"].includes(receipt.status)) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
cursor = message.id;
|
|
709
|
+
}
|
|
710
|
+
this._delivered.set(recipient, cursor);
|
|
711
|
+
this._trimReceiptHistory();
|
|
712
|
+
return { receipts, cursor };
|
|
713
|
+
}
|
|
714
|
+
|
|
288
715
|
/**
|
|
289
716
|
* Return a recipient's undelivered messages and advance its delivery cursor so
|
|
290
717
|
* they are not returned again. A teammate never receives its own broadcast.
|
|
@@ -351,12 +778,14 @@ export class TeamMailbox {
|
|
|
351
778
|
registeredRecipients: this._recipients.size,
|
|
352
779
|
pressure: this.pressure(),
|
|
353
780
|
counters: { ...this._counters },
|
|
781
|
+
receipts: this._receipts.size,
|
|
782
|
+
idempotencyRecords: this._idempotency.size,
|
|
354
783
|
};
|
|
355
784
|
}
|
|
356
785
|
|
|
357
786
|
snapshot() {
|
|
358
787
|
return {
|
|
359
|
-
version:
|
|
788
|
+
version: 3,
|
|
360
789
|
limits: { ...this._limits },
|
|
361
790
|
log: this._log.map((m) => this._cloneMessage(m)),
|
|
362
791
|
seq: this._seq,
|
|
@@ -364,6 +793,13 @@ export class TeamMailbox {
|
|
|
364
793
|
recipients: Array.from(this._recipients),
|
|
365
794
|
totalBytes: this._totalBytes,
|
|
366
795
|
counters: { ...this._counters },
|
|
796
|
+
receipts: Array.from(this._receipts.entries()).map(([key, receipt]) => [
|
|
797
|
+
key,
|
|
798
|
+
this._cloneMessage(receipt),
|
|
799
|
+
]),
|
|
800
|
+
idempotency: Array.from(this._idempotency.entries()).map(
|
|
801
|
+
([key, record]) => [key, this._cloneMessage(record)],
|
|
802
|
+
),
|
|
367
803
|
};
|
|
368
804
|
}
|
|
369
805
|
|
|
@@ -374,6 +810,8 @@ export class TeamMailbox {
|
|
|
374
810
|
maxMessageBytes,
|
|
375
811
|
maxMessages,
|
|
376
812
|
maxTotalBytes,
|
|
813
|
+
maxReceiptHistory,
|
|
814
|
+
maxIdempotencyHistory,
|
|
377
815
|
} = {},
|
|
378
816
|
) {
|
|
379
817
|
const savedLimits = snap?.limits || {};
|
|
@@ -391,6 +829,14 @@ export class TeamMailbox {
|
|
|
391
829
|
maxTotalBytes ??
|
|
392
830
|
savedLimits.maxTotalBytes ??
|
|
393
831
|
DEFAULT_TEAM_MAILBOX_LIMITS.maxTotalBytes,
|
|
832
|
+
maxReceiptHistory:
|
|
833
|
+
maxReceiptHistory ??
|
|
834
|
+
savedLimits.maxReceiptHistory ??
|
|
835
|
+
DEFAULT_TEAM_MAILBOX_LIMITS.maxReceiptHistory,
|
|
836
|
+
maxIdempotencyHistory:
|
|
837
|
+
maxIdempotencyHistory ??
|
|
838
|
+
savedLimits.maxIdempotencyHistory ??
|
|
839
|
+
DEFAULT_TEAM_MAILBOX_LIMITS.maxIdempotencyHistory,
|
|
394
840
|
recipients: snap?.recipients || [],
|
|
395
841
|
});
|
|
396
842
|
mb._log = Array.isArray(snap?.log)
|
|
@@ -465,6 +911,62 @@ export class TeamMailbox {
|
|
|
465
911
|
}
|
|
466
912
|
mb._delivered.set(recipient, cursor);
|
|
467
913
|
}
|
|
914
|
+
mb._receipts = new Map();
|
|
915
|
+
for (const entry of Array.isArray(snap?.receipts) ? snap.receipts : []) {
|
|
916
|
+
const [key, receipt] = Array.isArray(entry) ? entry : [];
|
|
917
|
+
if (
|
|
918
|
+
typeof key !== "string" ||
|
|
919
|
+
!receipt ||
|
|
920
|
+
typeof receipt !== "object" ||
|
|
921
|
+
typeof receipt.recipient !== "string" ||
|
|
922
|
+
!Number.isSafeInteger(receipt.messageId) ||
|
|
923
|
+
receipt.messageId <= 0 ||
|
|
924
|
+
receipt.messageId > mb._seq ||
|
|
925
|
+
!RECEIPT_STATUSES.has(receipt.status) ||
|
|
926
|
+
key !== mb._receiptKey(receipt.recipient, receipt.messageId)
|
|
927
|
+
) {
|
|
928
|
+
throw createMailboxError(
|
|
929
|
+
TEAM_MAILBOX_ERROR_CODES.MESSAGE_INVALID,
|
|
930
|
+
"TeamMailbox.restore: invalid message receipt",
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
mb._receipts.set(key, mb._cloneMessage(receipt));
|
|
934
|
+
}
|
|
935
|
+
if (mb._receipts.size > mb._limits.maxReceiptHistory) {
|
|
936
|
+
throw createMailboxError(
|
|
937
|
+
TEAM_MAILBOX_ERROR_CODES.CAPACITY_EXCEEDED,
|
|
938
|
+
"TeamMailbox.restore: receipt history exceeds mailbox capacity",
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
mb._idempotency = new Map();
|
|
942
|
+
for (const entry of Array.isArray(snap?.idempotency)
|
|
943
|
+
? snap.idempotency
|
|
944
|
+
: []) {
|
|
945
|
+
const [key, record] = Array.isArray(entry) ? entry : [];
|
|
946
|
+
if (
|
|
947
|
+
typeof key !== "string" ||
|
|
948
|
+
!record ||
|
|
949
|
+
typeof record !== "object" ||
|
|
950
|
+
!Number.isSafeInteger(record.messageId) ||
|
|
951
|
+
record.messageId <= 0 ||
|
|
952
|
+
record.messageId > mb._seq ||
|
|
953
|
+
typeof record.payloadDigest !== "string" ||
|
|
954
|
+
!record.message ||
|
|
955
|
+
record.message.id !== record.messageId
|
|
956
|
+
) {
|
|
957
|
+
throw createMailboxError(
|
|
958
|
+
TEAM_MAILBOX_ERROR_CODES.MESSAGE_INVALID,
|
|
959
|
+
"TeamMailbox.restore: invalid idempotency record",
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
mb._idempotency.set(key, mb._cloneMessage(record));
|
|
963
|
+
}
|
|
964
|
+
if (mb._idempotency.size > mb._limits.maxIdempotencyHistory) {
|
|
965
|
+
throw createMailboxError(
|
|
966
|
+
TEAM_MAILBOX_ERROR_CODES.CAPACITY_EXCEEDED,
|
|
967
|
+
"TeamMailbox.restore: idempotency history exceeds mailbox capacity",
|
|
968
|
+
);
|
|
969
|
+
}
|
|
468
970
|
const counters = snap?.counters;
|
|
469
971
|
mb._counters = {
|
|
470
972
|
acceptedMessages: nonNegativeNumber(counters?.acceptedMessages, mb._seq),
|
|
@@ -474,6 +976,10 @@ export class TeamMailbox {
|
|
|
474
976
|
compactionRuns: nonNegativeNumber(counters?.compactionRuns),
|
|
475
977
|
compactedMessages: nonNegativeNumber(counters?.compactedMessages),
|
|
476
978
|
compactedBytes: nonNegativeNumber(counters?.compactedBytes),
|
|
979
|
+
deliveryAttempts: nonNegativeNumber(counters?.deliveryAttempts),
|
|
980
|
+
processedMessages: nonNegativeNumber(counters?.processedMessages),
|
|
981
|
+
deadLetteredMessages: nonNegativeNumber(counters?.deadLetteredMessages),
|
|
982
|
+
idempotentReplays: nonNegativeNumber(counters?.idempotentReplays),
|
|
477
983
|
};
|
|
478
984
|
return mb;
|
|
479
985
|
}
|