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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Remote Session vendor push — OPPO (HeyTap / ColorOS push) sender.
|
|
2
|
+
//
|
|
3
|
+
// A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
|
|
4
|
+
// for OPPO/OnePlus/realme devices via the OPPO Push (HeyTap) server API. Auth is
|
|
5
|
+
// a two-step handshake: sign = SHA256(app_key + timestamp + master_secret) is
|
|
6
|
+
// exchanged for a short-lived `auth_token` (valid ~24h), then a form POST to the
|
|
7
|
+
// unicast endpoint carries that token. Plain HTTPS with an injectable `fetch`
|
|
8
|
+
// (default global), fully unit-testable with a fake transport. The payload
|
|
9
|
+
// carries only routing ids (no session content).
|
|
10
|
+
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_HOST = "https://api.push.oppomobile.com";
|
|
15
|
+
const AUTH_PATH = "/server/v1/auth";
|
|
16
|
+
const SEND_PATH = "/server/v1/message/notification/unicast";
|
|
17
|
+
// OPPO auth_token is valid for 24h; refresh a few minutes early.
|
|
18
|
+
const AUTH_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
const AUTH_TOKEN_SKEW_MS = 5 * 60_000;
|
|
20
|
+
const CODE_SUCCESS = 0;
|
|
21
|
+
// OPPO has no single canonical "unregistered" code for a stale registration id;
|
|
22
|
+
// match the message text it returns alongside the failing code.
|
|
23
|
+
const INVALID_TOKEN_RE =
|
|
24
|
+
/invalid.*registration|registration.*invalid|invalid.*token|token.*invalid|unregist|not ?exist|无效|不存在/i;
|
|
25
|
+
|
|
26
|
+
async function safeText(res) {
|
|
27
|
+
try {
|
|
28
|
+
return await res.text();
|
|
29
|
+
} catch {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function sha256Hex(input) {
|
|
35
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Mints + caches an OPPO auth_token from app key + master secret. */
|
|
39
|
+
export class OppoAuthProvider {
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
if (!options.appKey) throw new Error("OppoAuthProvider requires an appKey");
|
|
42
|
+
if (!options.masterSecret) {
|
|
43
|
+
throw new Error("OppoAuthProvider requires a masterSecret");
|
|
44
|
+
}
|
|
45
|
+
this.appKey = options.appKey;
|
|
46
|
+
this.masterSecret = options.masterSecret;
|
|
47
|
+
this.host = options.host || DEFAULT_HOST;
|
|
48
|
+
this.now = options.now || Date.now;
|
|
49
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
50
|
+
this._cached = null; // { token, expiresAt }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getAuthToken() {
|
|
54
|
+
const now = this.now();
|
|
55
|
+
if (this._cached && this._cached.expiresAt - AUTH_TOKEN_SKEW_MS > now) {
|
|
56
|
+
return this._cached.token;
|
|
57
|
+
}
|
|
58
|
+
const timestamp = String(now);
|
|
59
|
+
const sign = sha256Hex(`${this.appKey}${timestamp}${this.masterSecret}`);
|
|
60
|
+
const body = new URLSearchParams({
|
|
61
|
+
app_key: this.appKey,
|
|
62
|
+
sign,
|
|
63
|
+
timestamp,
|
|
64
|
+
});
|
|
65
|
+
const res = await this.fetch(`${this.host}${AUTH_PATH}`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
68
|
+
body: body.toString(),
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`OPPO auth failed (${res.status}): ${await safeText(res)}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const json = await res.json();
|
|
76
|
+
const token = json?.data?.auth_token;
|
|
77
|
+
if (Number(json.code) !== CODE_SUCCESS || !token) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`OPPO auth response missing auth_token: ${json.message || json.code}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
this._cached = { token, expiresAt: now + AUTH_TOKEN_TTL_MS };
|
|
83
|
+
return token;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Sends one notification to an OPPO registration id. */
|
|
88
|
+
export class OppoPushSender {
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
if (!options.authProvider) {
|
|
91
|
+
throw new Error("OppoPushSender requires an authProvider");
|
|
92
|
+
}
|
|
93
|
+
this.authProvider = options.authProvider;
|
|
94
|
+
this.host = options.host || DEFAULT_HOST;
|
|
95
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Bound function matching the dispatcher's `sender` contract. */
|
|
99
|
+
get handler() {
|
|
100
|
+
return (payload) => this.send(payload);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async send({ token, notification, sessionId, clientId } = {}) {
|
|
104
|
+
if (!token) throw new Error("OPPO send requires a registration id");
|
|
105
|
+
const authToken = await this.authProvider.getAuthToken();
|
|
106
|
+
// Routing ids only — no session content.
|
|
107
|
+
const data = { type: "remote-session.approval-request" };
|
|
108
|
+
if (sessionId != null) data.sessionId = String(sessionId);
|
|
109
|
+
if (clientId != null) data.clientId = String(clientId);
|
|
110
|
+
const message = {
|
|
111
|
+
target_type: 2, // 2 = single registration_id
|
|
112
|
+
target_value: token,
|
|
113
|
+
notification: {
|
|
114
|
+
title: notification?.title || "Approval requested",
|
|
115
|
+
content: notification?.body || "A coding session needs your approval",
|
|
116
|
+
click_action_type: 0, // open the app
|
|
117
|
+
action_parameters: JSON.stringify(data),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const body = new URLSearchParams({
|
|
121
|
+
auth_token: authToken,
|
|
122
|
+
message: JSON.stringify(message),
|
|
123
|
+
});
|
|
124
|
+
const res = await this.fetch(`${this.host}${SEND_PATH}`, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
128
|
+
auth_token: authToken,
|
|
129
|
+
},
|
|
130
|
+
body: body.toString(),
|
|
131
|
+
});
|
|
132
|
+
const json = await res.json().catch(() => ({}));
|
|
133
|
+
if (res.ok && Number(json.code) === CODE_SUCCESS) {
|
|
134
|
+
return { id: json.data?.messageId || null };
|
|
135
|
+
}
|
|
136
|
+
const reason = json.message || `HTTP ${res.status}`;
|
|
137
|
+
if (INVALID_TOKEN_RE.test(String(reason))) {
|
|
138
|
+
throw new PushTokenUnregisteredError(
|
|
139
|
+
`OPPO registration id invalid: ${reason}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`OPPO push failed (${json.code ?? res.status}): ${reason}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build a bound OPPO `sender` from env, or null when unconfigured.
|
|
148
|
+
*
|
|
149
|
+
* OPPO env:
|
|
150
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_OPPO_APP_KEY
|
|
151
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_OPPO_MASTER_SECRET
|
|
152
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_OPPO_HOST (optional; override the API host)
|
|
153
|
+
*/
|
|
154
|
+
export function createOppoPushSender(env = {}, options = {}) {
|
|
155
|
+
const appKey =
|
|
156
|
+
options.appKey || env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_APP_KEY;
|
|
157
|
+
const masterSecret =
|
|
158
|
+
options.masterSecret ||
|
|
159
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_MASTER_SECRET;
|
|
160
|
+
if (!appKey || !masterSecret) return null;
|
|
161
|
+
const host = options.host || env.CHAINLESSCHAIN_REMOTE_SESSION_OPPO_HOST;
|
|
162
|
+
let authProvider;
|
|
163
|
+
try {
|
|
164
|
+
authProvider =
|
|
165
|
+
options.authProvider ||
|
|
166
|
+
new OppoAuthProvider({ appKey, masterSecret, host, ...options });
|
|
167
|
+
} catch {
|
|
168
|
+
return null; // Malformed credentials — stay disabled rather than crash.
|
|
169
|
+
}
|
|
170
|
+
return new OppoPushSender({ authProvider, host, ...options }).handler;
|
|
171
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Remote Session vendor push — provider dispatch.
|
|
2
|
+
//
|
|
3
|
+
// Picks a concrete `sender` implementation by CHAINLESSCHAIN_REMOTE_SESSION_
|
|
4
|
+
// PUSH_PROVIDER (default "fcm"). Each provider factory returns a bound sender
|
|
5
|
+
// or null when unconfigured; unknown/unimplemented providers yield null so the
|
|
6
|
+
// dispatcher stays a no-op. Adding a provider (HMS / Xiaomi / …) is one import
|
|
7
|
+
// + one case here.
|
|
8
|
+
|
|
9
|
+
import { createFcmPushSender } from "./remote-session-push-fcm.js";
|
|
10
|
+
import { createApnsPushSender } from "./remote-session-push-apns.js";
|
|
11
|
+
import { createWebPushSender } from "./remote-session-push-web.js";
|
|
12
|
+
import { createXiaomiPushSender } from "./remote-session-push-xiaomi.js";
|
|
13
|
+
import { createHuaweiPushSender } from "./remote-session-push-huawei.js";
|
|
14
|
+
import { createOppoPushSender } from "./remote-session-push-oppo.js";
|
|
15
|
+
import { createVivoPushSender } from "./remote-session-push-vivo.js";
|
|
16
|
+
|
|
17
|
+
export function createRemoteSessionPushSender(env = {}, options = {}) {
|
|
18
|
+
const provider = (
|
|
19
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_PUSH_PROVIDER || "fcm"
|
|
20
|
+
).toLowerCase();
|
|
21
|
+
switch (provider) {
|
|
22
|
+
case "fcm":
|
|
23
|
+
return createFcmPushSender(env, options);
|
|
24
|
+
case "apns":
|
|
25
|
+
return createApnsPushSender(env, options);
|
|
26
|
+
case "web":
|
|
27
|
+
return createWebPushSender(env, options);
|
|
28
|
+
case "xiaomi":
|
|
29
|
+
case "mi":
|
|
30
|
+
return createXiaomiPushSender(env, options);
|
|
31
|
+
case "huawei":
|
|
32
|
+
case "hms":
|
|
33
|
+
return createHuaweiPushSender(env, options);
|
|
34
|
+
case "oppo":
|
|
35
|
+
case "heytap":
|
|
36
|
+
return createOppoPushSender(env, options);
|
|
37
|
+
case "vivo":
|
|
38
|
+
return createVivoPushSender(env, options);
|
|
39
|
+
default:
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Remote Session vendor push — vivo (vpush) sender.
|
|
2
|
+
//
|
|
3
|
+
// A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
|
|
4
|
+
// for vivo/iQOO devices via the vivo Push server API. Auth is a two-step
|
|
5
|
+
// handshake: sign = MD5(appId + appKey + timestamp + appSecret) is exchanged for
|
|
6
|
+
// a short-lived `authToken` (valid ~24h), then a JSON POST to the single-send
|
|
7
|
+
// endpoint carries that token in the `authToken` header. Plain HTTPS with an
|
|
8
|
+
// injectable `fetch` (default global), fully unit-testable with a fake
|
|
9
|
+
// transport. The payload carries only routing ids (no session content).
|
|
10
|
+
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
+
import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_HOST = "https://api-push.vivo.com.cn";
|
|
15
|
+
const AUTH_PATH = "/message/auth";
|
|
16
|
+
const SEND_PATH = "/message/send";
|
|
17
|
+
// vivo authToken is valid for ~24h; refresh a few minutes early.
|
|
18
|
+
const AUTH_TOKEN_TTL_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
const AUTH_TOKEN_SKEW_MS = 5 * 60_000;
|
|
20
|
+
const RESULT_SUCCESS = 0;
|
|
21
|
+
// vivo regId-invalid result codes (single-send). 10302/10303 cover an illegal
|
|
22
|
+
// regId or one that does not belong to this app.
|
|
23
|
+
const INVALID_TOKEN_CODES = new Set([10302, 10303]);
|
|
24
|
+
// vivo has no single canonical string either; also match the desc text.
|
|
25
|
+
const INVALID_TOKEN_RE =
|
|
26
|
+
/invalid.*reg|reg.*invalid|illegal|unregist|not ?exist|无效|不存在|非法/i;
|
|
27
|
+
|
|
28
|
+
async function safeText(res) {
|
|
29
|
+
try {
|
|
30
|
+
return await res.text();
|
|
31
|
+
} catch {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function md5Hex(input) {
|
|
37
|
+
return createHash("md5").update(input, "utf8").digest("hex");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Mints + caches a vivo authToken from app id + key + secret. */
|
|
41
|
+
export class VivoAuthProvider {
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
if (!options.appId) throw new Error("VivoAuthProvider requires an appId");
|
|
44
|
+
if (!options.appKey) throw new Error("VivoAuthProvider requires an appKey");
|
|
45
|
+
if (!options.appSecret) {
|
|
46
|
+
throw new Error("VivoAuthProvider requires an appSecret");
|
|
47
|
+
}
|
|
48
|
+
this.appId = options.appId;
|
|
49
|
+
this.appKey = options.appKey;
|
|
50
|
+
this.appSecret = options.appSecret;
|
|
51
|
+
this.host = options.host || DEFAULT_HOST;
|
|
52
|
+
this.now = options.now || Date.now;
|
|
53
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
54
|
+
this._cached = null; // { token, expiresAt }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getAuthToken() {
|
|
58
|
+
const now = this.now();
|
|
59
|
+
if (this._cached && this._cached.expiresAt - AUTH_TOKEN_SKEW_MS > now) {
|
|
60
|
+
return this._cached.token;
|
|
61
|
+
}
|
|
62
|
+
const timestamp = now;
|
|
63
|
+
const sign = md5Hex(
|
|
64
|
+
`${this.appId}${this.appKey}${timestamp}${this.appSecret}`,
|
|
65
|
+
);
|
|
66
|
+
const res = await this.fetch(`${this.host}${AUTH_PATH}`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { "Content-Type": "application/json" },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
appId: this.appId,
|
|
71
|
+
appKey: this.appKey,
|
|
72
|
+
timestamp,
|
|
73
|
+
sign,
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`vivo auth failed (${res.status}): ${await safeText(res)}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const json = await res.json();
|
|
82
|
+
if (Number(json.result) !== RESULT_SUCCESS || !json.authToken) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`vivo auth response missing authToken: ${json.desc || json.result}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
this._cached = {
|
|
88
|
+
token: json.authToken,
|
|
89
|
+
expiresAt: now + AUTH_TOKEN_TTL_MS,
|
|
90
|
+
};
|
|
91
|
+
return this._cached.token;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Sends one notification to a vivo regId. */
|
|
96
|
+
export class VivoPushSender {
|
|
97
|
+
constructor(options = {}) {
|
|
98
|
+
if (!options.authProvider) {
|
|
99
|
+
throw new Error("VivoPushSender requires an authProvider");
|
|
100
|
+
}
|
|
101
|
+
this.authProvider = options.authProvider;
|
|
102
|
+
this.host = options.host || DEFAULT_HOST;
|
|
103
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
104
|
+
// vivo requires a unique requestId per message; injectable for tests.
|
|
105
|
+
this.requestId =
|
|
106
|
+
options.requestId || (() => randomUUID().replace(/-/g, ""));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Bound function matching the dispatcher's `sender` contract. */
|
|
110
|
+
get handler() {
|
|
111
|
+
return (payload) => this.send(payload);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async send({ token, notification, sessionId, clientId } = {}) {
|
|
115
|
+
if (!token) throw new Error("vivo send requires a regId");
|
|
116
|
+
const authToken = await this.authProvider.getAuthToken();
|
|
117
|
+
// Routing ids only — no session content.
|
|
118
|
+
const custom = { type: "remote-session.approval-request" };
|
|
119
|
+
if (sessionId != null) custom.sessionId = String(sessionId);
|
|
120
|
+
if (clientId != null) custom.clientId = String(clientId);
|
|
121
|
+
const message = {
|
|
122
|
+
regId: token,
|
|
123
|
+
notifyType: 1, // 1 = notification bar
|
|
124
|
+
title: notification?.title || "Approval requested",
|
|
125
|
+
content: notification?.body || "A coding session needs your approval",
|
|
126
|
+
skipType: 1, // 1 = open the app
|
|
127
|
+
requestId: this.requestId(),
|
|
128
|
+
clientCustomMap: custom,
|
|
129
|
+
};
|
|
130
|
+
const res = await this.fetch(`${this.host}${SEND_PATH}`, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: {
|
|
133
|
+
"Content-Type": "application/json",
|
|
134
|
+
authToken,
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(message),
|
|
137
|
+
});
|
|
138
|
+
const json = await res.json().catch(() => ({}));
|
|
139
|
+
if (res.ok && Number(json.result) === RESULT_SUCCESS) {
|
|
140
|
+
return { id: json.taskId || null };
|
|
141
|
+
}
|
|
142
|
+
const code = Number(json.result);
|
|
143
|
+
const desc = json.desc || `HTTP ${res.status}`;
|
|
144
|
+
if (INVALID_TOKEN_CODES.has(code) || INVALID_TOKEN_RE.test(String(desc))) {
|
|
145
|
+
throw new PushTokenUnregisteredError(`vivo regId invalid: ${desc}`);
|
|
146
|
+
}
|
|
147
|
+
throw new Error(
|
|
148
|
+
`vivo push failed (${Number.isNaN(code) ? res.status : code}): ${desc}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build a bound vivo `sender` from env, or null when unconfigured.
|
|
155
|
+
*
|
|
156
|
+
* vivo env:
|
|
157
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_ID
|
|
158
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_KEY
|
|
159
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_SECRET
|
|
160
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VIVO_HOST (optional; override the API host)
|
|
161
|
+
*/
|
|
162
|
+
export function createVivoPushSender(env = {}, options = {}) {
|
|
163
|
+
const appId = options.appId || env.CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_ID;
|
|
164
|
+
const appKey =
|
|
165
|
+
options.appKey || env.CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_KEY;
|
|
166
|
+
const appSecret =
|
|
167
|
+
options.appSecret || env.CHAINLESSCHAIN_REMOTE_SESSION_VIVO_APP_SECRET;
|
|
168
|
+
if (!appId || !appKey || !appSecret) return null;
|
|
169
|
+
const host = options.host || env.CHAINLESSCHAIN_REMOTE_SESSION_VIVO_HOST;
|
|
170
|
+
let authProvider;
|
|
171
|
+
try {
|
|
172
|
+
authProvider =
|
|
173
|
+
options.authProvider ||
|
|
174
|
+
new VivoAuthProvider({ appId, appKey, appSecret, host, ...options });
|
|
175
|
+
} catch {
|
|
176
|
+
return null; // Malformed credentials — stay disabled rather than crash.
|
|
177
|
+
}
|
|
178
|
+
return new VivoPushSender({ authProvider, host, ...options }).handler;
|
|
179
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// Remote Session vendor push — Web Push (browser) sender.
|
|
2
|
+
//
|
|
3
|
+
// A concrete implementation of the RemoteSessionPushDispatcher `sender` contract
|
|
4
|
+
// for browser clients, using the Web Push protocol: VAPID (RFC 8292) auth via an
|
|
5
|
+
// ES256 JWT signed with the server VAPID key, and RFC 8291 (aes128gcm) payload
|
|
6
|
+
// encryption bound to the subscription's ECDH public key + auth secret. The
|
|
7
|
+
// "token" is the browser PushSubscription (endpoint + keys) as JSON.
|
|
8
|
+
//
|
|
9
|
+
// Web Push endpoints accept plain HTTPS, so the network transport is an
|
|
10
|
+
// injectable `fetch` (default global). Encryption/signing use node:crypto and
|
|
11
|
+
// are exercised by a real encrypt→decrypt round-trip in the tests — no live
|
|
12
|
+
// push service or credentials needed. The payload carries only routing ids.
|
|
13
|
+
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import { PushTokenUnregisteredError } from "./remote-session-push-errors.js";
|
|
16
|
+
|
|
17
|
+
const VAPID_TTL_MS = 12 * 60 * 60 * 1000;
|
|
18
|
+
const VAPID_SKEW_MS = 60 * 1000;
|
|
19
|
+
const RECORD_SIZE = 4096;
|
|
20
|
+
const DEFAULT_TTL_SECONDS = 2419200; // 28 days, capped by the push service
|
|
21
|
+
|
|
22
|
+
function base64url(buf) {
|
|
23
|
+
return Buffer.from(buf)
|
|
24
|
+
.toString("base64")
|
|
25
|
+
.replace(/\+/g, "-")
|
|
26
|
+
.replace(/\//g, "_")
|
|
27
|
+
.replace(/=+$/, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function fromBase64url(value) {
|
|
31
|
+
return Buffer.from(String(value), "base64url");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hmac(key, data) {
|
|
35
|
+
return crypto.createHmac("sha256", key).update(data).digest();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// HKDF-Expand for a single output block (len <= 32).
|
|
39
|
+
function hkdfExpand(prk, info, len) {
|
|
40
|
+
return hmac(prk, Buffer.concat([info, Buffer.from([0x01])])).subarray(0, len);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// HKDF(salt, ikm, info, len) = Expand(Extract(salt, ikm), info, len).
|
|
44
|
+
function hkdf(salt, ikm, info, len) {
|
|
45
|
+
return hkdfExpand(hmac(salt, ikm), info, len);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function uint32be(value) {
|
|
49
|
+
const buf = Buffer.alloc(4);
|
|
50
|
+
buf.writeUInt32BE(value);
|
|
51
|
+
return buf;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// RFC 8291 §3.4 + RFC 8188 §2.1 content-encryption key + nonce.
|
|
55
|
+
function deriveContentKeys({
|
|
56
|
+
ecdhSecret,
|
|
57
|
+
authSecret,
|
|
58
|
+
salt,
|
|
59
|
+
uaPublic,
|
|
60
|
+
asPublic,
|
|
61
|
+
}) {
|
|
62
|
+
const keyInfo = Buffer.concat([
|
|
63
|
+
Buffer.from("WebPush: info\0", "utf8"),
|
|
64
|
+
uaPublic,
|
|
65
|
+
asPublic,
|
|
66
|
+
]);
|
|
67
|
+
const ikm = hkdf(authSecret, ecdhSecret, keyInfo, 32);
|
|
68
|
+
const prk = hmac(salt, ikm); // HKDF-Extract(salt, IKM)
|
|
69
|
+
const cek = hkdfExpand(
|
|
70
|
+
prk,
|
|
71
|
+
Buffer.from("Content-Encoding: aes128gcm\0", "utf8"),
|
|
72
|
+
16,
|
|
73
|
+
);
|
|
74
|
+
const nonce = hkdfExpand(
|
|
75
|
+
prk,
|
|
76
|
+
Buffer.from("Content-Encoding: nonce\0", "utf8"),
|
|
77
|
+
12,
|
|
78
|
+
);
|
|
79
|
+
return { cek, nonce };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Encrypt a payload for a Web Push subscription (RFC 8291, aes128gcm). Returns
|
|
84
|
+
* the full content-encoded body (header + ciphertext). Generates a fresh
|
|
85
|
+
* ephemeral key each call unless one is injected (tests).
|
|
86
|
+
*/
|
|
87
|
+
export function encryptWebPushPayload({
|
|
88
|
+
payload,
|
|
89
|
+
uaPublicKey,
|
|
90
|
+
authSecret,
|
|
91
|
+
ecdh,
|
|
92
|
+
salt,
|
|
93
|
+
}) {
|
|
94
|
+
const server = ecdh || crypto.createECDH("prime256v1");
|
|
95
|
+
if (!ecdh) server.generateKeys();
|
|
96
|
+
const asPublic = server.getPublicKey();
|
|
97
|
+
const ecdhSecret = server.computeSecret(uaPublicKey);
|
|
98
|
+
const useSalt = salt || crypto.randomBytes(16);
|
|
99
|
+
const { cek, nonce } = deriveContentKeys({
|
|
100
|
+
ecdhSecret,
|
|
101
|
+
authSecret,
|
|
102
|
+
salt: useSalt,
|
|
103
|
+
uaPublic: uaPublicKey,
|
|
104
|
+
asPublic,
|
|
105
|
+
});
|
|
106
|
+
const data = Buffer.isBuffer(payload)
|
|
107
|
+
? payload
|
|
108
|
+
: Buffer.from(payload, "utf8");
|
|
109
|
+
const record = Buffer.concat([data, Buffer.from([0x02])]); // last-record delimiter
|
|
110
|
+
const cipher = crypto.createCipheriv("aes-128-gcm", cek, nonce);
|
|
111
|
+
const ciphertext = Buffer.concat([
|
|
112
|
+
cipher.update(record),
|
|
113
|
+
cipher.final(),
|
|
114
|
+
cipher.getAuthTag(),
|
|
115
|
+
]);
|
|
116
|
+
const header = Buffer.concat([
|
|
117
|
+
useSalt,
|
|
118
|
+
uint32be(RECORD_SIZE),
|
|
119
|
+
Buffer.from([asPublic.length]),
|
|
120
|
+
asPublic,
|
|
121
|
+
]);
|
|
122
|
+
return Buffer.concat([header, ciphertext]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Inverse of {@link encryptWebPushPayload}; used for interop tests. */
|
|
126
|
+
export function decryptWebPushPayload({ body, uaEcdh, authSecret }) {
|
|
127
|
+
const salt = body.subarray(0, 16);
|
|
128
|
+
const idlen = body.readUInt8(20);
|
|
129
|
+
const asPublic = body.subarray(21, 21 + idlen);
|
|
130
|
+
const ciphertext = body.subarray(21 + idlen);
|
|
131
|
+
const ecdhSecret = uaEcdh.computeSecret(asPublic);
|
|
132
|
+
const { cek, nonce } = deriveContentKeys({
|
|
133
|
+
ecdhSecret,
|
|
134
|
+
authSecret,
|
|
135
|
+
salt,
|
|
136
|
+
uaPublic: uaEcdh.getPublicKey(),
|
|
137
|
+
asPublic,
|
|
138
|
+
});
|
|
139
|
+
const tag = ciphertext.subarray(ciphertext.length - 16);
|
|
140
|
+
const body2 = ciphertext.subarray(0, ciphertext.length - 16);
|
|
141
|
+
const decipher = crypto.createDecipheriv("aes-128-gcm", cek, nonce);
|
|
142
|
+
decipher.setAuthTag(tag);
|
|
143
|
+
const record = Buffer.concat([decipher.update(body2), decipher.final()]);
|
|
144
|
+
let end = record.length - 1;
|
|
145
|
+
while (end >= 0 && record[end] === 0x00) end -= 1; // strip padding
|
|
146
|
+
return record.subarray(0, end); // drop the 0x02 delimiter
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Builds an EC private key object from raw base64url VAPID keys. */
|
|
150
|
+
function importVapidKey(publicKeyB64url, privateKeyB64url) {
|
|
151
|
+
const pub = fromBase64url(publicKeyB64url);
|
|
152
|
+
if (pub.length !== 65 || pub[0] !== 0x04) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"VAPID public key must be a 65-byte uncompressed P-256 key",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return crypto.createPrivateKey({
|
|
158
|
+
key: {
|
|
159
|
+
kty: "EC",
|
|
160
|
+
crv: "P-256",
|
|
161
|
+
d: privateKeyB64url,
|
|
162
|
+
x: base64url(pub.subarray(1, 33)),
|
|
163
|
+
y: base64url(pub.subarray(33, 65)),
|
|
164
|
+
},
|
|
165
|
+
format: "jwk",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Mints + caches a VAPID (RFC 8292) ES256 JWT per push-endpoint origin. */
|
|
170
|
+
export class VapidTokenProvider {
|
|
171
|
+
constructor(options = {}) {
|
|
172
|
+
if (!options.publicKey)
|
|
173
|
+
throw new Error("VapidTokenProvider requires publicKey");
|
|
174
|
+
if (!options.privateKey)
|
|
175
|
+
throw new Error("VapidTokenProvider requires privateKey");
|
|
176
|
+
if (!options.subject) {
|
|
177
|
+
throw new Error("VapidTokenProvider requires a subject (mailto: or URL)");
|
|
178
|
+
}
|
|
179
|
+
this.publicKey = options.publicKey;
|
|
180
|
+
this.subject = options.subject;
|
|
181
|
+
this.now = options.now || Date.now;
|
|
182
|
+
this._key =
|
|
183
|
+
options.key || importVapidKey(options.publicKey, options.privateKey);
|
|
184
|
+
this.sign =
|
|
185
|
+
options.sign ||
|
|
186
|
+
((input, key) =>
|
|
187
|
+
crypto.sign("sha256", Buffer.from(input), {
|
|
188
|
+
key,
|
|
189
|
+
dsaEncoding: "ieee-p1363",
|
|
190
|
+
}));
|
|
191
|
+
this._cache = new Map(); // origin → { token, expiresAt }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
getToken(aud) {
|
|
195
|
+
const now = this.now();
|
|
196
|
+
const cached = this._cache.get(aud);
|
|
197
|
+
if (cached && cached.expiresAt - VAPID_SKEW_MS > now) return cached.token;
|
|
198
|
+
const header = base64url(JSON.stringify({ typ: "JWT", alg: "ES256" }));
|
|
199
|
+
const payload = base64url(
|
|
200
|
+
JSON.stringify({
|
|
201
|
+
aud,
|
|
202
|
+
exp: Math.floor((now + VAPID_TTL_MS) / 1000),
|
|
203
|
+
sub: this.subject,
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
const signingInput = `${header}.${payload}`;
|
|
207
|
+
const token = `${signingInput}.${base64url(this.sign(signingInput, this._key))}`;
|
|
208
|
+
this._cache.set(aud, { token, expiresAt: now + VAPID_TTL_MS });
|
|
209
|
+
return token;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Sends one encrypted Web Push message to a browser PushSubscription. */
|
|
214
|
+
export class WebPushSender {
|
|
215
|
+
constructor(options = {}) {
|
|
216
|
+
if (!options.vapid)
|
|
217
|
+
throw new Error("WebPushSender requires a vapid provider");
|
|
218
|
+
this.vapid = options.vapid;
|
|
219
|
+
this.fetch = options.fetch || globalThis.fetch;
|
|
220
|
+
this.ttl = options.ttl || DEFAULT_TTL_SECONDS;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
get handler() {
|
|
224
|
+
return (payload) => this.send(payload);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async send({ token, notification, sessionId, clientId } = {}) {
|
|
228
|
+
if (!token) throw new Error("Web Push send requires a subscription");
|
|
229
|
+
const subscription = typeof token === "string" ? JSON.parse(token) : token;
|
|
230
|
+
const endpoint = subscription?.endpoint;
|
|
231
|
+
const p256dh = subscription?.keys?.p256dh;
|
|
232
|
+
const auth = subscription?.keys?.auth;
|
|
233
|
+
if (!endpoint || !p256dh || !auth) {
|
|
234
|
+
throw new Error("Web Push subscription is missing endpoint/keys");
|
|
235
|
+
}
|
|
236
|
+
const message = { type: "remote-session.approval-request" };
|
|
237
|
+
if (notification?.title) message.title = notification.title;
|
|
238
|
+
if (notification?.body) message.body = notification.body;
|
|
239
|
+
if (sessionId != null) message.sessionId = String(sessionId);
|
|
240
|
+
if (clientId != null) message.clientId = String(clientId);
|
|
241
|
+
|
|
242
|
+
const body = encryptWebPushPayload({
|
|
243
|
+
payload: JSON.stringify(message),
|
|
244
|
+
uaPublicKey: fromBase64url(p256dh),
|
|
245
|
+
authSecret: fromBase64url(auth),
|
|
246
|
+
});
|
|
247
|
+
const origin = new URL(endpoint).origin;
|
|
248
|
+
const res = await this.fetch(endpoint, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
Authorization: `vapid t=${this.vapid.getToken(origin)}, k=${this.vapid.publicKey}`,
|
|
252
|
+
"Content-Encoding": "aes128gcm",
|
|
253
|
+
"Content-Type": "application/octet-stream",
|
|
254
|
+
TTL: String(this.ttl),
|
|
255
|
+
Urgency: "high",
|
|
256
|
+
},
|
|
257
|
+
body,
|
|
258
|
+
});
|
|
259
|
+
if (res.status === 201 || res.ok) {
|
|
260
|
+
return { id: res.headers?.get?.("location") || null };
|
|
261
|
+
}
|
|
262
|
+
// 404/410 mean the subscription is gone — prune it rather than retry.
|
|
263
|
+
if (res.status === 404 || res.status === 410) {
|
|
264
|
+
throw new PushTokenUnregisteredError(
|
|
265
|
+
`Web Push subscription expired (${res.status})`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
throw new Error(`Web Push failed (${res.status})`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Build a bound Web Push `sender` from env, or null when VAPID is unconfigured.
|
|
274
|
+
*
|
|
275
|
+
* Web env:
|
|
276
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VAPID_PUBLIC_KEY (base64url, 65-byte P-256)
|
|
277
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VAPID_PRIVATE_KEY (base64url, 32-byte scalar)
|
|
278
|
+
* CHAINLESSCHAIN_REMOTE_SESSION_VAPID_SUBJECT (mailto: or https URL)
|
|
279
|
+
*/
|
|
280
|
+
export function createWebPushSender(env = {}, options = {}) {
|
|
281
|
+
const publicKey =
|
|
282
|
+
options.vapidPublicKey ||
|
|
283
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_VAPID_PUBLIC_KEY;
|
|
284
|
+
const privateKey =
|
|
285
|
+
options.vapidPrivateKey ||
|
|
286
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_VAPID_PRIVATE_KEY;
|
|
287
|
+
const subject =
|
|
288
|
+
options.vapidSubject || env.CHAINLESSCHAIN_REMOTE_SESSION_VAPID_SUBJECT;
|
|
289
|
+
if (!publicKey || !privateKey || !subject) return null;
|
|
290
|
+
let vapid;
|
|
291
|
+
try {
|
|
292
|
+
vapid =
|
|
293
|
+
options.vapid ||
|
|
294
|
+
new VapidTokenProvider({ publicKey, privateKey, subject, ...options });
|
|
295
|
+
} catch {
|
|
296
|
+
return null; // Malformed VAPID keys — stay disabled rather than crash.
|
|
297
|
+
}
|
|
298
|
+
return new WebPushSender({ vapid, ...options }).handler;
|
|
299
|
+
}
|