chainlesschain 0.162.149 → 0.162.151
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 +3 -2
- package/src/assets/web-panel/assets/{AIOps-BJif14yR.js → AIOps-DuEOzebi.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXek6gJY.js → ActionButton-C6GiJa5H.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BgQhdAJi.js → Analytics-BBIiL_pO.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DTYl5NtR.js → AppLayout-CuI2gZRy.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-BzATQAeR.js → Audit-CbUjfr-K.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BzdPEQhL.js → Backup-CsSW9ljf.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BM0KVh8E.js → BaseInput-kM3rLe7F.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D7fHXHkz.js → Chat-kfE51YN3.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-Wm34RR-b.js → ChatBubbleRenderer-Bg2FTW6A.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CAiSQ9vO.js → Checkbox-jYb1uj-M.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Df40CrEe.js → Codegen-X9XiD5Lj.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DIT2fNFU.js → Col-BfbHu1xI.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CSTIbW2k.js → Community-c4oJMFS-.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DtqXZZUi.js → Compact-BFmnZpH7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CUtzw6o7.js → Compliance-s9shVHBS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CezmlnmU.js → Cowork-C5da--TC.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-P4BITarg.js → Cron-9kNbyNE4.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BRRiYwvq.js → Crosschain-C1k3NJD4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-L5ijB9i4.js → DID-DkheEvNK.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CFxro4ob.js → Dashboard-C3FuSGmJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BmAFCQ71.js → Dropdown-CAV6FMFD.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DJBCEuon.js → EmailListRenderer-Cc-Ulc4l.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-z4oLq6eT.js → FamilyGuardDashboard-8CitHEtv.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsVCpHMF.js → Federation-DFSZ5KDt.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DP3bP5th.js → FormItemContext-CEWCtnG2.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BAXP2Gc6.js → GenericCardRenderer-CwRzNzmX.js} +1 -1
- package/src/assets/web-panel/assets/{Git-I3g_bAw9.js → Git-0rgDM7cJ.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jHS5-ioS.js → Governance-CfBKtK9F.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CPL7sfx9.js → Inference-CzcUVvud.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Jf236h4C.js → KnowledgeGraph-Dwc4YL4A.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Cm2tcSKg.js → Logs-DgFPwR2F.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CZK82rhi.js → Marketplace-DnpPTbGU.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CZCeji7a.js → McpTools-DRxxINyW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-3l2KVS9k.js → Memory-OhmW_6Z3.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-VYdpoLh6.js → MobileBridge-DkTW-RK5.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CiB9cmm1.js → MobileProjects-DpCGV_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-wCFZbBuL.js → Mtc--vmkL0AS.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-1a3THIyG.js → MtcAudit-DGoFWjD4.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DMzLB2hG.js → Multisig-B4kWgq95.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D9wZbf6l.js → NLProgramming-YQwvommE.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-hrFe2ZM-.js → Notes-CgjtUADJ.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CHGX5Jwm.js → NotificationSettings-6JDrpBG5.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-PM6IlxjO.js +1 -0
- package/src/assets/web-panel/assets/{Organization-By7FkhtY.js → Organization-8McOT_OF.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow--khGSzJn.js → Overflow-DAZpSCGc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CAG9dH35.js → P2P-B7l2ZWLy.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B3SQZmK9.js → PdhVaultBrowser-W1Ud-NAL.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D35ec6JA.js → Permissions-Czm_w9u_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CxBF7hW_.js → PersonalDataHub-B4F0_ZVM.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-s6EtOO1s.js → Pipeline-B37aFiWv.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BbJYmWmO.js → Privacy-94KBnRrD.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-VGQq1jMT.js → ProjectInit-B5h8_dlh.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B1ew3Teh.js → ProjectSettings-lmeI3Lpm.js} +2 -2
- package/src/assets/web-panel/assets/Projects-D5AYOasl.js +1 -0
- package/src/assets/web-panel/assets/{Providers-mrO47FIK.js → Providers-B3cNWwJg.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-IJF2mHyw.js → QrScannerModal-CHRvOw1R.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CU0wN_Z0.js → QuickAsk-D1JMgFEd.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DhN37R6G.js → Recommend-EYWGR79p.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-D_xOX_hu.js → RemoteSession-BAMC2pJt.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D_Ssv7uH.js → Reputation-X2Hk1XG_.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Kx5dKxX7.js → Row-Eq98LRtU.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BlGwqZkY.js → RssFeed-i76s6z_o.js} +2 -2
- package/src/assets/web-panel/assets/{Search-hLFQzxpb.js → Search-D_Es7CAI.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DMlvsVt4.js → Security-kayD2e94.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Bd0Qsj2m.js → Services-Ctsm6ul8.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DFclq9X3.js → Skeleton-CRNYlfdf.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BNtln2qY.js → Skills-DkZYpF4-.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Dch5H19G.js → Sla-DXkoBcOP.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-2UfQcU1g.js → SpeechSettings-DXi_GI1d.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CD9YQr4i.js → SyncSettings-rNKpfomn.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BNu-7MzI.js → Tasks-XF4uPtG-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D1n9CaIR.js → Templates-i0Hzfeyu.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DenL3iBW.js → Tenant-sMpHAi27.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-BoZvaYAl.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-D87IfukO.js → TimelineRenderer-yuC6K7Rf.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BkiZc8E3.js → Tokens-B8O8i0s6.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BNRSytGN.js → Trigger-Bv6yrlCr.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bwbd4X2m.js → Trust-C4mW95Ev.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B8O2bs0o.js → UkeySign-DV6yKaHn.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C2fL8zmH.js → VideoEditing-UI479-sD.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BORYDdE1.js → Wallet-M5oV9EVP.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C0V5S2FM.js → WebAuthn-DDyDHPDR.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-2dR0fcHL.js → WorkflowEditor-RoBv4rqk.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BmuAFAn8.js → chat-Cw1mE5LS.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CN3smMcK.js → colors-BFP62Gwf.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DCnxb4kW.js → compact-item-17HL6pyC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-vlR43pHn.js → createContext-D55X4fZX.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CqbHYuRc.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D04tHYFd.js → hasIn-D3E5mNsP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbVsQBOH.js → index--44J50mc.js} +1 -1
- package/src/assets/web-panel/assets/{index-TslMTwNy.js → index-ASPYTzMK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTOCnIQ7.js → index-BC6zxOlU.js} +1 -1
- package/src/assets/web-panel/assets/{index-ljuBiQW9.js → index-BEa1RW4c.js} +1 -1
- package/src/assets/web-panel/assets/{index-jzR7Ujuw.js → index-BQmZg5si.js} +1 -1
- package/src/assets/web-panel/assets/{index-BudvKQfG.js → index-BY_xZ2jy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP9m9Ggr.js → index-BgN5G5vF.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cr-A0FYJ.js → index-BnGpft-Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5LZVZ0L.js → index-Bq83UBDr.js} +1 -1
- package/src/assets/web-panel/assets/{index-ToUh2b1-.js → index-Bzpp2z2S.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbADHwhr.js → index-CFYGn88j.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClhAHDK3.js → index-CGyTos4p.js} +1 -1
- package/src/assets/web-panel/assets/index-CKBI1U5K.js +1 -0
- package/src/assets/web-panel/assets/{index-C-6NO5il.js → index-CLEzsZ8A.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8vwp4qp.js → index-CR9rNUvi.js} +1 -1
- package/src/assets/web-panel/assets/{index-_FIkN3jd.js → index-CW6LLoJO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D65_XseV.js → index-C_MfcrLf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnXebZLL.js → index-Cdvk2_2q.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_n8_vfI.js → index-CjCwi7we.js} +1 -1
- package/src/assets/web-panel/assets/index-CkLp6DTO.js +1 -0
- package/src/assets/web-panel/assets/{index-DDwLgQY9.js → index-CvCFKojj.js} +1 -1
- package/src/assets/web-panel/assets/{index-430S68MN.js → index-D4LHSde4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXRg8GFQ.js → index-DCLu84x-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTtRscUa.js → index-DD_qGBrt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BasvsWDM.js → index-DIRvdwBX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeGDEXFs.js → index-DOjA8bvj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsAsCPJ6.js → index-Da27u6j0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D28DeAAB.js → index-DkBVYlE4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CngCC8hC.js → index-DtAnPBWP.js} +1 -1
- package/src/assets/web-panel/assets/{index-B78xL3s2.js → index-DzG7mkgW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIpY0qM5.js → index-H9_X1Cg_.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5Sxb1sE.js → index-ITeEHb8L.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTPEZTk1.js → index-KtLJXn4s.js} +1 -1
- package/src/assets/web-panel/assets/{index-hpvvtAZ3.js → index-RYZgFIwo.js} +1 -1
- package/src/assets/web-panel/assets/{index-HhNjnCfe.js → index-gAVxDiTZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTWg-18U.js → index-paVubXzn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqhASEL4.js → index-tUH_6bdJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYAtmqiv.js → index-tVRPgYGr.js} +1 -1
- package/src/assets/web-panel/assets/{index-0DnaZGkC.js → index-uSsiwgZ2.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C3wUb4je.js → initDefaultProps-Ckp7xVZS.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D-jYFUNA.js → motion-DUZgCpn6.js} +1 -1
- package/src/assets/web-panel/assets/{move-tqb8nwJ2.js → move-B2rttglH.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DSOjMJMz.js → mtc-parser-BYWcLTuN.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CJ7VimIW.js → omit-DhHqKeFx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qXiG1iT3.js → pickAttrs-ANpJaWMO.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CkGBcy1Z.js → placementArrow-CfZWe7CA.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-D3WvTlLO.js → responsiveObserve-D7Xz2y-R.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BEE2ftge.js → slide-DekqCBY9.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CsfBpxiG.js → statusUtils-BET6XU7h.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-xPrIiJPI.js → styleChecker-qHmJjV62.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C5Yd-SCR.js → useFlexGapSupport-BoMCTNyu.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-D_MDS8HI.js → useFs-TdW-NgFC.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqJh-usA.js → usePersonalDataHub-Dyox7wCu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-chm_p-gz.js → vnode-XUn5S8CP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-3-YDNz0Z.js → zoom-Ls77i8hn.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-manifest.json +8 -1
- package/src/commands/a2a.js +19 -4
- package/src/commands/activitypub.js +20 -3
- package/src/commands/agent.js +27 -5
- package/src/commands/audit.js +40 -24
- package/src/commands/changelog.js +148 -0
- package/src/commands/codeintel.js +68 -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 +7 -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 +125 -5
- 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 +9 -2
- 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/data/changelog.json +189 -0
- package/src/gateways/ws/message-dispatcher.js +5 -2
- package/src/gateways/ws/remote-session-protocol.js +110 -42
- package/src/harness/jsonl-session-store.js +12 -5
- package/src/harness/plugin-manager.js +16 -4
- package/src/harness/remote-command-ledger.js +46 -15
- package/src/harness/worktree-isolator.js +16 -6
- package/src/index.js +2 -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 +2 -0
- package/src/lib/agent-economy.js +262 -29
- package/src/lib/agent-network.js +7 -1
- package/src/lib/agent-team/team-runner.js +25 -9
- package/src/lib/agent-team/team-worktree.js +36 -4
- package/src/lib/async-hook-supervisor.cjs +102 -16
- package/src/lib/audit-logger.js +7 -6
- package/src/lib/autonomous-developer.js +60 -0
- package/src/lib/changelog.js +252 -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 +89 -1
- package/src/lib/eval/tasks.js +170 -0
- package/src/lib/eval/trend.js +16 -1
- 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__/lsp-client.test.js +11 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +69 -0
- package/src/lib/lsp/benchmark.js +257 -0
- package/src/lib/lsp/lsp-client.js +30 -9
- package/src/lib/lsp/lsp-manager.js +54 -2
- package/src/lib/lsp/lsp-server-registry.js +15 -4
- 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-runtime/__tests__/remote-source.test.js +256 -0
- package/src/lib/plugin-runtime/hooks.js +20 -4
- package/src/lib/plugin-runtime/install.js +9 -1
- package/src/lib/plugin-runtime/monitors.js +20 -4
- package/src/lib/plugin-runtime/policy.js +9 -1
- package/src/lib/plugin-runtime/remote-source.js +240 -0
- package/src/lib/plugin-runtime/signature.js +16 -0
- package/src/lib/pqc-manager.js +64 -0
- package/src/lib/reputation-optimizer.js +101 -0
- package/src/lib/sandbox-egress-proxy.js +195 -59
- package/src/lib/sandbox-fs-policy.js +112 -0
- package/src/lib/sandbox-network-policy.js +44 -5
- package/src/lib/sandbox-v2.js +14 -14
- package/src/lib/scim-manager.js +36 -0
- package/src/lib/session-manager.js +5 -2
- package/src/lib/settings-hooks.cjs +1 -0
- package/src/lib/siem-exporter.js +45 -0
- package/src/lib/skill-loader.js +20 -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 +12 -1
- 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 +17 -2
- package/src/runtime/__tests__/auto-pin.test.js +104 -0
- package/src/runtime/agent-core.js +241 -68
- package/src/runtime/auto-pin.js +117 -0
- package/src/runtime/headless-runner.js +35 -0
- package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +0 -1
- package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +0 -1
- package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +0 -3
- package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +0 -1
- package/src/assets/web-panel/assets/index-Cg2ldRfU.js +0 -1
- package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +0 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remote-source tests — resolving a plugin from a hosted registry/manifest URL,
|
|
3
|
+
* with private-token auth and an offline cache. `fetch` and fs are injected via
|
|
4
|
+
* `_deps` so nothing hits the network or the real disk.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
isRemoteSource,
|
|
10
|
+
validateRegistry,
|
|
11
|
+
resolvePluginEntry,
|
|
12
|
+
listRegistryPlugins,
|
|
13
|
+
resolveRegistryToken,
|
|
14
|
+
fetchRegistry,
|
|
15
|
+
resolveRemoteSource,
|
|
16
|
+
registryCachePath,
|
|
17
|
+
_deps,
|
|
18
|
+
} from "../remote-source.js";
|
|
19
|
+
|
|
20
|
+
const REGISTRY = {
|
|
21
|
+
name: "acme",
|
|
22
|
+
plugins: [
|
|
23
|
+
{
|
|
24
|
+
name: "toml-tools",
|
|
25
|
+
source: "https://github.com/x/toml.git",
|
|
26
|
+
ref: "v1.0.0",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
description: "TOML LSP",
|
|
29
|
+
sha256: "abc123",
|
|
30
|
+
},
|
|
31
|
+
{ name: "py-helpers", source: "owner/py-helpers" },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A fake in-memory fs + fetch wired into _deps. */
|
|
36
|
+
function install(fakes = {}) {
|
|
37
|
+
const files = new Map(fakes.files || []);
|
|
38
|
+
_deps.existsSync = (p) => files.has(String(p));
|
|
39
|
+
_deps.readFileSync = (p) => {
|
|
40
|
+
if (!files.has(String(p))) throw new Error("ENOENT");
|
|
41
|
+
return files.get(String(p));
|
|
42
|
+
};
|
|
43
|
+
_deps.writeFileSync = (p, text) => files.set(String(p), text);
|
|
44
|
+
_deps.mkdirSync = () => {};
|
|
45
|
+
if (fakes.fetch) _deps.fetch = fakes.fetch;
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Build a fetch that returns JSON once, then throws (to test caching). */
|
|
50
|
+
function okThenOffline(body) {
|
|
51
|
+
let calls = 0;
|
|
52
|
+
return vi.fn(async () => {
|
|
53
|
+
calls++;
|
|
54
|
+
if (calls === 1) {
|
|
55
|
+
return { ok: true, status: 200, text: async () => JSON.stringify(body) };
|
|
56
|
+
}
|
|
57
|
+
throw new Error("ECONNREFUSED");
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let orig;
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
orig = { ..._deps };
|
|
64
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
65
|
+
});
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
Object.assign(_deps, orig);
|
|
68
|
+
vi.restoreAllMocks();
|
|
69
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("isRemoteSource", () => {
|
|
73
|
+
it("recognizes http(s) .json URLs", () => {
|
|
74
|
+
expect(isRemoteSource("https://x.com/registry.json")).toBe(true);
|
|
75
|
+
expect(isRemoteSource("http://x.com/a/b.json?ref=1")).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it("rejects local dirs, git URLs, and owner/repo", () => {
|
|
78
|
+
expect(isRemoteSource("./plugins/foo")).toBe(false);
|
|
79
|
+
expect(isRemoteSource("https://github.com/x/foo.git")).toBe(false);
|
|
80
|
+
expect(isRemoteSource("owner/repo")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("validateRegistry", () => {
|
|
85
|
+
it("accepts a multi-plugin index", () => {
|
|
86
|
+
expect(validateRegistry(REGISTRY).plugins).toHaveLength(2);
|
|
87
|
+
});
|
|
88
|
+
it("wraps a single-plugin manifest into a one-entry registry", () => {
|
|
89
|
+
const r = validateRegistry({
|
|
90
|
+
name: "solo",
|
|
91
|
+
source: "owner/solo",
|
|
92
|
+
ref: "v2",
|
|
93
|
+
});
|
|
94
|
+
expect(r.plugins).toEqual([
|
|
95
|
+
{ name: "solo", source: "owner/solo", ref: "v2" },
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
it("rejects garbage and entries missing name/source", () => {
|
|
99
|
+
expect(() => validateRegistry(null)).toThrow(/not a JSON object/);
|
|
100
|
+
expect(() => validateRegistry({ foo: 1 })).toThrow(
|
|
101
|
+
/plugins.*array.*source/,
|
|
102
|
+
);
|
|
103
|
+
expect(() => validateRegistry({ plugins: [{ name: "x" }] })).toThrow(
|
|
104
|
+
/missing name\/source/,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("resolvePluginEntry", () => {
|
|
110
|
+
it("selects by name", () => {
|
|
111
|
+
expect(resolvePluginEntry(REGISTRY, "py-helpers").source).toBe(
|
|
112
|
+
"owner/py-helpers",
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
it("auto-selects the sole plugin when name omitted", () => {
|
|
116
|
+
const solo = { plugins: [REGISTRY.plugins[0]] };
|
|
117
|
+
expect(resolvePluginEntry(solo).name).toBe("toml-tools");
|
|
118
|
+
});
|
|
119
|
+
it("requires a name when the registry has many", () => {
|
|
120
|
+
expect(() => resolvePluginEntry(REGISTRY)).toThrow(/pick one with --name/);
|
|
121
|
+
});
|
|
122
|
+
it("lists options when the name is unknown", () => {
|
|
123
|
+
expect(() => resolvePluginEntry(REGISTRY, "nope")).toThrow(
|
|
124
|
+
/not found.*toml-tools, py-helpers/s,
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("resolveRegistryToken", () => {
|
|
130
|
+
it("prefers an explicit token, then env, then per-host config", () => {
|
|
131
|
+
expect(
|
|
132
|
+
resolveRegistryToken("https://h/r.json", { token: "explicit" }),
|
|
133
|
+
).toBe("explicit");
|
|
134
|
+
process.env.CC_PLUGIN_REGISTRY_TOKEN = "envtok";
|
|
135
|
+
expect(resolveRegistryToken("https://h/r.json", {})).toBe("envtok");
|
|
136
|
+
delete process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
137
|
+
const config = { plugins: { registryTokens: { "priv.co": "cfgtok" } } };
|
|
138
|
+
expect(resolveRegistryToken("https://priv.co/r.json", { config })).toBe(
|
|
139
|
+
"cfgtok",
|
|
140
|
+
);
|
|
141
|
+
expect(
|
|
142
|
+
resolveRegistryToken("https://other.co/r.json", { config }),
|
|
143
|
+
).toBeNull();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("fetchRegistry", () => {
|
|
148
|
+
it("fetches, validates, and caches; sends a bearer token when given", async () => {
|
|
149
|
+
const fetch = vi.fn(async () => ({
|
|
150
|
+
ok: true,
|
|
151
|
+
status: 200,
|
|
152
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
153
|
+
}));
|
|
154
|
+
install({ fetch });
|
|
155
|
+
const { registry, fromCache } = await fetchRegistry("https://h/r.json", {
|
|
156
|
+
token: "t",
|
|
157
|
+
cacheDir: "/cache",
|
|
158
|
+
});
|
|
159
|
+
expect(fromCache).toBe(false);
|
|
160
|
+
expect(registry.plugins).toHaveLength(2);
|
|
161
|
+
const [, init] = fetch.mock.calls[0];
|
|
162
|
+
expect(init.headers.Authorization).toBe("Bearer t");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("falls back to the offline cache when the network fails", async () => {
|
|
166
|
+
const fetch = okThenOffline(REGISTRY);
|
|
167
|
+
install({ fetch });
|
|
168
|
+
// 1st call populates the cache
|
|
169
|
+
await fetchRegistry("https://h/r.json", { cacheDir: "/cache" });
|
|
170
|
+
// 2nd call: network throws → served from cache
|
|
171
|
+
const { registry, fromCache } = await fetchRegistry("https://h/r.json", {
|
|
172
|
+
cacheDir: "/cache",
|
|
173
|
+
});
|
|
174
|
+
expect(fromCache).toBe(true);
|
|
175
|
+
expect(registry.plugins).toHaveLength(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("throws on HTTP error with no cache", async () => {
|
|
179
|
+
const fetch = vi.fn(async () => ({
|
|
180
|
+
ok: false,
|
|
181
|
+
status: 404,
|
|
182
|
+
statusText: "Not Found",
|
|
183
|
+
text: async () => "",
|
|
184
|
+
}));
|
|
185
|
+
install({ fetch });
|
|
186
|
+
await expect(
|
|
187
|
+
fetchRegistry("https://h/missing.json", { cacheDir: "/cache" }),
|
|
188
|
+
).rejects.toThrow(/HTTP 404/);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("throws when offline and nothing is cached", async () => {
|
|
192
|
+
const fetch = vi.fn(async () => {
|
|
193
|
+
throw new Error("ENOTFOUND");
|
|
194
|
+
});
|
|
195
|
+
install({ fetch });
|
|
196
|
+
await expect(
|
|
197
|
+
fetchRegistry("https://h/r.json", { cacheDir: "/cache" }),
|
|
198
|
+
).rejects.toThrow(/no offline cache/);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("resolveRemoteSource", () => {
|
|
203
|
+
it("resolves a named entry to a git source string with a #ref and carries sha256", async () => {
|
|
204
|
+
const fetch = vi.fn(async () => ({
|
|
205
|
+
ok: true,
|
|
206
|
+
status: 200,
|
|
207
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
208
|
+
}));
|
|
209
|
+
install({ fetch });
|
|
210
|
+
const r = await resolveRemoteSource("https://h/r.json", {
|
|
211
|
+
name: "toml-tools",
|
|
212
|
+
cacheDir: "/cache",
|
|
213
|
+
});
|
|
214
|
+
expect(r.source).toBe("https://github.com/x/toml.git#v1.0.0");
|
|
215
|
+
expect(r.sha256).toBe("abc123");
|
|
216
|
+
expect(r.fromCache).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("omits the #ref when the entry has none", async () => {
|
|
220
|
+
const fetch = vi.fn(async () => ({
|
|
221
|
+
ok: true,
|
|
222
|
+
status: 200,
|
|
223
|
+
text: async () => JSON.stringify(REGISTRY),
|
|
224
|
+
}));
|
|
225
|
+
install({ fetch });
|
|
226
|
+
const r = await resolveRemoteSource("https://h/r.json", {
|
|
227
|
+
name: "py-helpers",
|
|
228
|
+
cacheDir: "/cache",
|
|
229
|
+
});
|
|
230
|
+
expect(r.source).toBe("owner/py-helpers");
|
|
231
|
+
expect(r.ref).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("listRegistryPlugins", () => {
|
|
236
|
+
it("flattens entries for display", () => {
|
|
237
|
+
const rows = listRegistryPlugins(REGISTRY);
|
|
238
|
+
expect(rows.map((r) => r.name)).toEqual(["toml-tools", "py-helpers"]);
|
|
239
|
+
expect(rows[0]).toMatchObject({
|
|
240
|
+
version: "1.0.0",
|
|
241
|
+
ref: "v1.0.0",
|
|
242
|
+
description: "TOML LSP",
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("registryCachePath", () => {
|
|
248
|
+
it("is stable and content-addressed by URL", () => {
|
|
249
|
+
const a = registryCachePath("https://h/r.json", "/cache");
|
|
250
|
+
const b = registryCachePath("https://h/r.json", "/cache");
|
|
251
|
+
const c = registryCachePath("https://h/OTHER.json", "/cache");
|
|
252
|
+
expect(a).toBe(b);
|
|
253
|
+
expect(a).not.toBe(c);
|
|
254
|
+
expect(a.endsWith(".json")).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -60,11 +60,27 @@ export function collectPluginHooks(opts = {}) {
|
|
|
60
60
|
for (const p of trusted) {
|
|
61
61
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
62
62
|
const h = p.manifest.components?.hooks;
|
|
63
|
-
if (!h
|
|
63
|
+
if (!h) continue;
|
|
64
64
|
let parsed;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if (h.absPath) {
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(_deps.readFileSync(h.absPath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
} else if (h.inline) {
|
|
72
|
+
// Hooks declared inline in plugin.json — the normalized component keeps
|
|
73
|
+
// only counts, so re-read the raw manifest for the actual entries (same
|
|
74
|
+
// approach as the MCP collector). Without this, inline hooks never fire.
|
|
75
|
+
try {
|
|
76
|
+
const raw = JSON.parse(
|
|
77
|
+
_deps.readFileSync(p.manifest.manifestPath, "utf8"),
|
|
78
|
+
);
|
|
79
|
+
parsed = raw && typeof raw === "object" ? raw.hooks : null;
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
68
84
|
continue;
|
|
69
85
|
}
|
|
70
86
|
const map = normalizeHookMap(parsed);
|
|
@@ -320,6 +320,14 @@ export function uninstall(name, opts = {}) {
|
|
|
320
320
|
`${name}@${opts.version} is not installed at ${scope} scope`,
|
|
321
321
|
);
|
|
322
322
|
}
|
|
323
|
+
// Capture the active version BEFORE removing, so we only repoint `.active`
|
|
324
|
+
// when the version we removed WAS the active one. Removing a NON-active
|
|
325
|
+
// version (e.g. cleaning up a newer version after rolling back to an older
|
|
326
|
+
// pinned one) must not silently change which version is active — the old
|
|
327
|
+
// code always rewrote `.active` to the newest remaining, so uninstalling an
|
|
328
|
+
// unrelated version could bump the user's pinned choice.
|
|
329
|
+
const removedWasActive =
|
|
330
|
+
getActiveVersion(name, { scope, cwd }) === opts.version;
|
|
323
331
|
_deps.rmSync(dir, { recursive: true, force: true });
|
|
324
332
|
removed.push(opts.version);
|
|
325
333
|
// Repoint .active to the newest remaining version, or clear it.
|
|
@@ -333,7 +341,7 @@ export function uninstall(name, opts = {}) {
|
|
|
333
341
|
recursive: true,
|
|
334
342
|
force: true,
|
|
335
343
|
});
|
|
336
|
-
} else if (_deps.existsSync(activeFile)) {
|
|
344
|
+
} else if (removedWasActive && _deps.existsSync(activeFile)) {
|
|
337
345
|
_deps.writeFileSync(activeFile, remaining[0], "utf8");
|
|
338
346
|
}
|
|
339
347
|
return { removed };
|
|
@@ -79,11 +79,27 @@ export function collectPluginMonitors(opts = {}) {
|
|
|
79
79
|
for (const p of trusted) {
|
|
80
80
|
if (!p.manifest || p.manifest.ok !== true) continue;
|
|
81
81
|
const m = p.manifest.components?.monitors;
|
|
82
|
-
if (!m
|
|
82
|
+
if (!m) continue;
|
|
83
83
|
let parsed;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if (m.absPath) {
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(_deps.readFileSync(m.absPath, "utf8"));
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
} else if (m.inline) {
|
|
91
|
+
// Monitors declared inline in plugin.json — the normalized component keeps
|
|
92
|
+
// only counts, so re-read the raw manifest for the actual entries (same
|
|
93
|
+
// approach as the MCP collector). Without this, inline monitors never spawn.
|
|
94
|
+
try {
|
|
95
|
+
const raw = JSON.parse(
|
|
96
|
+
_deps.readFileSync(p.manifest.manifestPath, "utf8"),
|
|
97
|
+
);
|
|
98
|
+
parsed = raw && typeof raw === "object" ? raw.monitors : null;
|
|
99
|
+
} catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
87
103
|
continue;
|
|
88
104
|
}
|
|
89
105
|
const list = Array.isArray(parsed?.monitors)
|
|
@@ -43,7 +43,15 @@ export function loadManagedPluginPolicy(opts = {}) {
|
|
|
43
43
|
const key = String(
|
|
44
44
|
opts.managedSettingsFile || env.CC_MANAGED_SETTINGS || "<default>",
|
|
45
45
|
);
|
|
46
|
-
if (_cache.has(key))
|
|
46
|
+
if (_cache.has(key)) {
|
|
47
|
+
const cached = _cache.get(key);
|
|
48
|
+
// A cached failure sentinel must re-throw on EVERY call — otherwise the
|
|
49
|
+
// first call fails closed but later callers get the truthy {__invalid}
|
|
50
|
+
// object, treat it as an empty policy, and silently bypass the org's
|
|
51
|
+
// deny/allow/requireSignedPlugins enforcement (fail-open).
|
|
52
|
+
if (cached && cached.__invalid) throw cached.__invalid;
|
|
53
|
+
return cached;
|
|
54
|
+
}
|
|
47
55
|
let settings = null;
|
|
48
56
|
try {
|
|
49
57
|
const loaded = _deps.loadManagedSettings({
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote plugin sources — install from a hosted registry / manifest URL, not
|
|
3
|
+
* just a local dir or git URL (Phase 3 "远程 manifest" + "私有仓认证" + "离线
|
|
4
|
+
* seed cache" work items).
|
|
5
|
+
*
|
|
6
|
+
* A remote source is an `https?://…/*.json` document, in one of two shapes:
|
|
7
|
+
*
|
|
8
|
+
* 1. Registry index — many plugins, selected by name:
|
|
9
|
+
* { "name": "acme-registry",
|
|
10
|
+
* "plugins": [
|
|
11
|
+
* { "name": "toml-tools", "source": "https://github.com/x/toml.git",
|
|
12
|
+
* "ref": "v1.0.0", "version": "1.0.0", "description": "…", "sha256": "…" },
|
|
13
|
+
* { "name": "py-helpers", "source": "owner/py-helpers" }
|
|
14
|
+
* ] }
|
|
15
|
+
*
|
|
16
|
+
* 2. Single-plugin manifest — one plugin, no selection needed:
|
|
17
|
+
* { "name": "toml-tools", "source": "https://github.com/x/toml.git", "ref": "v1.0.0" }
|
|
18
|
+
*
|
|
19
|
+
* The remote layer is an INDIRECTION: it resolves a name → a git source string
|
|
20
|
+
* that the existing installer (`installFromSource`) already knows how to clone.
|
|
21
|
+
* This keeps the sync install core untouched and avoids bundling a tarball
|
|
22
|
+
* extractor. Downloaded registry JSON is cached content-addressed so browsing /
|
|
23
|
+
* installing works OFFLINE, and a bearer token can gate a PRIVATE registry.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from "fs";
|
|
27
|
+
import os from "os";
|
|
28
|
+
import path from "path";
|
|
29
|
+
import crypto from "crypto";
|
|
30
|
+
import { getElectronUserDataDir } from "../paths.js";
|
|
31
|
+
|
|
32
|
+
export const _deps = {
|
|
33
|
+
// Node 22+ global fetch; injectable for tests.
|
|
34
|
+
fetch: (...args) => globalThis.fetch(...args),
|
|
35
|
+
readFileSync: fs.readFileSync,
|
|
36
|
+
writeFileSync: fs.writeFileSync,
|
|
37
|
+
existsSync: fs.existsSync,
|
|
38
|
+
mkdirSync: fs.mkdirSync,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
42
|
+
|
|
43
|
+
/** True when `raw` looks like a remote registry/manifest URL (http(s) + .json). */
|
|
44
|
+
export function isRemoteSource(raw) {
|
|
45
|
+
const s = String(raw || "").trim();
|
|
46
|
+
return /^https?:\/\//i.test(s) && /\.json(\?.*)?(#.*)?$/i.test(s);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Where a fetched registry is cached (content-addressed by its URL). */
|
|
50
|
+
export function registryCachePath(url, cacheDir) {
|
|
51
|
+
const dir =
|
|
52
|
+
cacheDir || path.join(getElectronUserDataDir(), "plugin-registry-cache");
|
|
53
|
+
const hash = crypto
|
|
54
|
+
.createHash("sha256")
|
|
55
|
+
.update(String(url))
|
|
56
|
+
.digest("hex")
|
|
57
|
+
.slice(0, 32);
|
|
58
|
+
return path.join(dir, `${hash}.json`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the bearer token for a registry host from (in order): explicit
|
|
63
|
+
* `opts.token`, `CC_PLUGIN_REGISTRY_TOKEN` env, or a per-host map in config
|
|
64
|
+
* (`config.plugins.registryTokens[host]`). Returns null if none — a public
|
|
65
|
+
* registry needs no token.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveRegistryToken(url, { token, config } = {}) {
|
|
68
|
+
if (token) return token;
|
|
69
|
+
if (process.env.CC_PLUGIN_REGISTRY_TOKEN)
|
|
70
|
+
return process.env.CC_PLUGIN_REGISTRY_TOKEN;
|
|
71
|
+
try {
|
|
72
|
+
const host = new URL(url).host;
|
|
73
|
+
const map = config?.plugins?.registryTokens;
|
|
74
|
+
if (map && map[host]) return map[host];
|
|
75
|
+
} catch {
|
|
76
|
+
/* bad URL — handled by fetch */
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* GET a registry/manifest URL as JSON, with optional bearer auth. On success the
|
|
83
|
+
* body is written to the offline cache; on a network/HTTP failure we fall back
|
|
84
|
+
* to that cache (so a previously-seen registry still installs offline). Throws
|
|
85
|
+
* only when neither the network NOR the cache can produce a valid document.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Promise<{ registry: object, fromCache: boolean }>}
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchRegistry(url, opts = {}) {
|
|
90
|
+
const {
|
|
91
|
+
token,
|
|
92
|
+
cacheDir,
|
|
93
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
94
|
+
allowCache = true,
|
|
95
|
+
} = opts;
|
|
96
|
+
const cachePath = registryCachePath(url, cacheDir);
|
|
97
|
+
const headers = { Accept: "application/json" };
|
|
98
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
99
|
+
|
|
100
|
+
let networkErr = null;
|
|
101
|
+
try {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
104
|
+
let res;
|
|
105
|
+
try {
|
|
106
|
+
res = await _deps.fetch(url, {
|
|
107
|
+
headers,
|
|
108
|
+
redirect: "follow",
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
if (!res.ok) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`registry fetch failed: HTTP ${res.status} ${res.statusText || ""}`.trim(),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
const registry = validateRegistry(JSON.parse(text), url);
|
|
121
|
+
if (allowCache) writeCache(cachePath, text);
|
|
122
|
+
return { registry, fromCache: false };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
networkErr = err;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Network failed — try the offline cache.
|
|
128
|
+
if (allowCache && _deps.existsSync(cachePath)) {
|
|
129
|
+
try {
|
|
130
|
+
const text = _deps.readFileSync(cachePath, "utf8");
|
|
131
|
+
const registry = validateRegistry(JSON.parse(text), url);
|
|
132
|
+
return { registry, fromCache: true };
|
|
133
|
+
} catch {
|
|
134
|
+
/* cache unreadable — fall through to throw the network error */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new Error(
|
|
138
|
+
`could not fetch registry ${url}: ${networkErr ? networkErr.message : "unknown error"}` +
|
|
139
|
+
(allowCache ? " (no offline cache available)" : ""),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function writeCache(cachePath, text) {
|
|
144
|
+
try {
|
|
145
|
+
_deps.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
146
|
+
_deps.writeFileSync(cachePath, text, "utf8");
|
|
147
|
+
} catch {
|
|
148
|
+
/* caching is best-effort; a read-only cache dir must not break install */
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Validate + normalize a fetched document into a registry object. Throws on garbage. */
|
|
153
|
+
export function validateRegistry(doc, url = "") {
|
|
154
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
155
|
+
throw new Error(`registry at ${url} is not a JSON object`);
|
|
156
|
+
}
|
|
157
|
+
if (Array.isArray(doc.plugins)) {
|
|
158
|
+
for (const p of doc.plugins) {
|
|
159
|
+
if (!p || typeof p !== "object" || !p.name || !p.source) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`registry at ${url} has a plugin entry missing name/source`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return doc;
|
|
166
|
+
}
|
|
167
|
+
// Single-plugin manifest.
|
|
168
|
+
if (doc.source) {
|
|
169
|
+
return { plugins: [doc] };
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
`registry at ${url} must have a "plugins" array or a top-level "source"`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** List installable entries from a registry (for `cc plugin search`). */
|
|
177
|
+
export function listRegistryPlugins(registry) {
|
|
178
|
+
return (registry.plugins || []).map((p) => ({
|
|
179
|
+
name: p.name,
|
|
180
|
+
version: p.version || null,
|
|
181
|
+
source: p.source,
|
|
182
|
+
ref: p.ref || null,
|
|
183
|
+
description: p.description || "",
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Pick one plugin entry from a registry. When the registry has exactly one
|
|
189
|
+
* plugin, `name` is optional; otherwise it must match. Returns the entry, or
|
|
190
|
+
* throws with a helpful list.
|
|
191
|
+
*/
|
|
192
|
+
export function resolvePluginEntry(registry, name) {
|
|
193
|
+
const plugins = registry.plugins || [];
|
|
194
|
+
if (plugins.length === 0) throw new Error("registry has no plugins");
|
|
195
|
+
if (!name) {
|
|
196
|
+
if (plugins.length === 1) return plugins[0];
|
|
197
|
+
throw new Error(
|
|
198
|
+
`registry has ${plugins.length} plugins — pick one with --name <plugin>:\n ` +
|
|
199
|
+
plugins.map((p) => p.name).join(", "),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const entry = plugins.find((p) => p.name === name);
|
|
203
|
+
if (!entry) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`plugin "${name}" not found in registry. Available:\n ` +
|
|
206
|
+
plugins.map((p) => p.name).join(", "),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return entry;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Full resolution: fetch the registry (with auth + offline cache), select the
|
|
214
|
+
* entry, and return the git source string the existing installer consumes plus
|
|
215
|
+
* carry-through metadata (ref pins the checkout; sha256 becomes an install
|
|
216
|
+
* integrity check).
|
|
217
|
+
*
|
|
218
|
+
* @returns {Promise<{ source: string, ref: string|null, sha256: string|null,
|
|
219
|
+
* entry: object, fromCache: boolean }>}
|
|
220
|
+
*/
|
|
221
|
+
export async function resolveRemoteSource(url, opts = {}) {
|
|
222
|
+
const token = resolveRegistryToken(url, opts);
|
|
223
|
+
const { registry, fromCache } = await fetchRegistry(url, { ...opts, token });
|
|
224
|
+
const entry = resolvePluginEntry(registry, opts.name);
|
|
225
|
+
// Carry a `#ref` on the source string so installFromSource pins the checkout,
|
|
226
|
+
// matching its existing `owner/repo#ref` / `url#ref` convention.
|
|
227
|
+
const source = entry.ref ? `${entry.source}#${entry.ref}` : entry.source;
|
|
228
|
+
return {
|
|
229
|
+
source,
|
|
230
|
+
ref: entry.ref || null,
|
|
231
|
+
sha256: entry.sha256 || null,
|
|
232
|
+
entry,
|
|
233
|
+
fromCache,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Best-effort local temp dir helper (kept here so tests can stub fs deps). */
|
|
238
|
+
export function _tmpRoot() {
|
|
239
|
+
return os.tmpdir();
|
|
240
|
+
}
|
|
@@ -77,6 +77,22 @@ export function verifyInstalledSignature(plugin, opts = {}) {
|
|
|
77
77
|
return { signed: false, reason: "lock is not signature-verified" };
|
|
78
78
|
}
|
|
79
79
|
const manifestFile = path.join(plugin.root, lock.manifest || "plugin.json");
|
|
80
|
+
// `lock.manifest` is untrusted — it lives in the (writable) plugin dir. A lock
|
|
81
|
+
// pointing OUTSIDE the plugin root (`manifest: "../../secret"`) would make us
|
|
82
|
+
// re-hash an arbitrary file and, with a matching locked sha256, forge
|
|
83
|
+
// signed:true — which gates load-time requireSignedPlugins. A plugin's manifest
|
|
84
|
+
// is ALWAYS inside its own root, so reject any path that escapes it (boundary
|
|
85
|
+
// via path.relative, not startsWith — the sandbox-fs-policy pattern).
|
|
86
|
+
const rel = path.relative(
|
|
87
|
+
path.resolve(plugin.root),
|
|
88
|
+
path.resolve(manifestFile),
|
|
89
|
+
);
|
|
90
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
91
|
+
return {
|
|
92
|
+
signed: false,
|
|
93
|
+
reason: "lock manifest path escapes the plugin root",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
80
96
|
if (!_deps.existsSync(manifestFile)) {
|
|
81
97
|
return { signed: false, reason: "locked manifest file is missing" };
|
|
82
98
|
}
|