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.
Files changed (166) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-D5_pwQ7M.js → AIOps-BnZ24WRk.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DgoYO32D.js → ActionButton-ivXY7MGb.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BIpoZ4QH.js → Analytics-BQS4OD-k.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BJ3XrCEe.js → AppLayout-wOBuOKTO.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DUFs_izZ.js → Artifacts-BYuC4nw-.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-BZdPwwfL.js → Audit-BIeVfBt6.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DK9Fn-8B.js → BackgroundAgents-hIuyhmJ7.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Bd6bOhV_.js → Backup-B6ohdgPQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CRnWlXrd.js → BaseInput-BzgYCw6h.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-D1G_yEl6.js → Chat-CwahvZU1.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDAlEtm-.js → ChatBubbleRenderer-B-1IZ_k8.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-CP35SH7R.js → Checkbox-26EDP2x0.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DugeL4pj.js → Codegen-DYLw1Zvd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DPuJsvKg.js → Col-DH_zAvD-.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CZqNjNNp.js → Community-DURrTXJu.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-eeyIPEAb.js → Compact-CQyBqN0p.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-MGzTw87k.js → Compliance-a0DfgKHD.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-D3goVIA5.js → Cowork-C6fY_5Ke.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-9HA8hLLG.js → Cron-DOwmpbTe.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DCSOX3l6.js → Crosschain-DEPOIEUs.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-ji3lN5Bc.js → DID-DaKc-CEV.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DlZvF1E5.js → Dashboard-C-0KLXq5.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-2FyZcow9.js → Dropdown-CnsDukzr.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-qH2bSpSY.js → EmailListRenderer-Bi_ACD4N.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bfx47wZj.js → FamilyGuardDashboard-BZASpnx3.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DhGcZbIv.js → Federation-43oTtzyp.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BMgi9_YQ.js → FormItemContext-oyVadnkZ.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DugMac-4.js → GenericCardRenderer-Cwvfd6TZ.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-D943BpH-.js → Git-Cgi2wvU0.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DZ66a8Aa.js → Governance-l8TWDKZ0.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-B00Mp2tN.js → Inference-Dc8j6TaO.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BdwXXO2Q.js → KnowledgeGraph-DgcO-Fwz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-_MVXyYWc.js → Logs-DjiZpiwa.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-hOTeNyi7.js → MarkdownRenderer-B5MFCi0N.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-CxeHRsnO.js → Marketplace-BeDn1hFc.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BOieTxZ4.js → McpTools-PGdVfWK6.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-BQD26CM3.js → Memory-e76lY3a1.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CPJ8ZlvR.js → MobileBridge-DKlQqot_.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-D7qRVu0y.js → MobileProjects-C4g6WOEr.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BOyndGuk.js → Mtc-1j3NpRQq.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-CXQ_8ZoR.js → MtcAudit-DgwiVd1C.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-32Oh9KbH.js → Multisig-Dzg5lZZY.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CFAd00CX.js → NLProgramming-D7GYhst-.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-BBCY_qJA.js → Notes-Blcfvaic.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-uaQ0PdmW.js → NotificationSettings-Dc4Y6vfr.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-dgM87XIn.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-CmFn7Oeo.js → Organization-DBXq024L.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-R_avat3a.js → Overflow-CFPbdx1Y.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BIhLFmkY.js → P2P-w7z61NR5.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DTJjk-_i.js → PdhVaultBrowser-B7eGGZOa.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-CkzYv8Yb.js → Permissions-Df5Cl50D.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub--0QvN2Nz.js → PersonalDataHub-C-We7RF6.js} +5 -5
  54. package/src/assets/web-panel/assets/{Pipeline-Bfw5gQCH.js → Pipeline-B6fHPnKy.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DicjKfax.js → Privacy-B85BdiIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DUl_ykBp.js → ProjectInit-CcOyazSe.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-CPYDvN8a.js → ProjectSettings-BE53KfuU.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-DnZ9_fAr.js → Projects-DOvMOoe_.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-BX1jP31w.js → Providers-5l7sT9lq.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-COab5qUl.js → QrScannerModal-DE37RXy4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CdKQ_JZ0.js → QuickAsk-BGmGCo7j.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BPFHg8pK.js → Recommend-BFdx8Nrm.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-CqrfTJvO.js → RemoteSession-C1w-j_hB.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DM-l8Pb3.js → Reputation-BOvn-10y.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CKGBTeCw.js → Row-db-a1YO9.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-Wb0YHIkc.js → RssFeed-DeS43ZGM.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DiBjxKv-.js → Search-BaQYNfeb.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-BAVEmbWP.js → Security-yt1ESHDg.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-A_a3TIRn.js → Services-BcNqPUsS.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CloHJ-I7.js → Skeleton-X3ceUQWf.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CkvV5hKa.js → Skills-DmYeax6C.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-BYozxxsV.js → Sla-C_fdfbuJ.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-CWianiZF.js → SpeechSettings-CDqohXXQ.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BR4G75do.js → SyncSettings-B25maYEY.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-D6gyUVrM.js → Tasks-3ILwauYw.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DbfYIRzT.js → Templates-DUH0iuc4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BHutvukT.js → Tenant-DrvhHf9T.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-CYEiy2wJ.js → Terminal-C2lJVYHA.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D9s1AxfE.js → TimelineRenderer-C1Hf_jYn.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-ij9vxnoy.js → Tokens-DniS1NpS.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dq_DCVeQ.js → Trigger-4jrcb8oA.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-Bs6wdfoH.js → Trust-DiBte55w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CLazyr5t.js → UkeySign-CZRtc4VB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-viWotIHA.js → VideoEditing-BtYGJDmg.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DlZ-A78d.js → Wallet-D1nrTm7H.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DSmOND3Y.js → WebAuthn-DI91acpx.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BempLOJi.js → WorkflowEditor-BtQjI50d.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CyJzf4JI.js → chat-I4A-f4FC.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-C62q7WrL.js → colors-QZkDkFVt.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-7dYM1KW3.js → compact-item--FtCdzpr.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CYTy7z2Q.js → createContext-CaIKIKxP.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-D7qVrkrO.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DHoclUqm.js → hasIn-DLYUII6-.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CW86F2Zw.js → index-0DYBQ-6e.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-C2efftJb.js → index-21I79XSf.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-4gWv-Ubg.js → index-3rVtgqa2.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-QXtrdrub.js → index-B65QxJaG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BMxvWauP.js → index-BGWSmE3G.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dyq9tK5u.js → index-BLGJmgmx.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DYKwGrAn.js → index-BcgtvJbC.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BMRmxFU_.js → index-BebTT7iR.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-XrsAHHQN.js → index-BkJgWW_L.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-2C8Xx2UO.js → index-Bs0qPPFb.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CnEZ4ZHj.js → index-BvdwNnHC.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-DkczsbiP.js → index-BxPoPofq.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DvEtHpLc.js → index-C3PnIEOH.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DK84lAS0.js → index-CBtII-nl.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DKdv8cju.js → index-CL4c5D6G.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CjeViaU-.js → index-CQ7OBHka.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DhJjifZe.js → index-CXaeZCRP.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BghdJ87W.js → index-CgMm85BY.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-lHfNxiB5.js → index-Ch0IFpGy.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DJzZaPlb.js → index-CisYjy88.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DghnbMlY.js → index-D2xETCkj.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C5YWYlW9.js → index-D4CXFU-w.js} +1 -1
  116. package/src/assets/web-panel/assets/index-D9Y_ZwZn.js +1 -0
  117. package/src/assets/web-panel/assets/{index-LK1MZoBj.js → index-DA_TPdzX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DOFAwpUK.js → index-DAuf8r5o.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DA-_807C.js → index-DGLHSCHM.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-9uR7rc9a.js → index-DJrd_CR3.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CVTnruDt.js → index-DK2r6Bv7.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ZXYp76IB.js → index-DVitI3UQ.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_cPes4G.js +1 -0
  124. package/src/assets/web-panel/assets/{index-Bzz0aJ86.js → index-Deg2S9ZJ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DHgSRimK.js → index-Du2wUavy.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-94ktimZG.js → index-OQPIS_am.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-3pI7AfmD.js → index-Ooo_5jHo.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DNLD3nZY.js → index-Ygi2rYL1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CZRBvN6i.js → index-eTqL91aV.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D6BC4mmz.js → index-hfiZ-i2b.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DAYanuSk.js → index-vfiDxdN2.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-NuNCstRO.js → index-yayaayEA.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CwCl5sFQ.js → initDefaultProps-DDkTvw8k.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BFJFy70N.js → motion-fkr96tN5.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-ByrtVGq8.js → move-CGBV0SV2.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BFiH1mT3.js → mtc-parser-DAsCliFt.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-bdvgpivj.js → omit-BM8PMmKN.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BlfuuE3s.js → pickAttrs-C6SdWoTK.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-t2SC3DMy.js → placementArrow-BG0EVu-d.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CoEXv2Li.js → responsiveObserve-L10IvfYC.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BfXCf0Fu.js → slide-TddIkbrn.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DNsdyOuS.js → statusUtils-FlveF2Yn.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-9aHukmoG.js → styleChecker-BrXRyebg.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-dTkPxzHN.js → useFlexGapSupport-B7_oiDXZ.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DINwfItP.js → useFs-CQYeRYWb.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-Bj_RriBV.js → usePersonalDataHub-DspIkhVi.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BzRAZrTc.js → vnode-BCwmwMAW.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CamPjkeK.js → zoom-B0cEGluV.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/team.js +74 -2
  151. package/src/data/changelog.json +26 -0
  152. package/src/generated/cc-agent-protocol.schema.json +49 -0
  153. package/src/lib/agent-team/team-mailbox.js +508 -2
  154. package/src/lib/agent-team/team-message-bridge.js +644 -0
  155. package/src/lib/agent-team/team-message-tools.js +198 -0
  156. package/src/lib/agent-team/team-runner.js +57 -3
  157. package/src/lib/graph-kernel/compiler.js +548 -16
  158. package/src/lib/graph-kernel/runtime.js +1158 -30
  159. package/src/lib/graph-kernel/trace-reducer.js +101 -7
  160. package/src/lib/subagent-inheritance.js +5 -3
  161. package/src/runtime/agent-core.js +38 -0
  162. package/src/runtime/headless-runner.js +36 -3
  163. package/src/assets/web-panel/assets/OrderTableRenderer-kpBxJ-SG.js +0 -1
  164. package/src/assets/web-panel/assets/devWarning-BdTg_AMF.js +0 -1
  165. package/src/assets/web-panel/assets/index-B6WvFV4N.js +0 -1
  166. 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({ from = null, to, subject = null, body = null } = {}) {
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: 2,
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
  }