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
@@ -966,7 +966,13 @@ export function updateReputation(
966
966
  throw new Error(
967
967
  `Invalid dimension: ${dimension}. Expected one of ${REPUTATION_DIMENSIONS.join(",")}`,
968
968
  );
969
- const s = Math.max(MIN_REPUTATION, Math.min(MAX_REPUTATION, Number(score)));
969
+ // Reject non-finite scores: Math.max/min pass NaN straight through, so a
970
+ // non-numeric CLI arg (Number("abc") → NaN) would persist NaN (→ NULL) and
971
+ // corrupt every getReputation average + routeTask agent scoring.
972
+ const numericScore = Number(score);
973
+ if (!Number.isFinite(numericScore))
974
+ throw new Error("score must be a finite number");
975
+ const s = Math.max(MIN_REPUTATION, Math.min(MAX_REPUTATION, numericScore));
970
976
  const id = `rep:${_uuid()}`;
971
977
  const now = _now();
972
978
  db.prepare(
@@ -1,29 +1,107 @@
1
1
  /** OS-isolated shell execution for the coding agent. */
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
+ import { proxyEnv } from "./sandbox-egress-proxy.js";
4
5
 
5
6
  export const DEFAULT_SANDBOX_IMAGE = "node:22-bookworm-slim";
6
7
  export const _deps = { spawnSync };
7
8
 
9
+ function stringList(value) {
10
+ if (!Array.isArray(value)) return [];
11
+ return [
12
+ ...new Set(
13
+ value.map((entry) => String(entry || "").trim()).filter(Boolean),
14
+ ),
15
+ ];
16
+ }
17
+
18
+ function resolvePolicyPaths(entries, cwd) {
19
+ return stringList(entries).map((entry) => {
20
+ if (entry.startsWith("~/")) {
21
+ return path.resolve(
22
+ process.env.HOME || process.env.USERPROFILE || "",
23
+ entry.slice(2),
24
+ );
25
+ }
26
+ return path.resolve(cwd, entry);
27
+ });
28
+ }
29
+
30
+ export function normalizeSandboxPolicy(settings = {}, cwd = process.cwd()) {
31
+ const filesystem = settings.filesystem || {};
32
+ const network = settings.network || {};
33
+ return {
34
+ allowRead: resolvePolicyPaths(filesystem.allowRead, cwd),
35
+ denyRead: resolvePolicyPaths(filesystem.denyRead, cwd),
36
+ allowWrite: resolvePolicyPaths(filesystem.allowWrite, cwd),
37
+ denyWrite: resolvePolicyPaths(filesystem.denyWrite, cwd),
38
+ allowedDomains: stringList(network.allowedDomains),
39
+ deniedDomains: stringList(network.deniedDomains),
40
+ excludedCommands: stringList(settings.excludedCommands),
41
+ allowUnsandboxedCommands: settings.allowUnsandboxedCommands !== false,
42
+ failIfUnavailable: settings.failIfUnavailable === true,
43
+ };
44
+ }
45
+
8
46
  export function normalizeAgentSandbox(value, options = {}) {
9
- if (!value) return null;
47
+ const settings = options.settings || {};
48
+ if (!value && settings.enabled !== true) return null;
49
+ if (value === false || settings.enabled === false) return null;
10
50
  const image =
11
51
  typeof value === "string" && value.trim() && value !== "true"
12
52
  ? value.trim()
13
53
  : DEFAULT_SANDBOX_IMAGE;
14
54
  return {
15
- engine: "docker",
16
- image,
55
+ engine: settings.engine || "docker",
56
+ image: settings.image || image,
17
57
  cwd: path.resolve(options.cwd || process.cwd()),
18
- network: options.network === true,
58
+ network: options.network === true || settings.network === true,
59
+ policy: normalizeSandboxPolicy(settings, options.cwd || process.cwd()),
19
60
  };
20
61
  }
21
62
 
22
63
  export function executeSandboxedShell(command, sandbox, options = {}) {
23
- if (!sandbox || sandbox.engine !== "docker") {
64
+ if (!sandbox || !["docker", "bubblewrap"].includes(sandbox.engine)) {
24
65
  throw new Error("A supported agent sandbox configuration is required");
25
66
  }
26
67
  const hostCwd = path.resolve(options.cwd || sandbox.cwd);
68
+ const policy = sandbox.policy || normalizeSandboxPolicy({}, hostCwd);
69
+ // An egress proxy (see sandbox-egress-proxy.js) ENFORCES the domain allow/deny
70
+ // policy: the caller starts it and passes `{ env }` here, and the sandboxed
71
+ // process routes its HTTP(S) traffic through it. Without a proxy, a
72
+ // domain-restricted network request has no enforcement point, so we refuse
73
+ // rather than grant unrestricted access.
74
+ const egress = options.egressProxy || null;
75
+ if (
76
+ (policy.allowedDomains.length || policy.deniedDomains.length) &&
77
+ sandbox.network &&
78
+ !egress
79
+ ) {
80
+ return {
81
+ stdout: "",
82
+ stderr:
83
+ "Domain-restricted sandbox networking requires a configured sandbox proxy; refusing unrestricted network access",
84
+ exitCode: 1,
85
+ failedToStart: true,
86
+ };
87
+ }
88
+ if (sandbox.engine === "bubblewrap") {
89
+ return executeBubblewrapShell(command, sandbox, options, hostCwd, policy);
90
+ }
91
+ if (
92
+ policy.allowRead.length ||
93
+ policy.denyRead.length ||
94
+ policy.allowWrite.length ||
95
+ policy.denyWrite.length
96
+ ) {
97
+ return {
98
+ stdout: "",
99
+ stderr:
100
+ "The Docker sandbox backend cannot enforce fine-grained filesystem policy; use engine=bubblewrap",
101
+ exitCode: 1,
102
+ failedToStart: true,
103
+ };
104
+ }
27
105
  const args = ["run", "--rm", "--init"];
28
106
  if (!sandbox.network) args.push("--network", "none");
29
107
  args.push("--mount", `type=bind,source=${hostCwd},target=/workspace`);
@@ -31,6 +109,17 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
31
109
  if (process.platform !== "win32" && process.getuid && process.getgid) {
32
110
  args.push("--user", `${process.getuid()}:${process.getgid()}`);
33
111
  }
112
+ // Route egress through the host proxy so the domain policy is enforced. The
113
+ // container reaches the host proxy via host.docker.internal (mapped to the
114
+ // gateway on Linux via host-gateway); the caller builds `egress.env` with that
115
+ // hostname (proxyEnv(port, "host.docker.internal")).
116
+ if (egress && egress.port && sandbox.network) {
117
+ args.push("--add-host", "host.docker.internal:host-gateway");
118
+ const penv = proxyEnv(egress.port, "host.docker.internal");
119
+ for (const [k, v] of Object.entries(penv)) {
120
+ args.push("--env", `${k}=${v}`);
121
+ }
122
+ }
34
123
  const env = options.env || {};
35
124
  for (const key of ["CLAUDECODE", "CC_SESSION_ID", "CLAUDE_CODE_SESSION_ID"]) {
36
125
  if (env[key] != null) args.push("--env", `${key}=${env[key]}`);
@@ -61,6 +150,65 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
61
150
  };
62
151
  }
63
152
 
153
+ function executeBubblewrapShell(command, sandbox, options, hostCwd, policy) {
154
+ const args = [
155
+ "--die-with-parent",
156
+ "--new-session",
157
+ "--unshare-all",
158
+ "--ro-bind",
159
+ "/",
160
+ "/",
161
+ "--bind",
162
+ hostCwd,
163
+ hostCwd,
164
+ "--chdir",
165
+ hostCwd,
166
+ "--proc",
167
+ "/proc",
168
+ "--dev",
169
+ "/dev",
170
+ "--tmpfs",
171
+ "/tmp",
172
+ ];
173
+ if (sandbox.network) args.push("--share-net");
174
+ for (const target of policy.allowWrite) args.push("--bind", target, target);
175
+ for (const target of policy.denyWrite) args.push("--ro-bind", target, target);
176
+ for (const target of policy.denyRead) args.push("--tmpfs", target);
177
+ args.push("--", "sh", "-lc", String(command || ""));
178
+ // bwrap `--share-net` shares the host network namespace, so the egress proxy
179
+ // is reachable at 127.0.0.1 directly — merge its env into the child's.
180
+ const egress = options.egressProxy || null;
181
+ const bwrapEnv =
182
+ egress && egress.port && sandbox.network
183
+ ? { ...(options.env || {}), ...proxyEnv(egress.port, "127.0.0.1") }
184
+ : options.env;
185
+ const result = _deps.spawnSync("bwrap", args, {
186
+ cwd: hostCwd,
187
+ env: bwrapEnv,
188
+ encoding: "utf8",
189
+ timeout: options.timeout,
190
+ maxBuffer: options.maxBuffer,
191
+ windowsHide: true,
192
+ });
193
+ if (result.error) {
194
+ return {
195
+ stdout: result.stdout || "",
196
+ stderr:
197
+ result.error.code === "ENOENT"
198
+ ? "bubblewrap is not installed"
199
+ : result.error.message,
200
+ exitCode: typeof result.status === "number" ? result.status : 1,
201
+ failedToStart: true,
202
+ };
203
+ }
204
+ return {
205
+ stdout: result.stdout || "",
206
+ stderr: result.stderr || "",
207
+ exitCode: typeof result.status === "number" ? result.status : 1,
208
+ signal: result.signal || null,
209
+ };
210
+ }
211
+
64
212
  export function sandboxSummary(sandbox) {
65
213
  if (!sandbox) return null;
66
214
  return {
@@ -68,5 +216,13 @@ export function sandboxSummary(sandbox) {
68
216
  image: sandbox.image,
69
217
  network: sandbox.network ? "enabled" : "disabled",
70
218
  workspace: "read-write",
219
+ policy: {
220
+ additionalReadPaths: sandbox.policy?.allowRead?.length || 0,
221
+ additionalWritePaths: sandbox.policy?.allowWrite?.length || 0,
222
+ networkRestricted:
223
+ Boolean(sandbox.policy?.allowedDomains?.length) ||
224
+ Boolean(sandbox.policy?.deniedDomains?.length),
225
+ failIfUnavailable: sandbox.policy?.failIfUnavailable === true,
226
+ },
71
227
  };
72
228
  }
@@ -0,0 +1,434 @@
1
+ /**
2
+ * TaskLeaseRegistry (Phase 4 — Agent Team) — the two capabilities the existing
3
+ * shared task list lacks, layered on top of it rather than reinventing it:
4
+ *
5
+ * 1. EXCLUSIVE LEASES WITH TTL. session-core's `SharedTaskList.claim()` is
6
+ * documented as "不互斥" (non-mutex) — it just stamps an assignee, so two
7
+ * teammates can claim the same task and duplicate the work. Here a task can
8
+ * be leased by AT MOST ONE holder at a time, the lease carries an expiry,
9
+ * and a crashed teammate's expired lease becomes reclaimable — satisfying
10
+ * the Phase 4 acceptance "多 Agent 不会重复处理已被有效 lease 的任务" and
11
+ * "teammate 崩溃后任务可回收并重新分配".
12
+ *
13
+ * 2. DEPENDENCY EDGES (a DAG). A task declares `dependsOn: [key,…]`; it is not
14
+ * claimable until every dependency is COMPLETED. Cycles are rejected at add
15
+ * time (a cyclic dep would deadlock — it could never become claimable).
16
+ *
17
+ * It COMPOSES a real `SharedTaskList` (optimistic-lock task store, status
18
+ * vocabulary, snapshot/restore, terminal-state enforcement) — the lease + edges
19
+ * live in each task's metadata, so persistence/recovery come for free. The clock
20
+ * is injected (`now`) so lease expiry is fully deterministic in tests.
21
+ *
22
+ * This is the scheduling brain; the claim→execute handoff to a real agent
23
+ * (SubAgentContext.run in a per-teammate worktree) is wired separately.
24
+ */
25
+
26
+ import { SharedTaskList, TASK_STATUS } from "@chainlesschain/session-core";
27
+
28
+ export const DEFAULT_LEASE_TTL_MS = 60000;
29
+ export const DEFAULT_MAX_ATTEMPTS = 3;
30
+
31
+ export class TaskLeaseRegistry {
32
+ constructor({
33
+ groupId = null,
34
+ now = () => Date.now(),
35
+ defaultTtlMs = DEFAULT_LEASE_TTL_MS,
36
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
37
+ } = {}) {
38
+ this._now = typeof now === "function" ? now : () => now;
39
+ this.defaultTtlMs = defaultTtlMs > 0 ? defaultTtlMs : DEFAULT_LEASE_TTL_MS;
40
+ this.maxAttempts = maxAttempts > 0 ? maxAttempts : DEFAULT_MAX_ATTEMPTS;
41
+ this._tasks = new SharedTaskList({ groupId, now: this._now });
42
+ this._byKey = new Map(); // stable user key → internal SharedTaskList id
43
+ }
44
+
45
+ /**
46
+ * Add a task to the graph. `key` is a stable, user-chosen identifier so
47
+ * `dependsOn` can reference tasks regardless of insertion order. Rejects a
48
+ * duplicate key or a dependency edge that would create a cycle.
49
+ *
50
+ * @returns {{ ok:boolean, key?:string, reason?:string, cycle?:string[] }}
51
+ */
52
+ addTask({
53
+ key,
54
+ title,
55
+ dependsOn = [],
56
+ priority = "normal",
57
+ metadata = {},
58
+ createdBy = null,
59
+ } = {}) {
60
+ const k = key || `t_${this._tasks.size() + 1}`;
61
+ if (this._byKey.has(k)) {
62
+ return { ok: false, reason: `duplicate task key "${k}"` };
63
+ }
64
+ if (!title || typeof title !== "string") {
65
+ return { ok: false, reason: "title required" };
66
+ }
67
+ const deps = Array.isArray(dependsOn) ? dependsOn.filter(Boolean) : [];
68
+ // Self-dependency and (once inserted) any back-edge that closes a cycle
69
+ // would deadlock the task. Check against the edges already present PLUS the
70
+ // proposed ones.
71
+ const cycle = this._detectCycleIfAdded(k, deps);
72
+ if (cycle) {
73
+ return { ok: false, reason: "dependency cycle", cycle };
74
+ }
75
+ const created = this._tasks.add({
76
+ title,
77
+ priority,
78
+ createdBy,
79
+ metadata: {
80
+ ...metadata,
81
+ key: k,
82
+ dependsOn: deps,
83
+ lease: null,
84
+ attempts: 0,
85
+ },
86
+ });
87
+ this._byKey.set(k, created.id);
88
+ return { ok: true, key: k, id: created.id };
89
+ }
90
+
91
+ /** Resolve a stable key → the current task object (or null). */
92
+ getTask(key) {
93
+ const id = this._byKey.get(key);
94
+ if (!id) return null;
95
+ const t = this._tasks.get(id);
96
+ if (!t) return null;
97
+ return this._view(t);
98
+ }
99
+
100
+ _view(t) {
101
+ const lease = t.metadata?.lease || null;
102
+ return {
103
+ key: t.metadata?.key,
104
+ id: t.id,
105
+ title: t.title,
106
+ status: t.status,
107
+ priority: t.priority,
108
+ dependsOn: t.metadata?.dependsOn || [],
109
+ attempts: t.metadata?.attempts || 0,
110
+ lease: lease ? { ...lease } : null,
111
+ assignee: t.assignee,
112
+ rev: t.rev,
113
+ // Full metadata is exposed so executors can read user payload
114
+ // (e.g. a task's shell `command` or agent `prompt`) — it carries the
115
+ // internal lease/dependsOn/key too, which callers simply ignore.
116
+ metadata: { ...t.metadata },
117
+ };
118
+ }
119
+
120
+ list() {
121
+ return this._tasks.list().map((t) => this._view(t));
122
+ }
123
+
124
+ _statusOf(key) {
125
+ const id = this._byKey.get(key);
126
+ const t = id ? this._tasks.get(id) : null;
127
+ return t ? t.status : null;
128
+ }
129
+
130
+ /** Unmet (not-yet-completed) dependency keys for a task. */
131
+ unmetDependencies(key) {
132
+ const t = this.getTask(key);
133
+ if (!t) return [];
134
+ return t.dependsOn.filter(
135
+ (d) => this._statusOf(d) !== TASK_STATUS.COMPLETED,
136
+ );
137
+ }
138
+
139
+ _leaseValid(lease, now) {
140
+ return !!(lease && lease.expiresAt > now);
141
+ }
142
+
143
+ /**
144
+ * Keys that could be leased right now: PENDING (or a task whose lease has
145
+ * expired) with all dependencies COMPLETED and no currently-valid lease.
146
+ */
147
+ claimable({ now = this._now() } = {}) {
148
+ const out = [];
149
+ for (const t of this._tasks.list()) {
150
+ const v = this._view(t);
151
+ if (
152
+ v.status === TASK_STATUS.COMPLETED ||
153
+ v.status === TASK_STATUS.CANCELLED
154
+ ) {
155
+ continue;
156
+ }
157
+ if (this._leaseValid(v.lease, now)) continue; // held by a live teammate
158
+ if (this.unmetDependencies(v.key).length > 0) continue; // blocked by deps
159
+ out.push(v.key);
160
+ }
161
+ return out;
162
+ }
163
+
164
+ /**
165
+ * Acquire (or renew, or steal an expired) exclusive lease.
166
+ * - blocked by unmet deps → { ok:false, reason:"blocked_by_deps", unmet }
167
+ * - terminal task → { ok:false, reason:"terminal" }
168
+ * - validly leased by someone else → { ok:false, reason:"leased", holder, expiresAt }
169
+ * - same holder → renew (extend expiry, bump renewals)
170
+ * - expired lease held by another → steal (stolen:true)
171
+ * @returns {{ ok:boolean, reason?:string, lease?:object, unmet?:string[], holder?:string, expiresAt?:number }}
172
+ */
173
+ acquire(key, { holder, ttlMs, now = this._now() } = {}) {
174
+ if (!holder) return { ok: false, reason: "holder required" };
175
+ const id = this._byKey.get(key);
176
+ const task = id ? this._tasks.get(id) : null;
177
+ if (!task) return { ok: false, reason: "not_found" };
178
+ if (
179
+ task.status === TASK_STATUS.COMPLETED ||
180
+ task.status === TASK_STATUS.CANCELLED
181
+ ) {
182
+ return { ok: false, reason: "terminal" };
183
+ }
184
+ const unmet = this.unmetDependencies(key);
185
+ if (unmet.length > 0) {
186
+ return { ok: false, reason: "blocked_by_deps", unmet };
187
+ }
188
+ const existing = task.metadata?.lease || null;
189
+ if (this._leaseValid(existing, now) && existing.holder !== holder) {
190
+ return {
191
+ ok: false,
192
+ reason: "leased",
193
+ holder: existing.holder,
194
+ expiresAt: existing.expiresAt,
195
+ };
196
+ }
197
+ const ttl = ttlMs > 0 ? ttlMs : this.defaultTtlMs;
198
+ const sameHolder = existing && existing.holder === holder;
199
+ const lease = {
200
+ holder,
201
+ acquiredAt: sameHolder ? existing.acquiredAt : now,
202
+ expiresAt: now + ttl,
203
+ renewals: sameHolder ? (existing.renewals || 0) + 1 : 0,
204
+ stolen: !!(existing && existing.holder !== holder),
205
+ };
206
+ return this._write(task, {
207
+ status: TASK_STATUS.IN_PROGRESS,
208
+ assignee: holder,
209
+ metadata: { ...task.metadata, lease },
210
+ })
211
+ ? { ok: true, lease }
212
+ : { ok: false, reason: "concurrent" };
213
+ }
214
+
215
+ /** Extend the lease you hold. Fails if you're not the current valid holder. */
216
+ renew(key, { holder, ttlMs, now = this._now() } = {}) {
217
+ const id = this._byKey.get(key);
218
+ const task = id ? this._tasks.get(id) : null;
219
+ if (!task) return { ok: false, reason: "not_found" };
220
+ const lease = task.metadata?.lease || null;
221
+ if (!this._leaseValid(lease, now) || lease.holder !== holder) {
222
+ return { ok: false, reason: "not_holder_or_expired" };
223
+ }
224
+ const ttl = ttlMs > 0 ? ttlMs : this.defaultTtlMs;
225
+ const next = {
226
+ ...lease,
227
+ expiresAt: now + ttl,
228
+ renewals: (lease.renewals || 0) + 1,
229
+ };
230
+ return this._write(task, { metadata: { ...task.metadata, lease: next } })
231
+ ? { ok: true, lease: next }
232
+ : { ok: false, reason: "concurrent" };
233
+ }
234
+
235
+ /** Voluntarily give up a lease — the task returns to PENDING for re-claim. */
236
+ release(key, { holder, now = this._now() } = {}) {
237
+ const id = this._byKey.get(key);
238
+ const task = id ? this._tasks.get(id) : null;
239
+ if (!task) return { ok: false, reason: "not_found" };
240
+ const lease = task.metadata?.lease || null;
241
+ if (!lease || lease.holder !== holder) {
242
+ return { ok: false, reason: "not_holder" };
243
+ }
244
+ return this._write(task, {
245
+ status: TASK_STATUS.PENDING,
246
+ assignee: null,
247
+ metadata: { ...task.metadata, lease: null },
248
+ })
249
+ ? { ok: true }
250
+ : { ok: false, reason: "concurrent" };
251
+ }
252
+
253
+ /** Complete a task you hold the lease on → terminal COMPLETED. */
254
+ complete(key, { holder, result = null, now = this._now() } = {}) {
255
+ const id = this._byKey.get(key);
256
+ const task = id ? this._tasks.get(id) : null;
257
+ if (!task) return { ok: false, reason: "not_found" };
258
+ const lease = task.metadata?.lease || null;
259
+ // Only the valid holder may complete — prevents a stale teammate (whose
260
+ // lease already expired and was reassigned) from marking another's work done.
261
+ if (!this._leaseValid(lease, now) || lease.holder !== holder) {
262
+ return { ok: false, reason: "not_holder_or_expired" };
263
+ }
264
+ return this._write(task, {
265
+ status: TASK_STATUS.COMPLETED,
266
+ metadata: { ...task.metadata, lease: null, result },
267
+ })
268
+ ? { ok: true }
269
+ : { ok: false, reason: "concurrent" };
270
+ }
271
+
272
+ /**
273
+ * Report a failed attempt. Under the attempt cap the task returns to PENDING
274
+ * (reclaimable for retry); at the cap it is CANCELLED (terminal) so the team
275
+ * doesn't loop forever on a doomed task.
276
+ * @returns {{ ok:boolean, retry?:boolean, attempts?:number, reason?:string }}
277
+ */
278
+ fail(key, { holder, error = null, now = this._now() } = {}) {
279
+ const id = this._byKey.get(key);
280
+ const task = id ? this._tasks.get(id) : null;
281
+ if (!task) return { ok: false, reason: "not_found" };
282
+ const lease = task.metadata?.lease || null;
283
+ if (!lease || lease.holder !== holder) {
284
+ return { ok: false, reason: "not_holder" };
285
+ }
286
+ const attempts = (task.metadata?.attempts || 0) + 1;
287
+ const willRetry = attempts < this.maxAttempts;
288
+ const ok = this._write(task, {
289
+ status: willRetry ? TASK_STATUS.PENDING : TASK_STATUS.CANCELLED,
290
+ assignee: null,
291
+ metadata: {
292
+ ...task.metadata,
293
+ lease: null,
294
+ attempts,
295
+ lastError: error ? String(error) : null,
296
+ },
297
+ });
298
+ if (!ok) return { ok: false, reason: "concurrent" };
299
+ return { ok: true, retry: willRetry, attempts };
300
+ }
301
+
302
+ /**
303
+ * Reclaim every task whose lease has EXPIRED (a crashed/lost teammate never
304
+ * renewed) back to PENDING so it can be re-assigned. Returns the reclaimed
305
+ * keys. This is the crash-recovery sweep.
306
+ */
307
+ reclaimExpired({ now = this._now() } = {}) {
308
+ const reclaimed = [];
309
+ for (const t of this._tasks.list()) {
310
+ const lease = t.metadata?.lease || null;
311
+ if (lease && !this._leaseValid(lease, now)) {
312
+ const fresh = this._tasks.get(t.id); // re-read for current rev
313
+ if (
314
+ this._write(fresh, {
315
+ status: TASK_STATUS.PENDING,
316
+ assignee: null,
317
+ metadata: { ...fresh.metadata, lease: null },
318
+ })
319
+ ) {
320
+ reclaimed.push(fresh.metadata?.key);
321
+ }
322
+ }
323
+ }
324
+ return reclaimed;
325
+ }
326
+
327
+ stats({ now = this._now() } = {}) {
328
+ const base = this._tasks.stats();
329
+ let leased = 0;
330
+ let expired = 0;
331
+ for (const t of this._tasks.list()) {
332
+ const lease = t.metadata?.lease || null;
333
+ if (lease) {
334
+ if (this._leaseValid(lease, now)) leased++;
335
+ else expired++;
336
+ }
337
+ }
338
+ return {
339
+ ...base,
340
+ leased,
341
+ expiredLeases: expired,
342
+ claimable: this.claimable({ now }).length,
343
+ };
344
+ }
345
+
346
+ /** Whether the graph is fully done (all terminal). */
347
+ allDone() {
348
+ return this._tasks
349
+ .list()
350
+ .every(
351
+ (t) =>
352
+ t.status === TASK_STATUS.COMPLETED ||
353
+ t.status === TASK_STATUS.CANCELLED,
354
+ );
355
+ }
356
+
357
+ snapshot() {
358
+ return {
359
+ registry: {
360
+ defaultTtlMs: this.defaultTtlMs,
361
+ maxAttempts: this.maxAttempts,
362
+ byKey: Array.from(this._byKey.entries()),
363
+ },
364
+ tasks: this._tasks.snapshot(),
365
+ };
366
+ }
367
+
368
+ static restore(snapshot, { now = () => Date.now() } = {}) {
369
+ const reg = new TaskLeaseRegistry({
370
+ now,
371
+ defaultTtlMs: snapshot?.registry?.defaultTtlMs,
372
+ maxAttempts: snapshot?.registry?.maxAttempts,
373
+ });
374
+ reg._tasks = SharedTaskList.restore(snapshot.tasks, { now: reg._now });
375
+ reg._byKey = new Map(snapshot?.registry?.byKey || []);
376
+ return reg;
377
+ }
378
+
379
+ // --- internals -----------------------------------------------------------
380
+
381
+ /** Optimistic write via the underlying list; false on rev conflict. */
382
+ _write(task, patch) {
383
+ try {
384
+ this._tasks.update(task.id, {
385
+ rev: task.rev,
386
+ patch,
387
+ actor: patch.assignee,
388
+ });
389
+ return true;
390
+ } catch {
391
+ // ConcurrencyError (rev changed under us) — caller re-reads and retries.
392
+ return false;
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Would adding edges `key → dependsOn[i]` create a cycle? Walks the existing
398
+ * edge set augmented with the proposed edges. Returns the offending path or
399
+ * null. A self-edge counts as a cycle.
400
+ */
401
+ _detectCycleIfAdded(key, deps) {
402
+ // Build adjacency from current tasks, then add the candidate.
403
+ const adj = new Map();
404
+ for (const t of this._tasks.list()) {
405
+ adj.set(t.metadata?.key, t.metadata?.dependsOn || []);
406
+ }
407
+ adj.set(key, deps);
408
+ const WHITE = 0,
409
+ GRAY = 1,
410
+ BLACK = 2;
411
+ const color = new Map();
412
+ const stack = [];
413
+ let found = null;
414
+ const visit = (node) => {
415
+ color.set(node, GRAY);
416
+ stack.push(node);
417
+ for (const next of adj.get(node) || []) {
418
+ if (!adj.has(next)) continue; // edge to a not-yet-added task: no cycle via it
419
+ const c = color.get(next) || WHITE;
420
+ if (c === GRAY) {
421
+ found = [...stack.slice(stack.indexOf(next)), next];
422
+ return true;
423
+ }
424
+ if (c === WHITE && visit(next)) return true;
425
+ }
426
+ stack.pop();
427
+ color.set(node, BLACK);
428
+ return false;
429
+ };
430
+ // Only need to start from the candidate — a new cycle must pass through it.
431
+ if (visit(key)) return found;
432
+ return null;
433
+ }
434
+ }