chainlesschain 0.162.148 → 0.162.150

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 (322) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-CBLvrjeG.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-DYv2GtE0.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-CL6PZ-Ww.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-xuyHt4uA.js} +3 -3
  8. package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-DOvSVycq.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-DMhfTswr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-CQxDdm63.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-BEeNhK2a.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CL04O-ER.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-C50o-n7x.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-CH-bDkzs.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-DqAIMvSe.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-C43fo_my.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-BERnMrdP.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-Baw3w1gp.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-BEqAHF1-.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-CvnmKwK4.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-iMPxgVdt.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard--4SJX3YR.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BgLV6xgz.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-BbFfJ1Xi.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DeGq1B-q.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-L1PjW-lT.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-PK-X58to.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-BePWEH-d.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-BKkfk9hy.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-BNMqUDla.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-w4WPiyz2.js} +5 -5
  37. package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-DUKMVGNi.js} +2 -2
  38. package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-BWhiEyTJ.js} +1 -1
  39. package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-Ocf5JEkX.js} +1 -1
  40. package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-D2B6MMhU.js} +4 -4
  41. package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-DdYjCq1O.js} +4 -4
  42. package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-Cb1hfuUZ.js} +3 -3
  43. package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-CTeImGp7.js} +1 -1
  44. package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-BjYNc7eQ.js} +4 -4
  45. package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
  46. package/src/assets/web-panel/assets/{OrderTableRenderer-DIp8Xcbd.js → OrderTableRenderer-DVYYIizh.js} +1 -1
  47. package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-Be2Kmr5j.js} +4 -4
  48. package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow-Cu_-qRlR.js} +1 -1
  49. package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-DD2HoGzE.js} +2 -2
  50. package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
  51. package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-C9Srcs2M.js} +4 -4
  52. package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CUqcMgo1.js} +3 -3
  53. package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-HBrQGLXt.js} +1 -1
  54. package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-D-B0vjwh.js} +1 -1
  55. package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-eTeLIu9e.js} +2 -2
  56. package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-DrRqCsC6.js} +2 -2
  57. package/src/assets/web-panel/assets/{Projects-Dpid9CDg.js → Projects-1G9DNOhI.js} +1 -1
  58. package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-RkTZzpxP.js} +1 -1
  59. package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-BSCUgsgC.js} +1 -1
  60. package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CHgyM3Bz.js} +1 -1
  61. package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-0lry4OZq.js} +1 -1
  62. package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-Cn45UroZ.js} +2 -2
  63. package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-Q-Zjb707.js} +1 -1
  64. package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-BdM70oda.js} +1 -1
  65. package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-DmOAKKap.js} +2 -2
  66. package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-Dwavbm07.js} +1 -1
  67. package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DukXFCnk.js} +3 -3
  68. package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-BIlRnITu.js} +2 -2
  69. package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-YYVQdhhQ.js} +1 -1
  70. package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-B8_iYHz2.js} +1 -1
  71. package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-BdVrhT31.js} +1 -1
  72. package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-CD3ggRx7.js} +1 -1
  73. package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-Bp02VZFH.js} +2 -2
  74. package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-DEVmCEsr.js} +1 -1
  75. package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-B6czWc4N.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-BCJLv17r.js} +1 -1
  77. package/src/assets/web-panel/assets/Terminal-CD132d2Y.js +3 -0
  78. package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BnSY0S-s.js} +1 -1
  80. package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-VZw9Oy2w.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-pZq7Hjd2.js} +1 -1
  82. package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-DnQaqk4q.js} +1 -1
  83. package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-BEUfmvJV.js} +1 -1
  84. package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-CoZKMXJ2.js} +4 -4
  85. package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-CBOGVx1I.js} +5 -5
  86. package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-huSKn7TT.js} +1 -1
  87. package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-A_3w6FBO.js} +1 -1
  88. package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-D1Bem9Oy.js} +1 -1
  89. package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-CJYJj0oO.js} +1 -1
  90. package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-BkbFiKT4.js} +1 -1
  91. package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
  92. package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-CKvUafVY.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-AjrSPnvv.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-B8i0lViZ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-B9Svn0zc.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-BHdHSX06.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BHdailyo.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-BJwlEnYo.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-BOoAQjJz.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-BSgWxAOG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-BdutMsne.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-BiDWxzbn.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-By5a0T_W.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-C-WIY-FQ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-C37f6iey.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-C9bWmTcO.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-CBaosWRO.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-CBlBVJ_m.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-CD62EEGo.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-CUj-l7GM.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-CVS8Ymuq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-ChkXUj3f.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-Cj0OZ-37.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-Co3OFL0t.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-308gCGh7.js → index-Crp8CbRg.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-D22zNXiS.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-DTs_D1OL.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-DfgCdFrN.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-DjYrecNb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-DpW9cf3e.js} +1 -1
  121. package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
  122. package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-F--HuPG1.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-Fuk6t_3K.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-JEAGMii9.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-PF-mpv8K.js} +1 -1
  126. package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
  127. package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-b-sbVdjQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-pyvMVX8r.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DISWAYce.js → index-qNTtOVi_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-tgO8iza6.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CahryTX0.js → index-ttDQVhZy.js} +1 -1
  132. package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-DtrnfsZH.js} +1 -1
  133. package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D4zqSaX3.js} +1 -1
  134. package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-CGFGOUQj.js} +1 -1
  135. package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DH2HvkJl.js} +1 -1
  136. package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-jJ2at5HX.js} +1 -1
  137. package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-CRyMT1Fv.js} +1 -1
  138. package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-DZ_CX0Pt.js} +1 -1
  139. package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-wruHk-h5.js} +1 -1
  140. package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BGgx8q67.js} +1 -1
  141. package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-Do5M7gKm.js} +1 -1
  142. package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-CRa-GZpX.js} +1 -1
  143. package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-v5O2ZcJm.js} +1 -1
  145. package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub--z2FU0Px.js} +1 -1
  146. package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-1emidVKd.js} +1 -1
  147. package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-CcJaRyHs.js} +1 -1
  148. package/src/assets/web-panel/index.html +1 -1
  149. package/src/assets/web-panel/remote-session-sw.js +43 -0
  150. package/src/command-manifest.json +22 -1
  151. package/src/commands/a2a.js +19 -4
  152. package/src/commands/activitypub.js +20 -3
  153. package/src/commands/agent.js +71 -10
  154. package/src/commands/audit.js +40 -24
  155. package/src/commands/codeintel.js +320 -0
  156. package/src/commands/collab.js +15 -2
  157. package/src/commands/compliance.js +5 -0
  158. package/src/commands/config.js +4 -1
  159. package/src/commands/dao.js +86 -14
  160. package/src/commands/dev.js +2 -0
  161. package/src/commands/did.js +7 -1
  162. package/src/commands/dlp.js +23 -1
  163. package/src/commands/economy.js +29 -3
  164. package/src/commands/eval.js +255 -0
  165. package/src/commands/evomap.js +26 -0
  166. package/src/commands/governance.js +17 -3
  167. package/src/commands/hardening.js +18 -0
  168. package/src/commands/incentive.js +5 -0
  169. package/src/commands/init.js +46 -2
  170. package/src/commands/kg.js +4 -0
  171. package/src/commands/loop.js +6 -1
  172. package/src/commands/lowcode.js +15 -2
  173. package/src/commands/marketplace.js +40 -6
  174. package/src/commands/matrix.js +20 -1
  175. package/src/commands/memory.js +4 -1
  176. package/src/commands/mtc.js +7 -1
  177. package/src/commands/nostr.js +31 -4
  178. package/src/commands/note.js +7 -4
  179. package/src/commands/plugin.js +474 -0
  180. package/src/commands/pqc.js +5 -0
  181. package/src/commands/reputation.js +10 -1
  182. package/src/commands/scim.js +6 -0
  183. package/src/commands/session.js +32 -10
  184. package/src/commands/siem.js +11 -0
  185. package/src/commands/sla.js +18 -3
  186. package/src/commands/social.js +38 -5
  187. package/src/commands/sso.js +10 -2
  188. package/src/commands/stress.js +23 -4
  189. package/src/commands/team.js +463 -0
  190. package/src/commands/tech.js +2 -0
  191. package/src/commands/tenant.js +10 -1
  192. package/src/commands/terraform.js +6 -0
  193. package/src/commands/update.js +1 -1
  194. package/src/commands/zkp.js +20 -0
  195. package/src/gateways/ws/message-dispatcher.js +48 -2
  196. package/src/gateways/ws/remote-session-protocol.js +140 -42
  197. package/src/gateways/ws/ws-server.js +1 -1
  198. package/src/harness/jsonl-session-store.js +12 -5
  199. package/src/harness/plugin-manager.js +16 -4
  200. package/src/harness/prompt-compressor.js +27 -1
  201. package/src/harness/remote-command-ledger.js +189 -0
  202. package/src/harness/remote-session-push-apns.js +246 -0
  203. package/src/harness/remote-session-push-errors.js +15 -0
  204. package/src/harness/remote-session-push-fcm.js +9 -25
  205. package/src/harness/remote-session-push-huawei.js +174 -0
  206. package/src/harness/remote-session-push-oppo.js +171 -0
  207. package/src/harness/remote-session-push-senders.js +42 -0
  208. package/src/harness/remote-session-push-vivo.js +179 -0
  209. package/src/harness/remote-session-push-web.js +299 -0
  210. package/src/harness/remote-session-push-xiaomi.js +99 -0
  211. package/src/harness/worktree-isolator.js +16 -6
  212. package/src/index.js +6 -0
  213. package/src/lib/__tests__/logger.test.js +5 -2
  214. package/src/lib/a2a-protocol.js +25 -1
  215. package/src/lib/activitypub-bridge.js +61 -0
  216. package/src/lib/agent-core.js +4 -0
  217. package/src/lib/agent-economy.js +262 -29
  218. package/src/lib/agent-network.js +7 -1
  219. package/src/lib/agent-sandbox.js +161 -5
  220. package/src/lib/agent-team/task-lease.js +434 -0
  221. package/src/lib/agent-team/team-budget.js +165 -0
  222. package/src/lib/agent-team/team-mailbox.js +106 -0
  223. package/src/lib/agent-team/team-runner.js +282 -0
  224. package/src/lib/agent-team/team-worktree.js +236 -0
  225. package/src/lib/agents.js +18 -0
  226. package/src/lib/async-hook-supervisor.cjs +391 -0
  227. package/src/lib/audit-logger.js +7 -6
  228. package/src/lib/autonomous-developer.js +60 -0
  229. package/src/lib/chat-core.js +17 -4
  230. package/src/lib/collaboration-governance.js +56 -0
  231. package/src/lib/community-governance.js +68 -0
  232. package/src/lib/compliance-manager.js +63 -0
  233. package/src/lib/cross-chain.js +5 -0
  234. package/src/lib/dao-governance.js +151 -2
  235. package/src/lib/did-manager.js +23 -10
  236. package/src/lib/dlp-engine.js +70 -0
  237. package/src/lib/eval/runner.js +251 -0
  238. package/src/lib/eval/tasks.js +626 -0
  239. package/src/lib/eval/trend.js +200 -0
  240. package/src/lib/evolution-system.js +92 -0
  241. package/src/lib/evomap-federation.js +55 -0
  242. package/src/lib/evomap-governance.js +94 -0
  243. package/src/lib/fatal-handler.js +17 -1
  244. package/src/lib/hardening-manager.js +73 -0
  245. package/src/lib/hierarchical-memory.js +14 -8
  246. package/src/lib/knowledge-exporter.js +8 -2
  247. package/src/lib/knowledge-graph.js +79 -0
  248. package/src/lib/llm-providers.js +23 -14
  249. package/src/lib/logger.js +4 -1
  250. package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
  251. package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
  252. package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
  253. package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
  254. package/src/lib/lsp/__tests__/lsp-client.test.js +269 -0
  255. package/src/lib/lsp/__tests__/lsp-manager.test.js +333 -0
  256. package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
  257. package/src/lib/lsp/benchmark.js +257 -0
  258. package/src/lib/lsp/code-intelligence.js +356 -0
  259. package/src/lib/lsp/jsonrpc-stream.js +139 -0
  260. package/src/lib/lsp/lsp-client.js +339 -0
  261. package/src/lib/lsp/lsp-manager.js +375 -0
  262. package/src/lib/lsp/lsp-server-registry.js +196 -0
  263. package/src/lib/matrix-bridge.js +84 -0
  264. package/src/lib/memory-manager.js +5 -3
  265. package/src/lib/nostr-bridge.js +53 -0
  266. package/src/lib/permission-engine.js +22 -4
  267. package/src/lib/plugin-monitor-supervisor.js +238 -0
  268. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
  269. package/src/lib/plugin-runtime/agents.js +51 -0
  270. package/src/lib/plugin-runtime/bin.js +100 -0
  271. package/src/lib/plugin-runtime/hooks.js +100 -0
  272. package/src/lib/plugin-runtime/install.js +396 -0
  273. package/src/lib/plugin-runtime/lsp.js +75 -0
  274. package/src/lib/plugin-runtime/manifest.js +458 -0
  275. package/src/lib/plugin-runtime/mcp.js +89 -0
  276. package/src/lib/plugin-runtime/monitors.js +100 -0
  277. package/src/lib/plugin-runtime/policy.js +152 -0
  278. package/src/lib/plugin-runtime/reload.js +79 -0
  279. package/src/lib/plugin-runtime/remote-source.js +240 -0
  280. package/src/lib/plugin-runtime/scopes.js +197 -0
  281. package/src/lib/plugin-runtime/settings.js +119 -0
  282. package/src/lib/plugin-runtime/signature.js +107 -0
  283. package/src/lib/plugin-runtime/skills.js +43 -0
  284. package/src/lib/plugin-runtime/trust.js +140 -0
  285. package/src/lib/pqc-manager.js +64 -0
  286. package/src/lib/reputation-optimizer.js +101 -0
  287. package/src/lib/sandbox-egress-proxy.js +262 -0
  288. package/src/lib/sandbox-fs-policy.js +112 -0
  289. package/src/lib/sandbox-network-policy.js +151 -0
  290. package/src/lib/sandbox-v2.js +24 -18
  291. package/src/lib/scim-manager.js +36 -0
  292. package/src/lib/session-manager.js +5 -2
  293. package/src/lib/settings-hook-events.cjs +78 -6
  294. package/src/lib/settings-hooks.cjs +30 -3
  295. package/src/lib/settings-loader.cjs +35 -1
  296. package/src/lib/siem-exporter.js +45 -0
  297. package/src/lib/skill-loader.js +38 -2
  298. package/src/lib/skill-marketplace.js +83 -0
  299. package/src/lib/sla-manager.js +88 -0
  300. package/src/lib/social-manager.js +74 -0
  301. package/src/lib/stress-tester.js +65 -0
  302. package/src/lib/tech-learning-engine.js +75 -0
  303. package/src/lib/telemetry/span-recorder.js +237 -0
  304. package/src/lib/tenant-saas.js +107 -0
  305. package/src/lib/terraform-manager.js +67 -0
  306. package/src/lib/token-incentive.js +180 -29
  307. package/src/lib/wallet-manager.js +81 -35
  308. package/src/lib/zkp-engine.js +87 -0
  309. package/src/repl/agent-repl.js +417 -6
  310. package/src/runtime/__tests__/auto-pin.test.js +104 -0
  311. package/src/runtime/agent-core.js +771 -104
  312. package/src/runtime/auto-pin.js +117 -0
  313. package/src/runtime/coding-agent-contract-shared.cjs +82 -8
  314. package/src/runtime/coding-agent-policy.cjs +32 -7
  315. package/src/runtime/fallback-model.js +134 -7
  316. package/src/runtime/headless-runner.js +388 -108
  317. package/src/runtime/mcp-config.js +46 -0
  318. package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
  319. package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
  320. package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
  321. package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
  322. package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
