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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-CBLvrjeG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-DYv2GtE0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-CL6PZ-Ww.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-xuyHt4uA.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-DOvSVycq.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-DMhfTswr.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-CQxDdm63.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-BEeNhK2a.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CL04O-ER.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-C50o-n7x.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-CH-bDkzs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-DqAIMvSe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-C43fo_my.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-BERnMrdP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-Baw3w1gp.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-BEqAHF1-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-CvnmKwK4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-iMPxgVdt.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard--4SJX3YR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BgLV6xgz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-BbFfJ1Xi.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DeGq1B-q.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-L1PjW-lT.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-PK-X58to.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-BePWEH-d.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-BKkfk9hy.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-BNMqUDla.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-w4WPiyz2.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-DUKMVGNi.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-BWhiEyTJ.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-Ocf5JEkX.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-D2B6MMhU.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-DdYjCq1O.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-Cb1hfuUZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-CTeImGp7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-BjYNc7eQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DIp8Xcbd.js → OrderTableRenderer-DVYYIizh.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-Be2Kmr5j.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow-Cu_-qRlR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-DD2HoGzE.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-C9Srcs2M.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CUqcMgo1.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-HBrQGLXt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-D-B0vjwh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-eTeLIu9e.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-DrRqCsC6.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Dpid9CDg.js → Projects-1G9DNOhI.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-RkTZzpxP.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-BSCUgsgC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CHgyM3Bz.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-0lry4OZq.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-Cn45UroZ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-Q-Zjb707.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-BdM70oda.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-DmOAKKap.js} +2 -2
- package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-Dwavbm07.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DukXFCnk.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-BIlRnITu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-YYVQdhhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-B8_iYHz2.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-BdVrhT31.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-CD3ggRx7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-Bp02VZFH.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-DEVmCEsr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-B6czWc4N.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-BCJLv17r.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-CD132d2Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BnSY0S-s.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-VZw9Oy2w.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-pZq7Hjd2.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-DnQaqk4q.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-BEUfmvJV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-CoZKMXJ2.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-CBOGVx1I.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-huSKn7TT.js} +1 -1
- package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-A_3w6FBO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-D1Bem9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-CJYJj0oO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-BkbFiKT4.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-CKvUafVY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-AjrSPnvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-B8i0lViZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-B9Svn0zc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-BHdHSX06.js} +1 -1
- package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BHdailyo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-BJwlEnYo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-BOoAQjJz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-BSgWxAOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-BdutMsne.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-BiDWxzbn.js} +1 -1
- package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-By5a0T_W.js} +1 -1
- package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-C-WIY-FQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-C37f6iey.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-C9bWmTcO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-CBaosWRO.js} +1 -1
- package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-CBlBVJ_m.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-CD62EEGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-CUj-l7GM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-CVS8Ymuq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-ChkXUj3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-Cj0OZ-37.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-Co3OFL0t.js} +1 -1
- package/src/assets/web-panel/assets/{index-308gCGh7.js → index-Crp8CbRg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-D22zNXiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-DTs_D1OL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-DfgCdFrN.js} +3 -3
- package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-DjYrecNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-DpW9cf3e.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
- package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-F--HuPG1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-Fuk6t_3K.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-JEAGMii9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-PF-mpv8K.js} +1 -1
- package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
- package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-b-sbVdjQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-pyvMVX8r.js} +1 -1
- package/src/assets/web-panel/assets/{index-DISWAYce.js → index-qNTtOVi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-tgO8iza6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahryTX0.js → index-ttDQVhZy.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-DtrnfsZH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D4zqSaX3.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-CGFGOUQj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DH2HvkJl.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-jJ2at5HX.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-CRyMT1Fv.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-DZ_CX0Pt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-wruHk-h5.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BGgx8q67.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-Do5M7gKm.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-CRa-GZpX.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-v5O2ZcJm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub--z2FU0Px.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-1emidVKd.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-CcJaRyHs.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/assets/web-panel/remote-session-sw.js +43 -0
- package/src/command-manifest.json +22 -1
- package/src/commands/a2a.js +19 -4
- package/src/commands/activitypub.js +20 -3
- package/src/commands/agent.js +71 -10
- package/src/commands/audit.js +40 -24
- package/src/commands/codeintel.js +320 -0
- package/src/commands/collab.js +15 -2
- package/src/commands/compliance.js +5 -0
- package/src/commands/config.js +4 -1
- package/src/commands/dao.js +86 -14
- package/src/commands/dev.js +2 -0
- package/src/commands/did.js +7 -1
- package/src/commands/dlp.js +23 -1
- package/src/commands/economy.js +29 -3
- package/src/commands/eval.js +255 -0
- package/src/commands/evomap.js +26 -0
- package/src/commands/governance.js +17 -3
- package/src/commands/hardening.js +18 -0
- package/src/commands/incentive.js +5 -0
- package/src/commands/init.js +46 -2
- package/src/commands/kg.js +4 -0
- package/src/commands/loop.js +6 -1
- package/src/commands/lowcode.js +15 -2
- package/src/commands/marketplace.js +40 -6
- package/src/commands/matrix.js +20 -1
- package/src/commands/memory.js +4 -1
- package/src/commands/mtc.js +7 -1
- package/src/commands/nostr.js +31 -4
- package/src/commands/note.js +7 -4
- package/src/commands/plugin.js +474 -0
- package/src/commands/pqc.js +5 -0
- package/src/commands/reputation.js +10 -1
- package/src/commands/scim.js +6 -0
- package/src/commands/session.js +32 -10
- package/src/commands/siem.js +11 -0
- package/src/commands/sla.js +18 -3
- package/src/commands/social.js +38 -5
- package/src/commands/sso.js +10 -2
- package/src/commands/stress.js +23 -4
- package/src/commands/team.js +463 -0
- package/src/commands/tech.js +2 -0
- package/src/commands/tenant.js +10 -1
- package/src/commands/terraform.js +6 -0
- package/src/commands/update.js +1 -1
- package/src/commands/zkp.js +20 -0
- package/src/gateways/ws/message-dispatcher.js +48 -2
- package/src/gateways/ws/remote-session-protocol.js +140 -42
- package/src/gateways/ws/ws-server.js +1 -1
- package/src/harness/jsonl-session-store.js +12 -5
- package/src/harness/plugin-manager.js +16 -4
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +189 -0
- package/src/harness/remote-session-push-apns.js +246 -0
- package/src/harness/remote-session-push-errors.js +15 -0
- package/src/harness/remote-session-push-fcm.js +9 -25
- package/src/harness/remote-session-push-huawei.js +174 -0
- package/src/harness/remote-session-push-oppo.js +171 -0
- package/src/harness/remote-session-push-senders.js +42 -0
- package/src/harness/remote-session-push-vivo.js +179 -0
- package/src/harness/remote-session-push-web.js +299 -0
- package/src/harness/remote-session-push-xiaomi.js +99 -0
- package/src/harness/worktree-isolator.js +16 -6
- package/src/index.js +6 -0
- package/src/lib/__tests__/logger.test.js +5 -2
- package/src/lib/a2a-protocol.js +25 -1
- package/src/lib/activitypub-bridge.js +61 -0
- package/src/lib/agent-core.js +4 -0
- package/src/lib/agent-economy.js +262 -29
- package/src/lib/agent-network.js +7 -1
- package/src/lib/agent-sandbox.js +161 -5
- package/src/lib/agent-team/task-lease.js +434 -0
- package/src/lib/agent-team/team-budget.js +165 -0
- package/src/lib/agent-team/team-mailbox.js +106 -0
- package/src/lib/agent-team/team-runner.js +282 -0
- package/src/lib/agent-team/team-worktree.js +236 -0
- package/src/lib/agents.js +18 -0
- package/src/lib/async-hook-supervisor.cjs +391 -0
- package/src/lib/audit-logger.js +7 -6
- package/src/lib/autonomous-developer.js +60 -0
- package/src/lib/chat-core.js +17 -4
- package/src/lib/collaboration-governance.js +56 -0
- package/src/lib/community-governance.js +68 -0
- package/src/lib/compliance-manager.js +63 -0
- package/src/lib/cross-chain.js +5 -0
- package/src/lib/dao-governance.js +151 -2
- package/src/lib/did-manager.js +23 -10
- package/src/lib/dlp-engine.js +70 -0
- package/src/lib/eval/runner.js +251 -0
- package/src/lib/eval/tasks.js +626 -0
- package/src/lib/eval/trend.js +200 -0
- package/src/lib/evolution-system.js +92 -0
- package/src/lib/evomap-federation.js +55 -0
- package/src/lib/evomap-governance.js +94 -0
- package/src/lib/fatal-handler.js +17 -1
- package/src/lib/hardening-manager.js +73 -0
- package/src/lib/hierarchical-memory.js +14 -8
- package/src/lib/knowledge-exporter.js +8 -2
- package/src/lib/knowledge-graph.js +79 -0
- package/src/lib/llm-providers.js +23 -14
- package/src/lib/logger.js +4 -1
- package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
- package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
- package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
- package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +269 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +333 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
- package/src/lib/lsp/benchmark.js +257 -0
- package/src/lib/lsp/code-intelligence.js +356 -0
- package/src/lib/lsp/jsonrpc-stream.js +139 -0
- package/src/lib/lsp/lsp-client.js +339 -0
- package/src/lib/lsp/lsp-manager.js +375 -0
- package/src/lib/lsp/lsp-server-registry.js +196 -0
- package/src/lib/matrix-bridge.js +84 -0
- package/src/lib/memory-manager.js +5 -3
- package/src/lib/nostr-bridge.js +53 -0
- package/src/lib/permission-engine.js +22 -4
- package/src/lib/plugin-monitor-supervisor.js +238 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
- package/src/lib/plugin-runtime/agents.js +51 -0
- package/src/lib/plugin-runtime/bin.js +100 -0
- package/src/lib/plugin-runtime/hooks.js +100 -0
- package/src/lib/plugin-runtime/install.js +396 -0
- package/src/lib/plugin-runtime/lsp.js +75 -0
- package/src/lib/plugin-runtime/manifest.js +458 -0
- package/src/lib/plugin-runtime/mcp.js +89 -0
- package/src/lib/plugin-runtime/monitors.js +100 -0
- package/src/lib/plugin-runtime/policy.js +152 -0
- package/src/lib/plugin-runtime/reload.js +79 -0
- package/src/lib/plugin-runtime/remote-source.js +240 -0
- package/src/lib/plugin-runtime/scopes.js +197 -0
- package/src/lib/plugin-runtime/settings.js +119 -0
- package/src/lib/plugin-runtime/signature.js +107 -0
- package/src/lib/plugin-runtime/skills.js +43 -0
- package/src/lib/plugin-runtime/trust.js +140 -0
- package/src/lib/pqc-manager.js +64 -0
- package/src/lib/reputation-optimizer.js +101 -0
- package/src/lib/sandbox-egress-proxy.js +262 -0
- package/src/lib/sandbox-fs-policy.js +112 -0
- package/src/lib/sandbox-network-policy.js +151 -0
- package/src/lib/sandbox-v2.js +24 -18
- package/src/lib/scim-manager.js +36 -0
- package/src/lib/session-manager.js +5 -2
- package/src/lib/settings-hook-events.cjs +78 -6
- package/src/lib/settings-hooks.cjs +30 -3
- package/src/lib/settings-loader.cjs +35 -1
- package/src/lib/siem-exporter.js +45 -0
- package/src/lib/skill-loader.js +38 -2
- package/src/lib/skill-marketplace.js +83 -0
- package/src/lib/sla-manager.js +88 -0
- package/src/lib/social-manager.js +74 -0
- package/src/lib/stress-tester.js +65 -0
- package/src/lib/tech-learning-engine.js +75 -0
- package/src/lib/telemetry/span-recorder.js +237 -0
- package/src/lib/tenant-saas.js +107 -0
- package/src/lib/terraform-manager.js +67 -0
- package/src/lib/token-incentive.js +180 -29
- package/src/lib/wallet-manager.js +81 -35
- package/src/lib/zkp-engine.js +87 -0
- package/src/repl/agent-repl.js +417 -6
- package/src/runtime/__tests__/auto-pin.test.js +104 -0
- package/src/runtime/agent-core.js +771 -104
- package/src/runtime/auto-pin.js +117 -0
- package/src/runtime/coding-agent-contract-shared.cjs +82 -8
- package/src/runtime/coding-agent-policy.cjs +32 -7
- package/src/runtime/fallback-model.js +134 -7
- package/src/runtime/headless-runner.js +388 -108
- package/src/runtime/mcp-config.js +46 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
package/src/lib/agent-network.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
package/src/lib/agent-sandbox.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
+
}
|