chainlesschain 0.165.5 → 0.165.8

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 (256) hide show
  1. package/package.json +2 -1
  2. package/src/assets/web-panel/assets/{AIOps-CEeYTeDB.js → AIOps-B3gWmXjo.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BeRUQ3V2.js → ActionButton-G9pdT-0e.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DrV718XF.js → Analytics-Ca30J7m2.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Cd2Frt7Q.js → AppLayout-BtVTm8PU.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CUJMlORU.js → Artifacts-BhViNa6u.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CXJcfT6p.js → Audit-DhKq-FnY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DOv6Z7H9.js → BackgroundAgents-BL0SVbnE.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BwdDyBI0.js → Backup-BWkblnFi.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BmLn4kYH.js → BaseInput-ClaRNrF8.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-COg8pAW2.js → Chat-sZE2Z77o.js} +4 -4
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-BAfKOnRe.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-BCP4uoU8.js → Checkbox-Cvhay5WM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-pRt6Y9JI.js → Codegen-BpejL-4N.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DLaprKhS.js → Col-g2Ki6g_1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DyATsz5f.js → Community-D2xKbNo9.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BXAaupZK.js → Compact-DbL1Pds3.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DpcJBELr.js → Compliance-DGtZCV4c.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DG4EFWyd.js → Cowork-4kQpoNsI.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-F6Oy5n2P.js → Cron-BxTYXXAN.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BOdgtalX.js → Crosschain-k0J0LT2N.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DT1dSkjH.js → DID-BaqhoPgN.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-NBUIqWzC.js → Dashboard-T4RVPIM1.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CSpHeMYB.js → Dropdown-DszOToWq.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CEG0xISr.js → EmailListRenderer-C9KJU7HF.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BTZ0BZna.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BuuCTn2V.js → Federation-BI-_2xZX.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-KAVceevk.js → FormItemContext-DsnDEPsl.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DnHpcQvW.js → GenericCardRenderer-BLncAKKK.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-C_B_0HwB.js → Git-BPQhGXDn.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DPSZuzuu.js → Governance-CyElbUQW.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BV-06V7q.js → Inference-CcSntBQD.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-wpDuupn6.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-Bseca1_I.js → Logs-CNEtr-wz.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-d3kM4DDL.js → Marketplace-Dd2notAJ.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-DQ_xZ23i.js → McpTools-BBsN2CSW.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-CRIeYO4L.js → Memory-Dg2uTvwx.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BsllIa9l.js → MobileBridge-r1YMRiJV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-BUuQy30X.js → MobileProjects-C-89kSm4.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BOFDIzrC.js → Mtc-CrPZcVgf.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-BQg-vyvG.js → MtcAudit-sxi7hqJb.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-Bo1jrPdN.js → Multisig-CAgOMfF1.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Bmod8alw.js → NLProgramming-DafaPts3.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CGGFL8w-.js → Notes-CJM-xUW_.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BsZvCJxC.js → NotificationSettings-L0ZjfAh_.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-C1-Quo_h.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-BjOeDYOi.js → Organization-DeLtgAD8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DwVV3l5A.js → Overflow-CKC2b10z.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cu3DQT9W.js → P2P-eyf2rzY1.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Cg0gR7dH.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-CsAXxRN5.js → Permissions-B8p3W6JK.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-C-nNmnYp.js → PersonalDataHub-BnRMxTq3.js} +5 -5
  54. package/src/assets/web-panel/assets/{Pipeline-BOnW7nNc.js → Pipeline-DjCNI1li.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-ClLSyGKt.js → Privacy-u6gXLbWT.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DuLvjjq7.js → ProjectInit-BZAw4tkM.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-DQYYbDD9.js → ProjectSettings-B1Tr72q7.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-Drw8MBGK.js → Projects-DO7HFuBQ.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-ckqHQe6K.js → Providers-BjCztyhj.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-DW5BXk-G.js → QrScannerModal-ZWyCUbR1.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DDLnhNTp.js → QuickAsk-DhDjeW4G.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-PJQ2C91U.js → Recommend-R_CN2MuU.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-s6wmda34.js → RemoteSession-uxFcujeu.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DC2sb-I3.js → Reputation-DX8D7rks.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-hPGk1eGj.js → Row-BYahQILD.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-RIvfZql0.js → RssFeed-CUSQ2tm-.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DFg1G1eR.js → Search-CnN_S8Cs.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-D6H5p8Dp.js → Security-DcZlh-O4.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-nxYiFV_w.js → Services-ZhBOjzIX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-BmgFNAdB.js → Skeleton-D5Qnq_lI.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CsaaD2av.js → Skills-BC9cZ3Be.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-yM5y9yvK.js → Sla-DauJ3E8_.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-CFVhEyyr.js → SyncSettings-DUvJbSct.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-DdO02Inn.js → Tasks-BXa-5noQ.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Eo2d8Llu.js → Templates-JxaiUt_3.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-ChESOXgy.js → Tenant-BW4ERQN8.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BJ7oDxTI.js → Terminal-DltUbdtv.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-hWX0fUvz.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-DVBZQnd-.js → Tokens-D_VEV60K.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-D89cQfVq.js → Trigger-CCXTCs0R.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BIVSb4-V.js → Trust-lkGRIPcG.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B58gWopl.js → UkeySign-BfGeQ9HX.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DhHI7bmG.js → VideoEditing-BEC7pYvc.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-C4FheWS_.js → Wallet-BZdG24o0.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DnPRq-Bx.js → WebAuthn-yoEddedp.js} +3 -3
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BerRPjpJ.js → WorkflowEditor-BXZEprOW.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BzZ52iKr.js → chat-gg0bzZal.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BI_E4BAn.js → colors-DjRVRuin.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-d50mr3yk.js → compact-item-su-dieAc.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-C6FsQ1IH.js → createContext-Ci3S8oKF.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DLZEMgLP.js → hasIn-D8F10heH.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-D-aBumtY.js → index-11wtYQiF.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CjuKZBqJ.js → index-1IgxgALJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-9H_M2jju.js → index-1agckWSq.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DE0V6cp-.js → index-5JqhujnR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Y2-q7QLm.js → index-6Tniqk1s.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DoFtCVZ7.js → index-8Z-8BIlN.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-KNGgO4Wm.js → index-BCI5v0yB.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DY4j5zkF.js → index-BDJntYmJ.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BZjjdQ62.js → index-BGzixOGZ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BTpIOTfR.js → index-BMMOVoLD.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BB9SqVSt.js → index-BTFpdBtt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-AHr7ryWf.js → index-Ba_HyPnE.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Pvf30m8Q.js → index-BbF1IHof.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CRN5TRTQ.js → index-BgT9su_e.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CUaEHfQ0.js → index-BzPB6kGZ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CdKt9fOd.js → index-C6ygzplr.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Dec0Kf_Z.js → index-CC3ZDjzY.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-IPkwSAW_.js → index-CEmXRhAE.js} +1 -1
  112. package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
  113. package/src/assets/web-panel/assets/{index-C3k8kevG.js → index-CMPqd5Cv.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-A5CDWP7y.js → index-COWd8uGx.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CO8DAF7U.js → index-CPIYSp4H.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CVzuawCX.js → index-CR0h6seT.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Co7s9LsB.js → index-CUmDhC4j.js} +1 -1
  118. package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
  119. package/src/assets/web-panel/assets/{index-CLoTQr0W.js → index-CusgZPtc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B81mo1Et.js → index-DBAETXft.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BcSUl9fA.js → index-DIJ2qFOC.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Q5Tn735s.js → index-Dbq4NMET.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-GXE0D8Nx.js → index-DkMEF5VT.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-TWG-_D1M.js → index-DkfQR3hM.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BAE3hwev.js → index-DnLucOn9.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CgkoH3_g.js → index-DugB_aKa.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BRGx3w6T.js → index-DyLUarhZ.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-BeM4hfht.js → index-T6_6ygJZ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DsscwxPE.js → index-Tu8eWqh-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bmr_vxXD.js → index-W7XUgdVL.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BoNZlzhQ.js → index-dHZinfko.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DqkbHHg8.js → index-jyNgp5FI.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D-4Hzgej.js → initDefaultProps-Cv4p8e4w.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-D4Wl4xU7.js → motion-B8EwmMOw.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Cd5eQvL0.js → move-B8R9F8TK.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-B4ilslCW.js → mtc-parser-DoFYz5Bp.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-Ckarx-tf.js → omit-CHWYLBjI.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-QJ0bpCmc.js → pickAttrs-CXuPDO77.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CKHm-tq8.js → placementArrow-BaAtUUon.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-CV6a_w_Y.js → responsiveObserve-DCc4zcDD.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-mHpeQFgk.js → slide-B_lx89oZ.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DIaQNQWA.js → statusUtils-DE8DPmR0.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-PJpzavV8.js → styleChecker-1ktXOsL_.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-BH7zTw8S.js → useFlexGapSupport-xH89-wjh.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BDNG3JDd.js → useFs-DscvfW5p.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CY2VWdUg.js → usePersonalDataHub-RdMKQta7.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-D0kw9jhz.js → vnode-Z3wTni2T.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-B5g7TN7r.js → zoom-B0iChCsm.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +3 -3
  151. package/src/commands/agent.js +105 -62
  152. package/src/commands/background-session.js +100 -10
  153. package/src/commands/git.js +39 -0
  154. package/src/commands/mcp.js +5 -0
  155. package/src/commands/plugin.js +160 -29
  156. package/src/commands/remote-control.js +295 -110
  157. package/src/commands/session-group.js +131 -0
  158. package/src/commands/session-location.js +48 -0
  159. package/src/commands/session-message.js +171 -0
  160. package/src/commands/session.js +22 -0
  161. package/src/commands/sync-providers.js +4 -1
  162. package/src/commands/update.js +1 -0
  163. package/src/data/changelog.json +41 -2
  164. package/src/gateways/ws/ws-agent-handler.js +20 -12
  165. package/src/harness/jsonl-session-store.js +279 -37
  166. package/src/harness/mcp-client.js +832 -91
  167. package/src/harness/session-index.js +15 -4
  168. package/src/lib/agent-worktree.js +8 -1
  169. package/src/lib/auto-mode-safety-classifier.js +3 -17
  170. package/src/lib/background-agent-keeper-protocol.js +26 -6
  171. package/src/lib/background-agent-supervisor.js +17 -1
  172. package/src/lib/background-session-transport.js +153 -27
  173. package/src/lib/browser-evidence.js +1405 -0
  174. package/src/lib/chrome-connector.js +465 -79
  175. package/src/lib/claude-project-auto-memory.js +481 -0
  176. package/src/lib/claude-project-storage-layout.js +104 -0
  177. package/src/lib/cloud/cloud-client.js +40 -0
  178. package/src/lib/execution-location-local-supervisor.mjs +226 -0
  179. package/src/lib/execution-location-runner-lifecycle.js +991 -0
  180. package/src/lib/execution-location-target-preflight.js +333 -0
  181. package/src/lib/execution-location-target.js +783 -13
  182. package/src/lib/file-lines.js +6 -1
  183. package/src/lib/gateway-client-resilience.js +571 -0
  184. package/src/lib/gitlab-mr-worktree.js +176 -0
  185. package/src/lib/hooks-v2-producers.js +119 -15
  186. package/src/lib/hooks-v2-workspace-context.js +44 -1
  187. package/src/lib/host-resource-budget.js +295 -0
  188. package/src/lib/ide-context.js +15 -2
  189. package/src/lib/llm-config-defaults.js +43 -0
  190. package/src/lib/mcp-client.js +2 -0
  191. package/src/lib/mcp-headers-helper.js +47 -5
  192. package/src/lib/mcp-lifecycle-authority.js +887 -0
  193. package/src/lib/mcp-oauth.js +156 -26
  194. package/src/lib/mcp-tls.js +330 -0
  195. package/src/lib/output-styles.js +12 -0
  196. package/src/lib/paths.js +260 -32
  197. package/src/lib/permission-request.js +48 -0
  198. package/src/lib/plugin-runtime/bin.js +3 -1
  199. package/src/lib/plugin-runtime/hooks.js +3 -1
  200. package/src/lib/plugin-runtime/install.js +730 -71
  201. package/src/lib/plugin-runtime/lsp.js +4 -4
  202. package/src/lib/plugin-runtime/marketplace-archive-source.js +366 -0
  203. package/src/lib/plugin-runtime/marketplace-catalog.js +63 -6
  204. package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
  205. package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
  206. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
  207. package/src/lib/plugin-runtime/marketplace-source-adapter.js +188 -0
  208. package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
  209. package/src/lib/plugin-runtime/mcp.js +3 -1
  210. package/src/lib/plugin-runtime/monitors.js +3 -1
  211. package/src/lib/plugin-runtime/remote-source.js +583 -58
  212. package/src/lib/plugin-runtime/settings.js +3 -1
  213. package/src/lib/plugin-runtime/trust.js +274 -13
  214. package/src/lib/plugin-security.js +393 -41
  215. package/src/lib/plugin-source-identity.js +850 -0
  216. package/src/lib/process-execution-broker/credential-agent.js +210 -41
  217. package/src/lib/process-execution-broker/index.js +15 -1
  218. package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
  219. package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
  220. package/src/lib/project-mcp-trust.js +165 -10
  221. package/src/lib/remote-approval-bridge.js +136 -46
  222. package/src/lib/remote-control.js +217 -31
  223. package/src/lib/resume-session.js +9 -6
  224. package/src/lib/session-core-singletons.js +26 -11
  225. package/src/lib/session-host-lease.js +52 -16
  226. package/src/lib/session-lifecycle-scope.js +153 -0
  227. package/src/lib/session-message-fabric.js +1252 -0
  228. package/src/lib/session-projection.js +286 -3
  229. package/src/lib/session-runtime-retention.js +155 -0
  230. package/src/lib/session-store-guard.js +3 -1
  231. package/src/lib/session-workbench-store.js +466 -0
  232. package/src/lib/settings-loader.cjs +29 -0
  233. package/src/lib/spellcheck.js +211 -0
  234. package/src/lib/sub-agent-context.js +9 -9
  235. package/src/lib/sync-cli-db.js +485 -129
  236. package/src/lib/sync-credentials.js +31 -20
  237. package/src/lib/web-fetch.js +112 -40
  238. package/src/lib/web-search.js +127 -61
  239. package/src/lib/workspace-trust.js +793 -0
  240. package/src/repl/agent-repl.js +175 -19
  241. package/src/repl/permission-prompt.js +29 -8
  242. package/src/repl/repl-keybindings.js +55 -0
  243. package/src/runtime/agent-core.js +362 -20
  244. package/src/runtime/bootstrap.js +37 -13
  245. package/src/runtime/coding-agent-contract-shared.cjs +11 -4
  246. package/src/runtime/headless-runner.js +281 -102
  247. package/src/runtime/headless-stream.js +199 -30
  248. package/src/runtime/mcp-config.js +298 -6
  249. package/src/runtime/policies/agent-policy.js +10 -0
  250. package/src/runtime/status-diagnostics-lite.js +9 -1
  251. package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +0 -1
  252. package/src/assets/web-panel/assets/MarkdownRenderer-B4RLp2zs.js +0 -1
  253. package/src/assets/web-panel/assets/SpeechSettings-DPy1XMYh.js +0 -1
  254. package/src/assets/web-panel/assets/devWarning-CagEcIex.js +0 -1
  255. package/src/assets/web-panel/assets/index-pzT9PpOU.js +0 -1
  256. package/src/assets/web-panel/assets/index-z1kFadaX.js +0 -1
@@ -0,0 +1,1252 @@
1
+ /**
2
+ * Durable, CLI-owned cross-session message fabric.
3
+ *
4
+ * The fabric deliberately owns only addressing, admission, receipts and inbox
5
+ * durability. Transports (same-host processes, Remote Control, A2A or a future
6
+ * relay) may all call the same API, so none of them can invent a broader
7
+ * inbound policy. Every mutation is a strict cross-process locked, atomic JSON
8
+ * transaction and every endpoint registration has a fresh epoch; a reused
9
+ * name can therefore never inherit an earlier session's inbox.
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { createHash, randomUUID } from "node:crypto";
15
+ import { ensureDir } from "./paths.js";
16
+ import {
17
+ ensureTrustedSessionLifecycleScope,
18
+ resolveTrustedSessionLifecycleScope,
19
+ sessionLifecycleFabricDirectory,
20
+ } from "./session-lifecycle-scope.js";
21
+ import { withFileLock } from "./with-file-lock.js";
22
+
23
+ export const SESSION_MESSAGE_FABRIC_SCHEMA =
24
+ "chainlesschain.session-message-fabric/v1";
25
+ export const SESSION_MESSAGE_FABRIC_PROJECTION_SCHEMA =
26
+ "chainlesschain.session-message-fabric-projection/v1";
27
+
28
+ export const SESSION_MESSAGE_POLICIES = Object.freeze([
29
+ "accept",
30
+ "hold",
31
+ "refuse",
32
+ ]);
33
+
34
+ export const SESSION_MESSAGE_RECEIPT_STATUSES = Object.freeze([
35
+ "delivered",
36
+ "held",
37
+ "refused",
38
+ "full",
39
+ "rate_limited",
40
+ "expired",
41
+ ]);
42
+
43
+ export const SESSION_MESSAGE_FABRIC_LIMITS = Object.freeze({
44
+ maxMessageBytes: 256 * 1024,
45
+ maxPendingPerRecipient: 100,
46
+ maxReceiptHistory: 5_000,
47
+ maxMessagesPerSenderWindow: 120,
48
+ senderRateWindowMs: 60_000,
49
+ defaultTtlMs: 24 * 60 * 60 * 1000,
50
+ maxTtlMs: 7 * 24 * 60 * 60 * 1000,
51
+ });
52
+
53
+ export const SESSION_MESSAGE_FABRIC_ERROR_CODES = Object.freeze({
54
+ INVALID_ARGUMENT: "SESSION_MESSAGE_INVALID_ARGUMENT",
55
+ ENDPOINT_NOT_FOUND: "SESSION_MESSAGE_ENDPOINT_NOT_FOUND",
56
+ NAME_CONFLICT: "SESSION_MESSAGE_NAME_CONFLICT",
57
+ MESSAGE_TOO_LARGE: "SESSION_MESSAGE_TOO_LARGE",
58
+ MESSAGE_ID_CONFLICT: "SESSION_MESSAGE_ID_CONFLICT",
59
+ STALE_REVISION: "SESSION_MESSAGE_STALE_REVISION",
60
+ STATE_CORRUPT: "SESSION_MESSAGE_STATE_CORRUPT",
61
+ STATE_UNAVAILABLE: "SESSION_MESSAGE_STATE_UNAVAILABLE",
62
+ });
63
+
64
+ const PENDING_STATUSES = new Set(["held", "delivered"]);
65
+ const RECEIPT_STATUSES = new Set(SESSION_MESSAGE_RECEIPT_STATUSES);
66
+ const POLICIES = new Set(SESSION_MESSAGE_POLICIES);
67
+ const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
68
+ const ID_PATTERN = /^[^\u0000-\u001f\u007f]{1,256}$/u;
69
+ const MESSAGE_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/u;
70
+
71
+ function fabricError(code, message, details = {}, cause = undefined) {
72
+ const error = new Error(message, cause ? { cause } : undefined);
73
+ error.name = "SessionMessageFabricError";
74
+ error.code = code;
75
+ Object.assign(error, details);
76
+ return error;
77
+ }
78
+
79
+ function stableValue(value) {
80
+ if (Array.isArray(value)) return value.map(stableValue);
81
+ if (!value || typeof value !== "object") return value;
82
+ return Object.fromEntries(
83
+ Object.keys(value)
84
+ .sort()
85
+ .map((key) => [key, stableValue(value[key])]),
86
+ );
87
+ }
88
+
89
+ function digestValue(value) {
90
+ return `sha256:${createHash("sha256")
91
+ .update(JSON.stringify(stableValue(value)))
92
+ .digest("hex")}`;
93
+ }
94
+
95
+ function clone(value) {
96
+ return JSON.parse(JSON.stringify(value));
97
+ }
98
+
99
+ function canonicalName(value) {
100
+ const name = String(value || "")
101
+ .trim()
102
+ .replace(/^@/u, "")
103
+ .toLowerCase();
104
+ if (!NAME_PATTERN.test(name)) {
105
+ throw fabricError(
106
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
107
+ "Session message name must be 1-64 lowercase ASCII letters, digits, '.', '_' or '-'",
108
+ );
109
+ }
110
+ return name;
111
+ }
112
+
113
+ function boundedId(value, label) {
114
+ const id = String(value || "").trim();
115
+ if (!ID_PATTERN.test(id)) {
116
+ throw fabricError(
117
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
118
+ `${label} must be a non-empty bounded string without control characters`,
119
+ );
120
+ }
121
+ return id;
122
+ }
123
+
124
+ function messageId(value, create = randomUUID) {
125
+ const id = value == null ? create() : String(value).trim();
126
+ if (!MESSAGE_ID_PATTERN.test(id)) {
127
+ throw fabricError(
128
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
129
+ "messageId must contain 1-128 safe identifier characters",
130
+ );
131
+ }
132
+ return id;
133
+ }
134
+
135
+ function policyValue(value) {
136
+ const policy = String(value || "accept")
137
+ .trim()
138
+ .toLowerCase();
139
+ if (!POLICIES.has(policy)) {
140
+ throw fabricError(
141
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
142
+ "Inbound policy must be accept, hold or refuse",
143
+ );
144
+ }
145
+ return policy;
146
+ }
147
+
148
+ function positiveInteger(value, fallback, maximum = Number.MAX_SAFE_INTEGER) {
149
+ const number = Number(value);
150
+ if (!Number.isSafeInteger(number) || number <= 0) return fallback;
151
+ return Math.min(number, maximum);
152
+ }
153
+
154
+ function emptyState(now) {
155
+ const state = {
156
+ schema: SESSION_MESSAGE_FABRIC_SCHEMA,
157
+ version: 1,
158
+ revision: 0,
159
+ updatedAt: now,
160
+ endpoints: [],
161
+ channels: [],
162
+ messages: [],
163
+ receipts: [],
164
+ // Per-sender fixed windows are durable so a new CLI process cannot evade
165
+ // the same-host admission limit by reopening the fabric state file.
166
+ rateBuckets: [],
167
+ };
168
+ return { ...state, digest: digestValue(state) };
169
+ }
170
+
171
+ function stateWithoutDigest(state) {
172
+ const { digest: _digest, ...rest } = state;
173
+ return rest;
174
+ }
175
+
176
+ function authorityId(machineId, sessionId) {
177
+ return `sha256:${createHash("sha256")
178
+ .update(`${machineId}\0${sessionId}`)
179
+ .digest("hex")}`;
180
+ }
181
+
182
+ function channelId(senderAuthority, recipientAuthority, recipientEpoch) {
183
+ return digestValue({ senderAuthority, recipientAuthority, recipientEpoch });
184
+ }
185
+
186
+ function endpointAddress(endpoint) {
187
+ return `cc-session://${encodeURIComponent(endpoint.machineId)}/@${endpoint.name}?epoch=${encodeURIComponent(endpoint.epoch)}`;
188
+ }
189
+
190
+ function parseAddress(value) {
191
+ const raw = String(value || "").trim();
192
+ if (!raw.startsWith("cc-session://")) return null;
193
+ let parsed;
194
+ try {
195
+ parsed = new URL(raw);
196
+ } catch (cause) {
197
+ throw fabricError(
198
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
199
+ "Session message address is malformed",
200
+ {},
201
+ cause,
202
+ );
203
+ }
204
+ const name = decodeURIComponent(parsed.pathname.replace(/^\/@?/u, ""));
205
+ const machineId = decodeURIComponent(parsed.hostname);
206
+ const epoch = parsed.searchParams.get("epoch");
207
+ if (!machineId || !epoch) {
208
+ throw fabricError(
209
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
210
+ "Session message address must bind machine and endpoint epoch",
211
+ );
212
+ }
213
+ return {
214
+ name: canonicalName(name),
215
+ machineId: boundedId(machineId, "address machineId"),
216
+ epoch: boundedId(epoch, "address epoch"),
217
+ };
218
+ }
219
+
220
+ function jsonBody(body) {
221
+ let serialized;
222
+ try {
223
+ serialized = JSON.stringify(body);
224
+ } catch (cause) {
225
+ throw fabricError(
226
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
227
+ "Session message body must be JSON-serializable",
228
+ {},
229
+ cause,
230
+ );
231
+ }
232
+ if (serialized === undefined) {
233
+ throw fabricError(
234
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
235
+ "Session message body must be JSON-serializable",
236
+ );
237
+ }
238
+ return { value: JSON.parse(serialized), serialized };
239
+ }
240
+
241
+ function receiptFor(state, senderAuthority, id) {
242
+ return state.receipts.find(
243
+ (receipt) =>
244
+ receipt.senderAuthority === senderAuthority && receipt.messageId === id,
245
+ );
246
+ }
247
+
248
+ function endpointFor(state, selector, { activeOnly = true } = {}) {
249
+ const raw = String(selector || "").trim();
250
+ const address = parseAddress(raw);
251
+ const name = raw.startsWith("@") ? canonicalName(raw) : null;
252
+ return state.endpoints.find((endpoint) => {
253
+ if (activeOnly && endpoint.active !== true) return false;
254
+ if (address) {
255
+ return (
256
+ endpoint.name === address.name &&
257
+ endpoint.machineId === address.machineId &&
258
+ endpoint.epoch === address.epoch
259
+ );
260
+ }
261
+ return (
262
+ endpoint.authorityId === raw ||
263
+ endpoint.sessionId === raw ||
264
+ (name != null && endpoint.name === name) ||
265
+ endpoint.name === raw.toLowerCase()
266
+ );
267
+ });
268
+ }
269
+
270
+ function requireEndpoint(state, selector) {
271
+ const endpoint = endpointFor(state, selector);
272
+ if (!endpoint) {
273
+ throw fabricError(
274
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.ENDPOINT_NOT_FOUND,
275
+ `Session message endpoint was not found: ${String(selector || "")}`,
276
+ );
277
+ }
278
+ return endpoint;
279
+ }
280
+
281
+ function channelFor(state, sender, recipient) {
282
+ const id = channelId(
283
+ sender.authorityId,
284
+ recipient.authorityId,
285
+ recipient.epoch,
286
+ );
287
+ let channel = state.channels.find((entry) => entry.id === id);
288
+ if (!channel) {
289
+ channel = {
290
+ id,
291
+ senderAuthority: sender.authorityId,
292
+ recipientAuthority: recipient.authorityId,
293
+ recipientEpoch: recipient.epoch,
294
+ nextExpected: 1,
295
+ };
296
+ state.channels.push(channel);
297
+ }
298
+ return channel;
299
+ }
300
+
301
+ function pendingForRecipient(state, recipient) {
302
+ return state.messages.filter(
303
+ (message) =>
304
+ message.recipientAuthority === recipient.authorityId &&
305
+ message.recipientEpoch === recipient.epoch &&
306
+ PENDING_STATUSES.has(message.status) &&
307
+ message.acknowledgedAt == null,
308
+ );
309
+ }
310
+
311
+ function updateReceipt(state, message, status, reason, now) {
312
+ let receipt = receiptFor(state, message.senderAuthority, message.messageId);
313
+ if (!receipt) {
314
+ receipt = {
315
+ messageId: message.messageId,
316
+ senderAuthority: message.senderAuthority,
317
+ recipientAuthority: message.recipientAuthority,
318
+ recipientEpoch: message.recipientEpoch,
319
+ sequence: message.sequence,
320
+ payloadDigest: message.payloadDigest,
321
+ status,
322
+ reason,
323
+ createdAt: now,
324
+ updatedAt: now,
325
+ idleNotifiedAt: null,
326
+ readAt: null,
327
+ };
328
+ state.receipts.push(receipt);
329
+ } else {
330
+ receipt.status = status;
331
+ receipt.reason = reason;
332
+ receipt.updatedAt = now;
333
+ }
334
+ message.status = status;
335
+ message.reason = reason;
336
+ message.updatedAt = now;
337
+ return receipt;
338
+ }
339
+
340
+ function pruneRateBuckets(state, now, windowMs) {
341
+ if (!Array.isArray(state.rateBuckets) || state.rateBuckets.length === 0) {
342
+ return false;
343
+ }
344
+ const before = state.rateBuckets.length;
345
+ state.rateBuckets = state.rateBuckets.filter(
346
+ (bucket) => now < bucket.windowStartedAt + windowMs,
347
+ );
348
+ return state.rateBuckets.length !== before;
349
+ }
350
+
351
+ function consumeSenderRate(state, senderAuthority, now, limit, windowMs) {
352
+ state.rateBuckets ??= [];
353
+ let bucket = state.rateBuckets.find(
354
+ (entry) => entry.senderAuthority === senderAuthority,
355
+ );
356
+ if (!bucket || now >= bucket.windowStartedAt + windowMs) {
357
+ if (!bucket) {
358
+ bucket = { senderAuthority, windowStartedAt: now, count: 0 };
359
+ state.rateBuckets.push(bucket);
360
+ } else {
361
+ bucket.windowStartedAt = now;
362
+ bucket.count = 0;
363
+ }
364
+ }
365
+ if (bucket.count >= limit) {
366
+ return {
367
+ allowed: false,
368
+ retryAfterMs: Math.max(1, bucket.windowStartedAt + windowMs - now),
369
+ };
370
+ }
371
+ bucket.count += 1;
372
+ return { allowed: true, retryAfterMs: 0 };
373
+ }
374
+
375
+ function trimReceiptHistory(state, limit) {
376
+ if (state.receipts.length <= limit) return false;
377
+ const protectedIds = new Set(
378
+ state.messages.map(
379
+ (message) => `${message.senderAuthority}\0${message.messageId}`,
380
+ ),
381
+ );
382
+ const removable = state.receipts
383
+ .filter(
384
+ (receipt) =>
385
+ !protectedIds.has(`${receipt.senderAuthority}\0${receipt.messageId}`),
386
+ )
387
+ .sort((left, right) => left.updatedAt - right.updatedAt);
388
+ let changed = false;
389
+ while (state.receipts.length > limit && removable.length > 0) {
390
+ const target = removable.shift();
391
+ const index = state.receipts.indexOf(target);
392
+ if (index >= 0) {
393
+ state.receipts.splice(index, 1);
394
+ changed = true;
395
+ }
396
+ }
397
+ return changed;
398
+ }
399
+
400
+ function compactSettledMessages(state) {
401
+ const before = state.messages.length;
402
+ state.messages = state.messages.filter((message) => {
403
+ if (!PENDING_STATUSES.has(message.status)) return false;
404
+ if (message.acknowledgedAt == null) return true;
405
+ if (message.notifyWhenIdle !== true) return false;
406
+ const receipt = receiptFor(
407
+ state,
408
+ message.senderAuthority,
409
+ message.messageId,
410
+ );
411
+ return receipt?.idleNotifiedAt == null;
412
+ });
413
+ return state.messages.length !== before;
414
+ }
415
+
416
+ function applyExpectedMessage(state, channel, message, recipient, now) {
417
+ if (recipient.policy === "refuse") {
418
+ updateReceipt(state, message, "refused", "policy_refuse", now);
419
+ } else if (recipient.policy === "hold") {
420
+ updateReceipt(state, message, "held", "policy_hold", now);
421
+ return false;
422
+ } else if (recipient.online !== true) {
423
+ // Durable admission is not delivery. In particular, an offline target
424
+ // must never produce a success-looking `delivered` receipt merely because
425
+ // this host appended the payload to its local queue. Reconnect promotes
426
+ // the exact retained sequence through the same policy gate.
427
+ updateReceipt(state, message, "held", "recipient_offline", now);
428
+ return false;
429
+ } else {
430
+ const receipt = updateReceipt(state, message, "delivered", null, now);
431
+ message.deliveredAt = now;
432
+ if (message.notifyWhenIdle && recipient.idle === true) {
433
+ receipt.idleNotifiedAt ??= now;
434
+ }
435
+ }
436
+ channel.nextExpected += 1;
437
+ return true;
438
+ }
439
+
440
+ function promoteChannel(state, channel, now) {
441
+ const recipient = state.endpoints.find(
442
+ (entry) =>
443
+ entry.authorityId === channel.recipientAuthority &&
444
+ entry.epoch === channel.recipientEpoch &&
445
+ entry.active === true,
446
+ );
447
+ if (!recipient) return false;
448
+ let changed = false;
449
+ for (;;) {
450
+ const message = state.messages.find(
451
+ (entry) =>
452
+ entry.channelId === channel.id &&
453
+ entry.sequence === channel.nextExpected &&
454
+ entry.status === "held",
455
+ );
456
+ if (!message) break;
457
+ if (message.expiresAt <= now) {
458
+ updateReceipt(state, message, "expired", "ttl_expired", now);
459
+ channel.nextExpected += 1;
460
+ changed = true;
461
+ continue;
462
+ }
463
+ if (!applyExpectedMessage(state, channel, message, recipient, now)) break;
464
+ changed = true;
465
+ }
466
+ return changed;
467
+ }
468
+
469
+ function sweepExpired(state, now) {
470
+ let changed = false;
471
+ for (const message of state.messages) {
472
+ if (
473
+ PENDING_STATUSES.has(message.status) &&
474
+ message.acknowledgedAt == null &&
475
+ message.expiresAt <= now
476
+ ) {
477
+ updateReceipt(state, message, "expired", "ttl_expired", now);
478
+ const channel = state.channels.find(
479
+ (entry) => entry.id === message.channelId,
480
+ );
481
+ if (channel && channel.nextExpected === message.sequence) {
482
+ channel.nextExpected += 1;
483
+ }
484
+ changed = true;
485
+ }
486
+ }
487
+ for (const channel of state.channels) {
488
+ changed = promoteChannel(state, channel, now) || changed;
489
+ }
490
+ return changed;
491
+ }
492
+
493
+ function validateState(state) {
494
+ const fail = (message) => {
495
+ throw fabricError(
496
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.STATE_CORRUPT,
497
+ `Session message state is corrupt: ${message}`,
498
+ );
499
+ };
500
+ if (
501
+ !state ||
502
+ state.schema !== SESSION_MESSAGE_FABRIC_SCHEMA ||
503
+ state.version !== 1 ||
504
+ !Number.isSafeInteger(state.revision) ||
505
+ state.revision < 0 ||
506
+ !Array.isArray(state.endpoints) ||
507
+ !Array.isArray(state.channels) ||
508
+ !Array.isArray(state.messages) ||
509
+ !Array.isArray(state.receipts) ||
510
+ (state.rateBuckets != null && !Array.isArray(state.rateBuckets)) ||
511
+ typeof state.digest !== "string"
512
+ ) {
513
+ fail("invalid envelope");
514
+ }
515
+ if (digestValue(stateWithoutDigest(state)) !== state.digest) {
516
+ fail("digest mismatch");
517
+ }
518
+ const authorityIds = new Set();
519
+ const activeNames = new Set();
520
+ for (const endpoint of state.endpoints) {
521
+ if (
522
+ typeof endpoint?.authorityId !== "string" ||
523
+ authorityIds.has(endpoint.authorityId) ||
524
+ !NAME_PATTERN.test(endpoint.name) ||
525
+ !POLICIES.has(endpoint.policy) ||
526
+ typeof endpoint.epoch !== "string" ||
527
+ typeof endpoint.active !== "boolean" ||
528
+ typeof endpoint.online !== "boolean" ||
529
+ typeof endpoint.idle !== "boolean"
530
+ ) {
531
+ fail("invalid endpoint");
532
+ }
533
+ authorityIds.add(endpoint.authorityId);
534
+ if (endpoint.active) {
535
+ if (activeNames.has(endpoint.name)) fail("duplicate active name");
536
+ activeNames.add(endpoint.name);
537
+ }
538
+ }
539
+ const channelIds = new Set();
540
+ for (const channel of state.channels) {
541
+ if (
542
+ typeof channel?.id !== "string" ||
543
+ channelIds.has(channel.id) ||
544
+ !Number.isSafeInteger(channel.nextExpected) ||
545
+ channel.nextExpected < 1
546
+ ) {
547
+ fail("invalid channel");
548
+ }
549
+ channelIds.add(channel.id);
550
+ }
551
+ const messageKeys = new Set();
552
+ for (const message of state.messages) {
553
+ const key = `${message?.senderAuthority}\0${message?.messageId}`;
554
+ if (
555
+ messageKeys.has(key) ||
556
+ !MESSAGE_ID_PATTERN.test(message?.messageId || "") ||
557
+ !Number.isSafeInteger(message?.sequence) ||
558
+ message.sequence < 1 ||
559
+ !RECEIPT_STATUSES.has(message?.status) ||
560
+ !channelIds.has(message?.channelId) ||
561
+ typeof message?.payloadDigest !== "string"
562
+ ) {
563
+ fail("invalid message");
564
+ }
565
+ messageKeys.add(key);
566
+ }
567
+ for (const receipt of state.receipts) {
568
+ if (
569
+ !MESSAGE_ID_PATTERN.test(receipt?.messageId || "") ||
570
+ !RECEIPT_STATUSES.has(receipt?.status) ||
571
+ typeof receipt?.payloadDigest !== "string"
572
+ ) {
573
+ fail("invalid receipt");
574
+ }
575
+ }
576
+ const bucketAuthorities = new Set();
577
+ for (const bucket of state.rateBuckets || []) {
578
+ if (
579
+ typeof bucket?.senderAuthority !== "string" ||
580
+ bucketAuthorities.has(bucket.senderAuthority) ||
581
+ !Number.isSafeInteger(bucket.windowStartedAt) ||
582
+ bucket.windowStartedAt < 0 ||
583
+ !Number.isSafeInteger(bucket.count) ||
584
+ bucket.count < 1
585
+ ) {
586
+ fail("invalid rate bucket");
587
+ }
588
+ bucketAuthorities.add(bucket.senderAuthority);
589
+ }
590
+ return state;
591
+ }
592
+
593
+ export function defaultSessionMessageFabricPath(options = {}) {
594
+ return resolveTrustedSessionLifecycleScope(options).messageFabricStatePath;
595
+ }
596
+
597
+ export class SessionMessageFabric {
598
+ constructor(options = {}) {
599
+ const {
600
+ statePath: configuredStatePath,
601
+ launchEnv,
602
+ cwd,
603
+ now = () => Date.now(),
604
+ createId = randomUUID,
605
+ lock = withFileLock,
606
+ lockTimeoutMs = 10_000,
607
+ maxMessageBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
608
+ maxPendingPerRecipient = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
609
+ maxReceiptHistory = SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
610
+ maxMessagesPerSenderWindow = SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
611
+ senderRateWindowMs = SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
612
+ defaultTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.defaultTtlMs,
613
+ maxTtlMs = SESSION_MESSAGE_FABRIC_LIMITS.maxTtlMs,
614
+ } = options;
615
+ const usesDefaultStatePath = configuredStatePath === undefined;
616
+ const lifecycleScope = usesDefaultStatePath
617
+ ? resolveTrustedSessionLifecycleScope({ launchEnv, cwd })
618
+ : null;
619
+ const statePath = usesDefaultStatePath
620
+ ? lifecycleScope.messageFabricStatePath
621
+ : configuredStatePath;
622
+ this.statePath = path.resolve(statePath);
623
+ this._lifecycleScope = lifecycleScope;
624
+ this._secureDefaultDirectory =
625
+ usesDefaultStatePath && lifecycleScope.kind === "legacy";
626
+ this._now = now;
627
+ this._createId = createId;
628
+ this._lock = lock;
629
+ this._lockTimeoutMs = lockTimeoutMs;
630
+ this._limits = {
631
+ maxMessageBytes: positiveInteger(
632
+ maxMessageBytes,
633
+ SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
634
+ ),
635
+ maxPendingPerRecipient: positiveInteger(
636
+ maxPendingPerRecipient,
637
+ SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
638
+ ),
639
+ maxReceiptHistory: positiveInteger(
640
+ maxReceiptHistory,
641
+ SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
642
+ ),
643
+ maxMessagesPerSenderWindow: positiveInteger(
644
+ maxMessagesPerSenderWindow,
645
+ SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
646
+ ),
647
+ senderRateWindowMs: positiveInteger(
648
+ senderRateWindowMs,
649
+ SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
650
+ ),
651
+ defaultTtlMs: positiveInteger(
652
+ defaultTtlMs,
653
+ SESSION_MESSAGE_FABRIC_LIMITS.defaultTtlMs,
654
+ ),
655
+ maxTtlMs: positiveInteger(
656
+ maxTtlMs,
657
+ SESSION_MESSAGE_FABRIC_LIMITS.maxTtlMs,
658
+ ),
659
+ };
660
+ }
661
+
662
+ _ensureDirectory() {
663
+ const directory = path.dirname(this.statePath);
664
+ if (this._lifecycleScope?.kind === "project") {
665
+ ensureTrustedSessionLifecycleScope(this._lifecycleScope, {
666
+ extraDirectories: [
667
+ sessionLifecycleFabricDirectory(this._lifecycleScope),
668
+ ],
669
+ });
670
+ return;
671
+ }
672
+ if (this._secureDefaultDirectory) {
673
+ ensureDir(directory);
674
+ return;
675
+ }
676
+ fs.mkdirSync(directory, {
677
+ recursive: true,
678
+ mode: 0o700,
679
+ });
680
+ }
681
+
682
+ _readState() {
683
+ try {
684
+ return validateState(JSON.parse(fs.readFileSync(this.statePath, "utf8")));
685
+ } catch (cause) {
686
+ if (cause?.code === "ENOENT") return emptyState(this._now());
687
+ if (cause?.code === SESSION_MESSAGE_FABRIC_ERROR_CODES.STATE_CORRUPT) {
688
+ throw cause;
689
+ }
690
+ throw fabricError(
691
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.STATE_CORRUPT,
692
+ "Could not read session message state",
693
+ {},
694
+ cause,
695
+ );
696
+ }
697
+ }
698
+
699
+ _writeState(state) {
700
+ const directory = path.dirname(this.statePath);
701
+ const temporary = path.join(
702
+ directory,
703
+ `.${path.basename(this.statePath)}.${process.pid}.${this._createId()}.tmp`,
704
+ );
705
+ let descriptor = null;
706
+ try {
707
+ descriptor = fs.openSync(temporary, "wx", 0o600);
708
+ fs.writeFileSync(
709
+ descriptor,
710
+ `${JSON.stringify(state, null, 2)}\n`,
711
+ "utf8",
712
+ );
713
+ fs.fsyncSync(descriptor);
714
+ fs.closeSync(descriptor);
715
+ descriptor = null;
716
+ fs.renameSync(temporary, this.statePath);
717
+ if (process.platform !== "win32") {
718
+ const directoryDescriptor = fs.openSync(directory, "r");
719
+ try {
720
+ fs.fsyncSync(directoryDescriptor);
721
+ } finally {
722
+ fs.closeSync(directoryDescriptor);
723
+ }
724
+ }
725
+ } catch (cause) {
726
+ if (descriptor != null) {
727
+ try {
728
+ fs.closeSync(descriptor);
729
+ } catch {
730
+ // Preserve the original write failure.
731
+ }
732
+ }
733
+ try {
734
+ fs.unlinkSync(temporary);
735
+ } catch {
736
+ // The rename may already have committed, or no temporary was created.
737
+ }
738
+ throw fabricError(
739
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.STATE_UNAVAILABLE,
740
+ "Could not atomically persist session message state",
741
+ {},
742
+ cause,
743
+ );
744
+ }
745
+ }
746
+
747
+ _transaction(mutator, { expectedRevision = null } = {}) {
748
+ this._ensureDirectory();
749
+ try {
750
+ return this._lock(
751
+ this.statePath,
752
+ () => {
753
+ const state = this._readState();
754
+ if (
755
+ expectedRevision != null &&
756
+ Number(expectedRevision) !== state.revision
757
+ ) {
758
+ throw fabricError(
759
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.STALE_REVISION,
760
+ "Session message fabric revision changed",
761
+ { currentRevision: state.revision },
762
+ );
763
+ }
764
+ const now = this._now();
765
+ const swept = sweepExpired(state, now);
766
+ const prunedRateBuckets = pruneRateBuckets(
767
+ state,
768
+ now,
769
+ this._limits.senderRateWindowMs,
770
+ );
771
+ const outcome = mutator(state, now) || { value: undefined };
772
+ const compacted = compactSettledMessages(state);
773
+ const trimmed = trimReceiptHistory(
774
+ state,
775
+ this._limits.maxReceiptHistory,
776
+ );
777
+ if (
778
+ swept ||
779
+ prunedRateBuckets ||
780
+ compacted ||
781
+ trimmed ||
782
+ outcome.changed === true
783
+ ) {
784
+ state.revision += 1;
785
+ state.updatedAt = now;
786
+ state.digest = digestValue(stateWithoutDigest(state));
787
+ validateState(state);
788
+ this._writeState(state);
789
+ }
790
+ return outcome.value;
791
+ },
792
+ {
793
+ failIfUnavailable: true,
794
+ timeoutMs: this._lockTimeoutMs,
795
+ retryMs: 5,
796
+ maxRetryMs: 50,
797
+ retryJitterMs: 10,
798
+ yieldAfterReleaseMs: 1,
799
+ },
800
+ );
801
+ } catch (cause) {
802
+ if (cause?.code?.startsWith?.("SESSION_MESSAGE_")) throw cause;
803
+ throw fabricError(
804
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.STATE_UNAVAILABLE,
805
+ "Session message state transaction failed",
806
+ {},
807
+ cause,
808
+ );
809
+ }
810
+ }
811
+
812
+ register({
813
+ sessionId,
814
+ name,
815
+ machineId = "local",
816
+ policy = "accept",
817
+ idle = true,
818
+ } = {}) {
819
+ const safeSessionId = boundedId(sessionId, "sessionId");
820
+ const safeMachineId = boundedId(machineId, "machineId").toLowerCase();
821
+ const safeName = canonicalName(name || safeSessionId);
822
+ const safePolicy = policyValue(policy);
823
+ const id = authorityId(safeMachineId, safeSessionId);
824
+ return this._transaction((state, now) => {
825
+ const conflict = state.endpoints.find(
826
+ (endpoint) =>
827
+ endpoint.active &&
828
+ endpoint.name === safeName &&
829
+ endpoint.authorityId !== id,
830
+ );
831
+ if (conflict) {
832
+ throw fabricError(
833
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.NAME_CONFLICT,
834
+ `Session message name is already active: @${safeName}`,
835
+ );
836
+ }
837
+ let endpoint = state.endpoints.find((entry) => entry.authorityId === id);
838
+ if (endpoint) {
839
+ if (!endpoint.active) {
840
+ endpoint.epoch = this._createId();
841
+ endpoint.registeredAt = now;
842
+ }
843
+ endpoint.sessionId = safeSessionId;
844
+ endpoint.machineId = safeMachineId;
845
+ endpoint.name = safeName;
846
+ endpoint.policy = safePolicy;
847
+ endpoint.idle = idle === true;
848
+ endpoint.online = true;
849
+ endpoint.active = true;
850
+ endpoint.updatedAt = now;
851
+ } else {
852
+ endpoint = {
853
+ authorityId: id,
854
+ sessionId: safeSessionId,
855
+ machineId: safeMachineId,
856
+ name: safeName,
857
+ epoch: this._createId(),
858
+ policy: safePolicy,
859
+ idle: idle === true,
860
+ online: true,
861
+ active: true,
862
+ registeredAt: now,
863
+ updatedAt: now,
864
+ };
865
+ state.endpoints.push(endpoint);
866
+ }
867
+ for (const channel of state.channels.filter(
868
+ (entry) =>
869
+ entry.recipientAuthority === endpoint.authorityId &&
870
+ entry.recipientEpoch === endpoint.epoch,
871
+ )) {
872
+ promoteChannel(state, channel, now);
873
+ }
874
+ return {
875
+ changed: true,
876
+ value: { ...clone(endpoint), address: endpointAddress(endpoint) },
877
+ };
878
+ });
879
+ }
880
+
881
+ unregister(selector) {
882
+ return this._transaction((state, now) => {
883
+ const endpoint = requireEndpoint(state, selector);
884
+ endpoint.active = false;
885
+ endpoint.online = false;
886
+ endpoint.idle = false;
887
+ endpoint.updatedAt = now;
888
+ for (const message of pendingForRecipient(state, endpoint)) {
889
+ updateReceipt(state, message, "refused", "recipient_unregistered", now);
890
+ }
891
+ return { changed: true, value: true };
892
+ });
893
+ }
894
+
895
+ reconnect(selector) {
896
+ return this._transaction((state, now) => {
897
+ const endpoint = requireEndpoint(state, selector);
898
+ endpoint.online = true;
899
+ endpoint.updatedAt = now;
900
+ for (const channel of state.channels.filter(
901
+ (entry) =>
902
+ entry.recipientAuthority === endpoint.authorityId &&
903
+ entry.recipientEpoch === endpoint.epoch,
904
+ )) {
905
+ promoteChannel(state, channel, now);
906
+ }
907
+ return {
908
+ changed: true,
909
+ value: { ...clone(endpoint), address: endpointAddress(endpoint) },
910
+ };
911
+ });
912
+ }
913
+
914
+ disconnect(selector) {
915
+ return this._transaction((state, now) => {
916
+ const endpoint = requireEndpoint(state, selector);
917
+ endpoint.online = false;
918
+ endpoint.updatedAt = now;
919
+ return { changed: true, value: true };
920
+ });
921
+ }
922
+
923
+ setPolicy(selector, policy, options = {}) {
924
+ const safePolicy = policyValue(policy);
925
+ return this._transaction((state, now) => {
926
+ const endpoint = requireEndpoint(state, selector);
927
+ endpoint.policy = safePolicy;
928
+ endpoint.updatedAt = now;
929
+ for (const channel of state.channels.filter(
930
+ (entry) =>
931
+ entry.recipientAuthority === endpoint.authorityId &&
932
+ entry.recipientEpoch === endpoint.epoch,
933
+ )) {
934
+ promoteChannel(state, channel, now);
935
+ }
936
+ return {
937
+ changed: true,
938
+ value: { ...clone(endpoint), address: endpointAddress(endpoint) },
939
+ };
940
+ }, options);
941
+ }
942
+
943
+ setIdle(selector, idle, options = {}) {
944
+ return this._transaction((state, now) => {
945
+ const endpoint = requireEndpoint(state, selector);
946
+ const becameIdle = endpoint.idle !== true && idle === true;
947
+ endpoint.idle = idle === true;
948
+ endpoint.updatedAt = now;
949
+ let notifications = 0;
950
+ if (becameIdle) {
951
+ for (const message of state.messages) {
952
+ if (
953
+ message.recipientAuthority !== endpoint.authorityId ||
954
+ message.recipientEpoch !== endpoint.epoch ||
955
+ message.notifyWhenIdle !== true ||
956
+ message.status !== "delivered"
957
+ ) {
958
+ continue;
959
+ }
960
+ const receipt = receiptFor(
961
+ state,
962
+ message.senderAuthority,
963
+ message.messageId,
964
+ );
965
+ if (receipt && receipt.idleNotifiedAt == null) {
966
+ receipt.idleNotifiedAt = now;
967
+ receipt.updatedAt = now;
968
+ notifications += 1;
969
+ }
970
+ }
971
+ }
972
+ return {
973
+ changed: true,
974
+ value: { idle: endpoint.idle, notifications },
975
+ };
976
+ }, options);
977
+ }
978
+
979
+ send({
980
+ from,
981
+ to,
982
+ body,
983
+ subject = null,
984
+ messageId: requestedMessageId = null,
985
+ sequence = null,
986
+ ttlMs = this._limits.defaultTtlMs,
987
+ notifyWhenIdle = false,
988
+ } = {}) {
989
+ const id = messageId(requestedMessageId, this._createId);
990
+ const cleanSubject = subject == null ? null : String(subject).slice(0, 512);
991
+ const cleanBody = jsonBody(body);
992
+ const messageBytes = Buffer.byteLength(
993
+ JSON.stringify({ subject: cleanSubject, body: cleanBody.value }),
994
+ "utf8",
995
+ );
996
+ if (messageBytes > this._limits.maxMessageBytes) {
997
+ throw fabricError(
998
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_TOO_LARGE,
999
+ `Session message is ${messageBytes} bytes; limit is ${this._limits.maxMessageBytes}`,
1000
+ {
1001
+ messageBytes,
1002
+ maxMessageBytes: this._limits.maxMessageBytes,
1003
+ },
1004
+ );
1005
+ }
1006
+ const safeTtlMs = positiveInteger(
1007
+ ttlMs,
1008
+ this._limits.defaultTtlMs,
1009
+ this._limits.maxTtlMs,
1010
+ );
1011
+ const requestedSequence =
1012
+ sequence == null ? null : positiveInteger(sequence, null);
1013
+ if (sequence != null && requestedSequence == null) {
1014
+ throw fabricError(
1015
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
1016
+ "Session message sequence must be a positive integer",
1017
+ );
1018
+ }
1019
+ return this._transaction((state, now) => {
1020
+ const sender = requireEndpoint(state, from);
1021
+ const recipient = endpointFor(state, to);
1022
+ const payloadDigest = digestValue({
1023
+ body: cleanBody.value,
1024
+ subject: cleanSubject,
1025
+ to: String(to),
1026
+ });
1027
+ const duplicate = receiptFor(state, sender.authorityId, id);
1028
+ if (duplicate) {
1029
+ if (duplicate.payloadDigest !== payloadDigest) {
1030
+ throw fabricError(
1031
+ SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_ID_CONFLICT,
1032
+ "messageId was already used with a different payload",
1033
+ );
1034
+ }
1035
+ return { value: clone(duplicate) };
1036
+ }
1037
+ const rate = consumeSenderRate(
1038
+ state,
1039
+ sender.authorityId,
1040
+ now,
1041
+ this._limits.maxMessagesPerSenderWindow,
1042
+ this._limits.senderRateWindowMs,
1043
+ );
1044
+ if (!rate.allowed) {
1045
+ const receipt = {
1046
+ messageId: id,
1047
+ senderAuthority: sender.authorityId,
1048
+ recipientAuthority: recipient?.authorityId || null,
1049
+ recipientEpoch: recipient?.epoch || null,
1050
+ sequence: requestedSequence,
1051
+ payloadDigest,
1052
+ status: "rate_limited",
1053
+ reason: "sender_rate_limit",
1054
+ retryAfterMs: rate.retryAfterMs,
1055
+ createdAt: now,
1056
+ updatedAt: now,
1057
+ idleNotifiedAt: null,
1058
+ readAt: null,
1059
+ };
1060
+ state.receipts.push(receipt);
1061
+ return { changed: true, value: clone(receipt) };
1062
+ }
1063
+ if (!recipient) {
1064
+ const receipt = {
1065
+ messageId: id,
1066
+ senderAuthority: sender.authorityId,
1067
+ recipientAuthority: null,
1068
+ recipientEpoch: null,
1069
+ sequence: requestedSequence,
1070
+ payloadDigest,
1071
+ status: "refused",
1072
+ reason: "unknown_recipient",
1073
+ createdAt: now,
1074
+ updatedAt: now,
1075
+ idleNotifiedAt: null,
1076
+ readAt: null,
1077
+ };
1078
+ state.receipts.push(receipt);
1079
+ return { changed: true, value: clone(receipt) };
1080
+ }
1081
+ const channel = channelFor(state, sender, recipient);
1082
+ const pendingSequences = state.messages
1083
+ .filter(
1084
+ (message) =>
1085
+ message.channelId === channel.id &&
1086
+ PENDING_STATUSES.has(message.status),
1087
+ )
1088
+ .map((message) => message.sequence);
1089
+ const safeSequence =
1090
+ requestedSequence ??
1091
+ Math.max(
1092
+ channel.nextExpected,
1093
+ ...pendingSequences.map((value) => value + 1),
1094
+ );
1095
+ const message = {
1096
+ messageId: id,
1097
+ channelId: channel.id,
1098
+ senderAuthority: sender.authorityId,
1099
+ senderName: sender.name,
1100
+ recipientAuthority: recipient.authorityId,
1101
+ recipientName: recipient.name,
1102
+ recipientEpoch: recipient.epoch,
1103
+ sequence: safeSequence,
1104
+ subject: cleanSubject,
1105
+ body: cleanBody.value,
1106
+ messageBytes,
1107
+ payloadDigest,
1108
+ status: "held",
1109
+ reason: "out_of_order",
1110
+ notifyWhenIdle: notifyWhenIdle === true,
1111
+ createdAt: now,
1112
+ updatedAt: now,
1113
+ deliveredAt: null,
1114
+ acknowledgedAt: null,
1115
+ expiresAt: now + safeTtlMs,
1116
+ };
1117
+ if (
1118
+ safeSequence < channel.nextExpected ||
1119
+ state.messages.some(
1120
+ (entry) =>
1121
+ entry.channelId === channel.id &&
1122
+ entry.sequence === safeSequence &&
1123
+ PENDING_STATUSES.has(entry.status),
1124
+ )
1125
+ ) {
1126
+ updateReceipt(
1127
+ state,
1128
+ message,
1129
+ "refused",
1130
+ safeSequence < channel.nextExpected
1131
+ ? "stale_sequence"
1132
+ : "duplicate_sequence",
1133
+ now,
1134
+ );
1135
+ return {
1136
+ changed: true,
1137
+ value: clone(receiptFor(state, sender.authorityId, id)),
1138
+ };
1139
+ }
1140
+ if (
1141
+ recipient.policy !== "refuse" &&
1142
+ pendingForRecipient(state, recipient).length >=
1143
+ this._limits.maxPendingPerRecipient
1144
+ ) {
1145
+ updateReceipt(state, message, "full", "queue_capacity", now);
1146
+ return {
1147
+ changed: true,
1148
+ value: clone(receiptFor(state, sender.authorityId, id)),
1149
+ };
1150
+ }
1151
+ state.messages.push(message);
1152
+ updateReceipt(state, message, "held", "out_of_order", now);
1153
+ if (safeSequence === channel.nextExpected) {
1154
+ applyExpectedMessage(state, channel, message, recipient, now);
1155
+ promoteChannel(state, channel, now);
1156
+ }
1157
+ return {
1158
+ changed: true,
1159
+ value: clone(receiptFor(state, sender.authorityId, id)),
1160
+ };
1161
+ });
1162
+ }
1163
+
1164
+ inbox(selector, { acknowledge = false } = {}) {
1165
+ return this._transaction((state, now) => {
1166
+ const endpoint = requireEndpoint(state, selector);
1167
+ const messages = pendingForRecipient(state, endpoint)
1168
+ .filter((message) => message.status === "delivered")
1169
+ .sort(
1170
+ (left, right) =>
1171
+ (left.senderAuthority === right.senderAuthority
1172
+ ? left.sequence - right.sequence
1173
+ : left.createdAt - right.createdAt) ||
1174
+ left.senderAuthority.localeCompare(right.senderAuthority),
1175
+ );
1176
+ if (acknowledge) {
1177
+ for (const message of messages) {
1178
+ message.acknowledgedAt = now;
1179
+ const receipt = receiptFor(
1180
+ state,
1181
+ message.senderAuthority,
1182
+ message.messageId,
1183
+ );
1184
+ if (receipt) {
1185
+ receipt.readAt = now;
1186
+ receipt.updatedAt = now;
1187
+ }
1188
+ }
1189
+ }
1190
+ return {
1191
+ changed: acknowledge && messages.length > 0,
1192
+ value: clone(messages),
1193
+ };
1194
+ });
1195
+ }
1196
+
1197
+ receipts(selector) {
1198
+ return this._transaction((state) => {
1199
+ const endpoint = requireEndpoint(state, selector);
1200
+ return {
1201
+ value: clone(
1202
+ state.receipts
1203
+ .filter(
1204
+ (receipt) => receipt.senderAuthority === endpoint.authorityId,
1205
+ )
1206
+ .sort(
1207
+ (left, right) =>
1208
+ left.createdAt - right.createdAt ||
1209
+ left.messageId.localeCompare(right.messageId),
1210
+ ),
1211
+ ),
1212
+ };
1213
+ });
1214
+ }
1215
+
1216
+ projection() {
1217
+ return this._transaction((state) => {
1218
+ const endpoints = state.endpoints
1219
+ .filter((endpoint) => endpoint.active)
1220
+ .map((endpoint) => {
1221
+ const pending = pendingForRecipient(state, endpoint);
1222
+ return {
1223
+ authorityId: endpoint.authorityId,
1224
+ sessionId: endpoint.sessionId,
1225
+ machineId: endpoint.machineId,
1226
+ name: endpoint.name,
1227
+ address: endpointAddress(endpoint),
1228
+ epoch: endpoint.epoch,
1229
+ policy: endpoint.policy,
1230
+ online: endpoint.online,
1231
+ idle: endpoint.idle,
1232
+ unread: pending.filter((message) => message.status === "delivered")
1233
+ .length,
1234
+ held: pending.filter((message) => message.status === "held").length,
1235
+ updatedAt: endpoint.updatedAt,
1236
+ };
1237
+ })
1238
+ .sort((left, right) => left.name.localeCompare(right.name));
1239
+ return {
1240
+ value: {
1241
+ schema: SESSION_MESSAGE_FABRIC_PROJECTION_SCHEMA,
1242
+ version: 1,
1243
+ authority: "cli",
1244
+ revision: state.revision,
1245
+ digest: state.digest,
1246
+ limits: clone(this._limits),
1247
+ endpoints,
1248
+ },
1249
+ };
1250
+ });
1251
+ }
1252
+ }