@@ -0,0 +1,189 @@
1
+ /**
2
+ * RemoteCommandLedger (Phase 5, Remote Control) — application-level idempotency
3
+ * + total ordering + device revocation for commands arriving from remote
4
+ * endpoints (terminal / web / mobile).
5
+ *
6
+ * The transport layer (remote-session-crypto) already rejects a replayed or
7
+ * out-of-order ENVELOPE within one connection — but a reconnect starts a fresh
8
+ * crypto session with a reset sequence baseline, so a command the client
9
+ * re-sends after a dropped ACK would sail through and EXECUTE A SECOND TIME. That
10
+ * is exactly the Phase 5 acceptance this guards: "断网恢复后不会重复执行工具调用".
11
+ *
12
+ * Guarantees:
13
+ * - Idempotency: a command carries a stable `commandId`; the side effect runs
14
+ * AT MOST ONCE. A re-delivered command returns its cached result flagged
15
+ * `replayed` — it never re-executes.
16
+ * - Total order: every applied command gets a monotonic `applyIndex`, so all
17
+ * three endpoints agree on one order; `appliedSince(cursor)` lets a
18
+ * reconnecting endpoint catch up deterministically.
19
+ * - Revocation: a revoked device's commands are rejected (can't read/control).
20
+ *
21
+ * Pure + injectable clock → fully unit-testable without any transport/hardware.
22
+ */
23
+
24
+ export class RemoteCommandLedger {
25
+ constructor({ now = () => Date.now(), revokedDevices = [] } = {}) {
26
+ this._now = typeof now === "function" ? now : () => now;
27
+ this._applied = new Map(); // commandId → { applyIndex, result, deviceId, ts }
28
+ this._order = []; // applyIndex → commandId (total order)
29
+ this._revoked = new Set(revokedDevices);
30
+ this._perDeviceSeq = new Map(); // deviceId → highest accepted seq
31
+ this._inFlight = new Map(); // commandId → Promise<record> for an in-progress apply
32
+ }
33
+
34
+ isRevoked(deviceId) {
35
+ return this._revoked.has(deviceId);
36
+ }
37
+
38
+ revokeDevice(deviceId) {
39
+ this._revoked.add(deviceId);
40
+ }
41
+
42
+ /**
43
+ * Apply a command idempotently. `execute` (async, side-effecting) runs AT MOST
44
+ * ONCE per commandId; a re-delivery returns the cached result without calling
45
+ * it again.
46
+ *
47
+ * @param {{commandId:string, deviceId:string, seq?:number, payload?:any}} command
48
+ * @param {(command)=>Promise<any>|any} execute
49
+ * @returns {Promise<{status:"applied"|"replayed"|"rejected", result?, reason?, applyIndex?}>}
50
+ */
51
+ async apply(command, execute) {
52
+ if (
53
+ !command ||
54
+ typeof command.commandId !== "string" ||
55
+ !command.commandId
56
+ ) {
57
+ return { status: "rejected", reason: "missing commandId" };
58
+ }
59
+ const { commandId, deviceId } = command;
60
+
61
+ // A revoked device can neither read nor control the session.
62
+ if (deviceId != null && this._revoked.has(deviceId)) {
63
+ return { status: "rejected", reason: "device revoked" };
64
+ }
65
+
66
+ // Idempotent replay: already applied → return the cached result, DO NOT
67
+ // re-run the side effect. This is the reconnect-safety guarantee.
68
+ const prior = this._applied.get(commandId);
69
+ if (prior) {
70
+ return {
71
+ status: "replayed",
72
+ result: prior.result,
73
+ applyIndex: prior.applyIndex,
74
+ };
75
+ }
76
+
77
+ // Coalesce CONCURRENT deliveries of the same commandId. `execute` is awaited
78
+ // below, so without this a second delivery arriving while the first is still
79
+ // running would ALSO pass the `prior` check (nothing is recorded until
80
+ // execute resolves) and run the side effect a SECOND time — the exact
81
+ // double-exec this ledger exists to prevent on reconnect. An already-in-flight
82
+ // same-commandId delivery waits for it and returns its result as `replayed`.
83
+ const inflight = this._inFlight.get(commandId);
84
+ if (inflight) {
85
+ try {
86
+ const rec = await inflight;
87
+ return {
88
+ status: "replayed",
89
+ result: rec.result,
90
+ applyIndex: rec.applyIndex,
91
+ };
92
+ } catch {
93
+ // The in-flight execution failed → it recorded nothing and is retryable;
94
+ // report rejection to this caller too (it can retry the same commandId).
95
+ return { status: "rejected", reason: "concurrent execution failed" };
96
+ }
97
+ }
98
+
99
+ // Optional per-device monotonic sequence check: a STALE seq (below what we've
100
+ // already accepted from this device) with a NEW commandId is a protocol
101
+ // violation (a genuine reconnect resends the SAME commandId, caught above).
102
+ if (
103
+ deviceId != null &&
104
+ Number.isFinite(command.seq) &&
105
+ this._perDeviceSeq.has(deviceId) &&
106
+ command.seq <= this._perDeviceSeq.get(deviceId)
107
+ ) {
108
+ return { status: "rejected", reason: "stale sequence" };
109
+ }
110
+
111
+ // Reserve the commandId BEFORE the first await so a concurrent same-id
112
+ // delivery (above) sees it in flight. The promise records the applied result
113
+ // on success and rejects on failure (so a retry can re-run the side effect).
114
+ const runPromise = (async () => {
115
+ const result = await execute(command);
116
+ const applyIndex = this._order.length;
117
+ const rec = { applyIndex, result, deviceId, ts: this._now() };
118
+ this._applied.set(commandId, rec);
119
+ this._order.push(commandId);
120
+ if (deviceId != null && Number.isFinite(command.seq)) {
121
+ this._perDeviceSeq.set(deviceId, command.seq);
122
+ }
123
+ return rec;
124
+ })();
125
+ this._inFlight.set(commandId, runPromise);
126
+ try {
127
+ const rec = await runPromise;
128
+ return {
129
+ status: "applied",
130
+ result: rec.result,
131
+ applyIndex: rec.applyIndex,
132
+ };
133
+ } catch (err) {
134
+ // A failed execution is NOT recorded as applied — the command may be
135
+ // retried (with the same commandId) and will run again. Surfacing the
136
+ // error lets the caller decide; idempotency only protects SUCCESSES.
137
+ return { status: "rejected", reason: `execute failed: ${err.message}` };
138
+ } finally {
139
+ this._inFlight.delete(commandId);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * The ordered tail of applied commands after `cursor` (an applyIndex), so a
145
+ * reconnecting endpoint can replay missed state in the one agreed order.
146
+ * @returns {Array<{applyIndex, commandId, result, deviceId}>}
147
+ */
148
+ appliedSince(cursor = -1) {
149
+ const out = [];
150
+ for (let i = cursor + 1; i < this._order.length; i++) {
151
+ const commandId = this._order[i];
152
+ const rec = this._applied.get(commandId);
153
+ out.push({
154
+ applyIndex: i,
155
+ commandId,
156
+ result: rec.result,
157
+ deviceId: rec.deviceId,
158
+ });
159
+ }
160
+ return out;
161
+ }
162
+
163
+ /** Highest assigned applyIndex (−1 when nothing applied). */
164
+ cursor() {
165
+ return this._order.length - 1;
166
+ }
167
+
168
+ appliedCount() {
169
+ return this._order.length;
170
+ }
171
+
172
+ snapshot() {
173
+ return {
174
+ order: [...this._order],
175
+ applied: Array.from(this._applied.entries()),
176
+ revoked: Array.from(this._revoked),
177
+ perDeviceSeq: Array.from(this._perDeviceSeq.entries()),
178
+ };
179
+ }
180
+
181
+ static restore(snap, { now = () => Date.now() } = {}) {
182
+ const l = new RemoteCommandLedger({ now });
183
+ l._order = [...(snap?.order || [])];
184
+ l._applied = new Map(snap?.applied || []);
185
+ l._revoked = new Set(snap?.revoked || []);
186
+ l._perDeviceSeq = new Map(snap?.perDeviceSeq || []);
187
+ return l;
188
+ }
189
+ }
@@ -0,0 +1,246 @@
1
+ // Remote Session vendor push — Apple Push Notification service (APNs) sender.
2
+ //
3
+ // A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
4
+ // for iOS/macOS devices, using APNs' HTTP/2 provider API with token-based
5
+ // (JWT ES256) auth: a short-lived bearer signed with the .p8 auth key
6
+ // (key id + team id). APNs mandates HTTP/2, so the network transport is an
7
+ // injectable `request` function (default: node:http2) — everything else is
8
+ // injectable too (clock, signer), so the whole chain is unit-testable with a
9
+ // generated EC keypair and a fake transport, no real credentials or network.
10
+ // The payload carries NO session content, only routing ids.
11
+
12
+ import { sign as cryptoSign } from "node:crypto";
13
+ import http2 from "node:http2";
14
+ import fs from "node:fs";
15
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
16
+
17
+ const PROD_HOST = "https://api.push.apple.com";
18
+ const SANDBOX_HOST = "https://api.sandbox.push.apple.com";
19
+ // APNs auth tokens are valid up to 60 min; refresh well inside that window.
20
+ const JWT_TTL_MS = 50 * 60 * 1000;
21
+ const JWT_SKEW_MS = 60 * 1000;
22
+
23
+ function base64url(input) {
24
+ return Buffer.from(input)
25
+ .toString("base64")
26
+ .replace(/\+/g, "-")
27
+ .replace(/\//g, "_")
28
+ .replace(/=+$/, "");
29
+ }
30
+
31
+ function dropUndefined(obj) {
32
+ const out = {};
33
+ for (const [key, value] of Object.entries(obj)) {
34
+ if (value !== undefined) out[key] = value;
35
+ }
36
+ return out;
37
+ }
38
+
39
+ /** Default HTTP/2 transport for APNs. Resolves `{ status, headers, body }`. */
40
+ function http2Request({ url, headers, body }) {
41
+ return new Promise((resolve, reject) => {
42
+ const target = new URL(url);
43
+ const client = http2.connect(`${target.protocol}//${target.host}`);
44
+ client.on("error", reject);
45
+ const req = client.request({
46
+ ":method": "POST",
47
+ ":path": `${target.pathname}${target.search}`,
48
+ ...headers,
49
+ });
50
+ let status = 0;
51
+ let responseHeaders = {};
52
+ let data = "";
53
+ req.on("response", (h) => {
54
+ status = Number(h[":status"]) || 0;
55
+ responseHeaders = h;
56
+ });
57
+ req.setEncoding("utf8");
58
+ req.on("data", (chunk) => {
59
+ data += chunk;
60
+ });
61
+ req.on("end", () => {
62
+ client.close();
63
+ resolve({ status, headers: responseHeaders, body: data });
64
+ });
65
+ req.on("error", (error) => {
66
+ client.close();
67
+ reject(error);
68
+ });
69
+ req.end(body);
70
+ });
71
+ }
72
+
73
+ /** Mints + caches the ES256 provider JWT from a .p8 auth key. */
74
+ export class ApnsTokenProvider {
75
+ constructor(options = {}) {
76
+ if (!options.keyId) throw new Error("ApnsTokenProvider requires a keyId");
77
+ if (!options.teamId) throw new Error("ApnsTokenProvider requires a teamId");
78
+ if (!options.privateKey) {
79
+ throw new Error("ApnsTokenProvider requires a privateKey (.p8 contents)");
80
+ }
81
+ this.keyId = options.keyId;
82
+ this.teamId = options.teamId;
83
+ this.privateKey = options.privateKey;
84
+ this.now = options.now || Date.now;
85
+ // ES256 = ECDSA P-256 / SHA-256 with a raw R||S signature (JOSE form).
86
+ this.sign =
87
+ options.sign ||
88
+ ((input, key) =>
89
+ cryptoSign("sha256", Buffer.from(input), {
90
+ key,
91
+ dsaEncoding: "ieee-p1363",
92
+ }));
93
+ this._cached = null; // { token, expiresAt }
94
+ }
95
+
96
+ getToken() {
97
+ const now = this.now();
98
+ if (this._cached && this._cached.expiresAt - JWT_SKEW_MS > now) {
99
+ return this._cached.token;
100
+ }
101
+ const header = base64url(
102
+ JSON.stringify({ alg: "ES256", kid: this.keyId, typ: "JWT" }),
103
+ );
104
+ const payload = base64url(
105
+ JSON.stringify({ iss: this.teamId, iat: Math.floor(now / 1000) }),
106
+ );
107
+ const signingInput = `${header}.${payload}`;
108
+ const signature = base64url(this.sign(signingInput, this.privateKey));
109
+ const token = `${signingInput}.${signature}`;
110
+ this._cached = { token, expiresAt: now + JWT_TTL_MS };
111
+ return token;
112
+ }
113
+ }
114
+
115
+ /** Sends one high-priority alert to a device token via APNs HTTP/2. */
116
+ export class ApnsPushSender {
117
+ constructor(options = {}) {
118
+ if (!options.tokenProvider) {
119
+ throw new Error("ApnsPushSender requires a tokenProvider");
120
+ }
121
+ if (!options.topic) {
122
+ throw new Error("ApnsPushSender requires a topic (app bundle id)");
123
+ }
124
+ this.tokenProvider = options.tokenProvider;
125
+ this.topic = options.topic;
126
+ this.host = options.host || (options.production ? PROD_HOST : SANDBOX_HOST);
127
+ this.request = options.request || http2Request;
128
+ }
129
+
130
+ /** Bound function matching the dispatcher's `sender` contract. */
131
+ get handler() {
132
+ return (payload) => this.send(payload);
133
+ }
134
+
135
+ async send({ token, notification, sessionId, clientId } = {}) {
136
+ if (!token) throw new Error("APNs send requires a device token");
137
+ const jwt = this.tokenProvider.getToken();
138
+ const payload = {
139
+ aps: dropUndefined({
140
+ alert: notification
141
+ ? dropUndefined({
142
+ title: notification.title,
143
+ body: notification.body,
144
+ })
145
+ : undefined,
146
+ sound: "default",
147
+ }),
148
+ // Routing ids only; no session content.
149
+ "remote-session": dropUndefined({
150
+ type: "approval-request",
151
+ sessionId: sessionId != null ? String(sessionId) : undefined,
152
+ clientId: clientId != null ? String(clientId) : undefined,
153
+ }),
154
+ };
155
+ const res = await this.request({
156
+ url: `${this.host}/3/device/${token}`,
157
+ headers: {
158
+ authorization: `bearer ${jwt}`,
159
+ "apns-topic": this.topic,
160
+ "apns-push-type": "alert",
161
+ "apns-priority": "10",
162
+ },
163
+ body: JSON.stringify(payload),
164
+ });
165
+ if (res.status === 200) {
166
+ return { id: res.headers?.["apns-id"] || null };
167
+ }
168
+ const reason = parseReason(res.body);
169
+ // A retired/invalid device token: 410 Unregistered or 400 BadDeviceToken —
170
+ // prune it rather than retry forever.
171
+ if (
172
+ res.status === 410 ||
173
+ reason === "Unregistered" ||
174
+ reason === "BadDeviceToken"
175
+ ) {
176
+ throw new PushTokenUnregisteredError(
177
+ `APNs token unregistered: ${reason || res.status}`,
178
+ );
179
+ }
180
+ throw new Error(`APNs send failed (${res.status}): ${reason || res.body}`);
181
+ }
182
+ }
183
+
184
+ function parseReason(body) {
185
+ if (!body) return null;
186
+ try {
187
+ return JSON.parse(body).reason || null;
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ function loadPrivateKey(env, options) {
194
+ if (options.privateKey) return options.privateKey;
195
+ const inline = env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY_P8;
196
+ if (inline) return inline;
197
+ const filePath = env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY;
198
+ if (filePath) {
199
+ const fsMod = options.fs || fs;
200
+ try {
201
+ return fsMod.readFileSync(filePath, "utf8");
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+
209
+ /**
210
+ * Build a bound APNs `sender` from env, or null when APNs is not configured.
211
+ *
212
+ * APNs env:
213
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY (path to .p8), or
214
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY_P8 (inline .p8 PEM)
215
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY_ID
216
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_TEAM_ID
217
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_TOPIC (app bundle id)
218
+ * CHAINLESSCHAIN_REMOTE_SESSION_APNS_PRODUCTION ("true" → prod host)
219
+ */
220
+ export function createApnsPushSender(env = {}, options = {}) {
221
+ const privateKey = loadPrivateKey(env, options);
222
+ const keyId = options.keyId || env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_KEY_ID;
223
+ const teamId =
224
+ options.teamId || env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_TEAM_ID;
225
+ const topic = options.topic || env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_TOPIC;
226
+ if (!privateKey || !keyId || !teamId || !topic) return null;
227
+ let tokenProvider;
228
+ try {
229
+ tokenProvider =
230
+ options.tokenProvider ||
231
+ new ApnsTokenProvider({ keyId, teamId, privateKey, ...options });
232
+ } catch {
233
+ return null; // Malformed credentials — stay disabled rather than crash.
234
+ }
235
+ const production =
236
+ options.production !== undefined
237
+ ? options.production
238
+ : String(env.CHAINLESSCHAIN_REMOTE_SESSION_APNS_PRODUCTION) === "true";
239
+ const sender = new ApnsPushSender({
240
+ tokenProvider,
241
+ topic,
242
+ production,
243
+ ...options,
244
+ });
245
+ return sender.handler;
246
+ }
@@ -0,0 +1,15 @@
1
+ // Shared error types for Remote Session vendor push senders.
2
+
3
+ /**
4
+ * A device token the vendor reports as no longer valid (uninstalled app, token
5
+ * rotated). The caller should prune it so it is never retried. Raised by every
6
+ * provider (FCM UNREGISTERED, APNs 410/BadDeviceToken, …) so the dispatcher and
7
+ * ws-server pruning logic can key off a single `code`.
8
+ */
9
+ export class PushTokenUnregisteredError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "PushTokenUnregisteredError";
13
+ this.code = "PUSH_TOKEN_UNREGISTERED";
14
+ }
15
+ }
@@ -14,6 +14,11 @@
14
14
 
15
15
  import { createSign } from "node:crypto";
16
16
  import fs from "node:fs";
17
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
18
+
19
+ // Re-exported for existing importers; canonical definition lives in the shared
20
+ // errors module so every provider raises the same type.
21
+ export { PushTokenUnregisteredError };
17
22
 
18
23
  const FCM_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
19
24
  const DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token";
@@ -45,18 +50,6 @@ function dropUndefined(obj) {
45
50
  return out;
46
51
  }
47
52
 
48
- /**
49
- * A device token the vendor reports as no longer valid (uninstalled app, token
50
- * rotated). The caller should prune it so it is never retried.
51
- */
52
- export class PushTokenUnregisteredError extends Error {
53
- constructor(message) {
54
- super(message);
55
- this.name = "PushTokenUnregisteredError";
56
- this.code = "PUSH_TOKEN_UNREGISTERED";
57
- }
58
- }
59
-
60
53
  /** Mints + caches OAuth2 access tokens from a Google service account. */
61
54
  export class GoogleServiceAccountTokenProvider {
62
55
  constructor(serviceAccount = {}, options = {}) {
@@ -215,26 +208,17 @@ function loadServiceAccount(env, options) {
215
208
  }
216
209
 
217
210
  /**
218
- * Build a concrete push `sender` from env, or null when unconfigured / for a
219
- * provider that is not yet implemented. Returns the bound sender function the
220
- * RemoteSessionPushDispatcher expects.
211
+ * Build a bound FCM `sender` from env, or null when FCM is not configured. The
212
+ * provider dispatch (fcm vs apns vs …) is the caller's job see
213
+ * remote-session-push-senders.js.
221
214
  *
222
215
  * FCM env:
223
- * CHAINLESSCHAIN_REMOTE_SESSION_PUSH_PROVIDER = "fcm" (default)
224
216
  * CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT (path to JSON), or
225
217
  * CHAINLESSCHAIN_REMOTE_SESSION_FCM_SERVICE_ACCOUNT_JSON (inline JSON)
226
218
  * CHAINLESSCHAIN_REMOTE_SESSION_FCM_PROJECT_ID (optional; else service
227
219
  * account project_id)
228
220
  */
229
- export function createRemoteSessionPushSender(env = {}, options = {}) {
230
- const provider = (
231
- env.CHAINLESSCHAIN_REMOTE_SESSION_PUSH_PROVIDER || "fcm"
232
- ).toLowerCase();
233
- if (provider !== "fcm") {
234
- // APNs / HMS / Xiaomi / OPPO senders are not implemented yet; the injection
235
- // seam means they can be added without touching the dispatcher.
236
- return null;
237
- }
221
+ export function createFcmPushSender(env = {}, options = {}) {
238
222
  const serviceAccount = loadServiceAccount(env, options);
239
223
  if (!serviceAccount) return null;
240
224
  const projectId =
@@ -0,0 +1,174 @@
1
+ // Remote Session vendor push — Huawei (HMS Push Kit) sender.
2
+ //
3
+ // A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
4
+ // for Huawei/HarmonyOS devices via HMS Push Kit. Auth is OAuth2 client
5
+ // credentials (app id + secret) exchanged for a short-lived access token —
6
+ // simpler than FCM's signed JWT — then a Bearer POST to messages:send. Plain
7
+ // HTTPS with an injectable `fetch` (default global), fully unit-testable with a
8
+ // fake transport. The payload carries only routing ids (no session content).
9
+
10
+ import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
11
+
12
+ const DEFAULT_TOKEN_URI =
13
+ "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
14
+ const DEFAULT_PUSH_HOST = "https://push-api.cloud.huawei.com";
15
+ const ACCESS_TOKEN_SKEW_MS = 60_000;
16
+ // HMS success + invalid-token result codes (single-token send).
17
+ const CODE_SUCCESS = "80000000";
18
+ const CODE_ALL_TOKENS_INVALID = "80300007";
19
+ const CODE_SOME_TOKENS_INVALID = "80100000";
20
+ const INVALID_TOKEN_RE = /invalid.*token|token.*invalid|unregist/i;
21
+
22
+ async function safeText(res) {
23
+ try {
24
+ return await res.text();
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ function dropUndefined(obj) {
31
+ const out = {};
32
+ for (const [key, value] of Object.entries(obj)) {
33
+ if (value !== undefined) out[key] = value;
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /** Mints + caches an OAuth2 access token from HMS client credentials. */
39
+ export class HuaweiTokenProvider {
40
+ constructor(options = {}) {
41
+ if (!options.appId)
42
+ throw new Error("HuaweiTokenProvider requires an appId");
43
+ if (!options.appSecret) {
44
+ throw new Error("HuaweiTokenProvider requires an appSecret");
45
+ }
46
+ this.appId = options.appId;
47
+ this.appSecret = options.appSecret;
48
+ this.tokenUri = options.tokenUri || DEFAULT_TOKEN_URI;
49
+ this.now = options.now || Date.now;
50
+ this.fetch = options.fetch || globalThis.fetch;
51
+ this._cached = null; // { token, expiresAt }
52
+ }
53
+
54
+ async getAccessToken() {
55
+ const now = this.now();
56
+ if (this._cached && this._cached.expiresAt - ACCESS_TOKEN_SKEW_MS > now) {
57
+ return this._cached.token;
58
+ }
59
+ const body = new URLSearchParams({
60
+ grant_type: "client_credentials",
61
+ client_id: this.appId,
62
+ client_secret: this.appSecret,
63
+ });
64
+ const res = await this.fetch(this.tokenUri, {
65
+ method: "POST",
66
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
67
+ body: body.toString(),
68
+ });
69
+ if (!res.ok) {
70
+ throw new Error(
71
+ `Huawei OAuth failed (${res.status}): ${await safeText(res)}`,
72
+ );
73
+ }
74
+ const json = await res.json();
75
+ if (!json.access_token) {
76
+ throw new Error("Huawei OAuth response missing access_token");
77
+ }
78
+ const ttl = (Number(json.expires_in) || 3600) * 1000;
79
+ this._cached = { token: json.access_token, expiresAt: now + ttl };
80
+ return this._cached.token;
81
+ }
82
+ }
83
+
84
+ /** Sends one notification to an HMS device token. */
85
+ export class HuaweiPushSender {
86
+ constructor(options = {}) {
87
+ if (!options.appId) throw new Error("HuaweiPushSender requires an appId");
88
+ if (!options.tokenProvider) {
89
+ throw new Error("HuaweiPushSender requires a tokenProvider");
90
+ }
91
+ this.appId = options.appId;
92
+ this.tokenProvider = options.tokenProvider;
93
+ this.fetch = options.fetch || globalThis.fetch;
94
+ this.endpoint =
95
+ options.endpoint ||
96
+ `${options.host || DEFAULT_PUSH_HOST}/v1/${options.appId}/messages:send`;
97
+ }
98
+
99
+ /** Bound function matching the dispatcher's `sender` contract. */
100
+ get handler() {
101
+ return (payload) => this.send(payload);
102
+ }
103
+
104
+ async send({ token, notification, sessionId, clientId } = {}) {
105
+ if (!token) throw new Error("Huawei send requires a device token");
106
+ const accessToken = await this.tokenProvider.getAccessToken();
107
+ const data = { type: "remote-session.approval-request" };
108
+ if (sessionId != null) data.sessionId = String(sessionId);
109
+ if (clientId != null) data.clientId = String(clientId);
110
+ const message = {
111
+ message: dropUndefined({
112
+ notification: notification
113
+ ? dropUndefined({
114
+ title: notification.title,
115
+ body: notification.body,
116
+ })
117
+ : undefined,
118
+ android: { urgency: "HIGH" },
119
+ data: JSON.stringify(data), // routing ids only, no session content
120
+ token: [token],
121
+ }),
122
+ };
123
+ const res = await this.fetch(this.endpoint, {
124
+ method: "POST",
125
+ headers: {
126
+ Authorization: `Bearer ${accessToken}`,
127
+ "Content-Type": "application/json",
128
+ },
129
+ body: JSON.stringify(message),
130
+ });
131
+ const json = await res.json().catch(() => ({}));
132
+ if (res.ok && json.code === CODE_SUCCESS) {
133
+ return { id: json.requestId || null };
134
+ }
135
+ const code = json.code || `HTTP ${res.status}`;
136
+ const msg = json.msg || "";
137
+ // A single-token send that reports invalid/all-invalid tokens means this
138
+ // token is dead — prune it rather than retry.
139
+ if (
140
+ code === CODE_ALL_TOKENS_INVALID ||
141
+ code === CODE_SOME_TOKENS_INVALID ||
142
+ INVALID_TOKEN_RE.test(String(msg))
143
+ ) {
144
+ throw new PushTokenUnregisteredError(
145
+ `Huawei token invalid (${code}): ${msg}`.trim(),
146
+ );
147
+ }
148
+ throw new Error(`Huawei push failed (${code}): ${msg}`.trim());
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Build a bound Huawei `sender` from env, or null when unconfigured.
154
+ *
155
+ * Huawei env:
156
+ * CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_ID
157
+ * CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_SECRET
158
+ */
159
+ export function createHuaweiPushSender(env = {}, options = {}) {
160
+ const appId =
161
+ options.appId || env.CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_ID;
162
+ const appSecret =
163
+ options.appSecret || env.CHAINLESSCHAIN_REMOTE_SESSION_HUAWEI_APP_SECRET;
164
+ if (!appId || !appSecret) return null;
165
+ let tokenProvider;
166
+ try {
167
+ tokenProvider =
168
+ options.tokenProvider ||
169
+ new HuaweiTokenProvider({ appId, appSecret, ...options });
170
+ } catch {
171
+ return null; // Malformed credentials — stay disabled rather than crash.
172
+ }
173
+ return new HuaweiPushSender({ appId, tokenProvider, ...options }).handler;
174
+ }