chainlesschain 0.162.149 → 0.162.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BJif14yR.js → AIOps-CBLvrjeG.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CXek6gJY.js → ActionButton-DYv2GtE0.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BgQhdAJi.js → Analytics-CL6PZ-Ww.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DTYl5NtR.js → AppLayout-xuyHt4uA.js} +3 -3
  6. package/src/assets/web-panel/assets/{Audit-BzATQAeR.js → Audit-DOvSVycq.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BzdPEQhL.js → Backup-DMhfTswr.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BM0KVh8E.js → BaseInput-CQxDdm63.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D7fHXHkz.js → Chat-BEeNhK2a.js} +5 -5
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Wm34RR-b.js → ChatBubbleRenderer-8nul8eTq.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-CAiSQ9vO.js → Checkbox-CL04O-ER.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Df40CrEe.js → Codegen-C50o-n7x.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-DIT2fNFU.js → Col-CH-bDkzs.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-CSTIbW2k.js → Community-DqAIMvSe.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DtqXZZUi.js → Compact-C43fo_my.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-CUtzw6o7.js → Compliance-BERnMrdP.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CezmlnmU.js → Cowork-Baw3w1gp.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-P4BITarg.js → Cron-BEqAHF1-.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-BRRiYwvq.js → Crosschain-CvnmKwK4.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-L5ijB9i4.js → DID-iMPxgVdt.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CFxro4ob.js → Dashboard--4SJX3YR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-BmAFCQ71.js → Dropdown-BgLV6xgz.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DJBCEuon.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-z4oLq6eT.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DsVCpHMF.js → Federation-BbFfJ1Xi.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-DP3bP5th.js → FormItemContext-DeGq1B-q.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-BAXP2Gc6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-I3g_bAw9.js → Git-L1PjW-lT.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-jHS5-ioS.js → Governance-PK-X58to.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-CPL7sfx9.js → Inference-BePWEH-d.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-Jf236h4C.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-Cm2tcSKg.js → Logs-BKkfk9hy.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CZK82rhi.js → Marketplace-BNMqUDla.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-CZCeji7a.js → McpTools-w4WPiyz2.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-3l2KVS9k.js → Memory-DUKMVGNi.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-VYdpoLh6.js → MobileBridge-BWhiEyTJ.js} +1 -1
  37. package/src/assets/web-panel/assets/{MobileProjects-CiB9cmm1.js → MobileProjects-Ocf5JEkX.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-wCFZbBuL.js → Mtc-D2B6MMhU.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-1a3THIyG.js → MtcAudit-DdYjCq1O.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DMzLB2hG.js → Multisig-Cb1hfuUZ.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-D9wZbf6l.js → NLProgramming-CTeImGp7.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-hrFe2ZM-.js → Notes-BjYNc7eQ.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-CHGX5Jwm.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-DVYYIizh.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-By7FkhtY.js → Organization-Be2Kmr5j.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow--khGSzJn.js → Overflow-Cu_-qRlR.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CAG9dH35.js → P2P-DD2HoGzE.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-B3SQZmK9.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-D35ec6JA.js → Permissions-C9Srcs2M.js} +3 -3
  50. package/src/assets/web-panel/assets/{PersonalDataHub-CxBF7hW_.js → PersonalDataHub-CUqcMgo1.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-s6EtOO1s.js → Pipeline-HBrQGLXt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BbJYmWmO.js → Privacy-D-B0vjwh.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-VGQq1jMT.js → ProjectInit-eTeLIu9e.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-B1ew3Teh.js → ProjectSettings-DrRqCsC6.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-1G9DNOhI.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-mrO47FIK.js → Providers-RkTZzpxP.js} +1 -1
  57. package/src/assets/web-panel/assets/{QrScannerModal-IJF2mHyw.js → QrScannerModal-BSCUgsgC.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-CU0wN_Z0.js → QuickAsk-CHgyM3Bz.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-DhN37R6G.js → Recommend-0lry4OZq.js} +1 -1
  60. package/src/assets/web-panel/assets/{RemoteSession-D_xOX_hu.js → RemoteSession-Cn45UroZ.js} +2 -2
  61. package/src/assets/web-panel/assets/{Reputation-D_Ssv7uH.js → Reputation-Q-Zjb707.js} +1 -1
  62. package/src/assets/web-panel/assets/{Row-Kx5dKxX7.js → Row-BdM70oda.js} +1 -1
  63. package/src/assets/web-panel/assets/{RssFeed-BlGwqZkY.js → RssFeed-DmOAKKap.js} +2 -2
  64. package/src/assets/web-panel/assets/{Search-hLFQzxpb.js → Search-Dwavbm07.js} +1 -1
  65. package/src/assets/web-panel/assets/{Security-DMlvsVt4.js → Security-DukXFCnk.js} +3 -3
  66. package/src/assets/web-panel/assets/{Services-Bd0Qsj2m.js → Services-BIlRnITu.js} +2 -2
  67. package/src/assets/web-panel/assets/{Skeleton-DFclq9X3.js → Skeleton-YYVQdhhQ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Skills-BNtln2qY.js → Skills-B8_iYHz2.js} +1 -1
  69. package/src/assets/web-panel/assets/{Sla-Dch5H19G.js → Sla-BdVrhT31.js} +1 -1
  70. package/src/assets/web-panel/assets/{SpeechSettings-2UfQcU1g.js → SpeechSettings-CD3ggRx7.js} +1 -1
  71. package/src/assets/web-panel/assets/{SyncSettings-CD9YQr4i.js → SyncSettings-Bp02VZFH.js} +2 -2
  72. package/src/assets/web-panel/assets/{Tasks-BNu-7MzI.js → Tasks-DEVmCEsr.js} +1 -1
  73. package/src/assets/web-panel/assets/{Templates-D1n9CaIR.js → Templates-B6czWc4N.js} +1 -1
  74. package/src/assets/web-panel/assets/{Tenant-DenL3iBW.js → Tenant-BCJLv17r.js} +1 -1
  75. package/src/assets/web-panel/assets/{Terminal-ZzU0Io1Y.js → Terminal-CD132d2Y.js} +2 -2
  76. package/src/assets/web-panel/assets/{TimelineRenderer-D87IfukO.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tokens-BkiZc8E3.js → Tokens-BnSY0S-s.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trigger-BNRSytGN.js → Trigger-VZw9Oy2w.js} +1 -1
  79. package/src/assets/web-panel/assets/{Trust-Bwbd4X2m.js → Trust-pZq7Hjd2.js} +1 -1
  80. package/src/assets/web-panel/assets/{UkeySign-B8O2bs0o.js → UkeySign-DnQaqk4q.js} +1 -1
  81. package/src/assets/web-panel/assets/{VideoEditing-C2fL8zmH.js → VideoEditing-BEUfmvJV.js} +1 -1
  82. package/src/assets/web-panel/assets/{Wallet-BORYDdE1.js → Wallet-CoZKMXJ2.js} +4 -4
  83. package/src/assets/web-panel/assets/{WebAuthn-C0V5S2FM.js → WebAuthn-CBOGVx1I.js} +5 -5
  84. package/src/assets/web-panel/assets/{WorkflowEditor-2dR0fcHL.js → WorkflowEditor-huSKn7TT.js} +1 -1
  85. package/src/assets/web-panel/assets/{chat-BmuAFAn8.js → chat-A_3w6FBO.js} +1 -1
  86. package/src/assets/web-panel/assets/{colors-CN3smMcK.js → colors-D1Bem9Oy.js} +1 -1
  87. package/src/assets/web-panel/assets/{compact-item-DCnxb4kW.js → compact-item-CJYJj0oO.js} +1 -1
  88. package/src/assets/web-panel/assets/{createContext-vlR43pHn.js → createContext-BkbFiKT4.js} +1 -1
  89. package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
  90. package/src/assets/web-panel/assets/{hasIn-D04tHYFd.js → hasIn-CKvUafVY.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-0DnaZGkC.js → index-AjrSPnvv.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BudvKQfG.js → index-B8i0lViZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BeGDEXFs.js → index-B9Svn0zc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-D5LZVZ0L.js → index-BHdHSX06.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTPEZTk1.js → index-BHdailyo.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DIpY0qM5.js → index-BJwlEnYo.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CsAsCPJ6.js → index-BOoAQjJz.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-ljuBiQW9.js → index-BSgWxAOG.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-hpvvtAZ3.js → index-BdutMsne.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-_FIkN3jd.js → index-BiDWxzbn.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C-6NO5il.js → index-By5a0T_W.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B78xL3s2.js → index-C-WIY-FQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-ClhAHDK3.js → index-C37f6iey.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-ToUh2b1-.js → index-C9bWmTcO.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D28DeAAB.js → index-CBaosWRO.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-D8vwp4qp.js → index-CBlBVJ_m.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BqhASEL4.js → index-CD62EEGo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DDwLgQY9.js → index-CUj-l7GM.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DbVsQBOH.js → index-CVS8Ymuq.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cr-A0FYJ.js → index-ChkXUj3f.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D65_XseV.js → index-Cj0OZ-37.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BXRg8GFQ.js → index-Co3OFL0t.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-430S68MN.js → index-Crp8CbRg.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CngCC8hC.js → index-D22zNXiS.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-HhNjnCfe.js → index-DTs_D1OL.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CP9m9Ggr.js → index-DfgCdFrN.js} +3 -3
  117. package/src/assets/web-panel/assets/{index-jzR7Ujuw.js → index-DjYrecNb.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CTOCnIQ7.js → index-DpW9cf3e.js} +1 -1
  119. package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
  120. package/src/assets/web-panel/assets/{index-D_n8_vfI.js → index-F--HuPG1.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BasvsWDM.js → index-Fuk6t_3K.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CnXebZLL.js → index-JEAGMii9.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DYAtmqiv.js → index-PF-mpv8K.js} +1 -1
  124. package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
  125. package/src/assets/web-panel/assets/{index-DTWg-18U.js → index-b-sbVdjQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C5Sxb1sE.js → index-pyvMVX8r.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-TslMTwNy.js → index-qNTtOVi_.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DTtRscUa.js → index-tgO8iza6.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DbADHwhr.js → index-ttDQVhZy.js} +1 -1
  130. package/src/assets/web-panel/assets/{initDefaultProps-C3wUb4je.js → initDefaultProps-DtrnfsZH.js} +1 -1
  131. package/src/assets/web-panel/assets/{motion-D-jYFUNA.js → motion-D4zqSaX3.js} +1 -1
  132. package/src/assets/web-panel/assets/{move-tqb8nwJ2.js → move-CGFGOUQj.js} +1 -1
  133. package/src/assets/web-panel/assets/{mtc-parser-DSOjMJMz.js → mtc-parser-DH2HvkJl.js} +1 -1
  134. package/src/assets/web-panel/assets/{omit-CJ7VimIW.js → omit-jJ2at5HX.js} +1 -1
  135. package/src/assets/web-panel/assets/{pickAttrs-qXiG1iT3.js → pickAttrs-CRyMT1Fv.js} +1 -1
  136. package/src/assets/web-panel/assets/{placementArrow-CkGBcy1Z.js → placementArrow-DZ_CX0Pt.js} +1 -1
  137. package/src/assets/web-panel/assets/{responsiveObserve-D3WvTlLO.js → responsiveObserve-wruHk-h5.js} +1 -1
  138. package/src/assets/web-panel/assets/{slide-BEE2ftge.js → slide-BGgx8q67.js} +1 -1
  139. package/src/assets/web-panel/assets/{statusUtils-CsfBpxiG.js → statusUtils-Do5M7gKm.js} +1 -1
  140. package/src/assets/web-panel/assets/{styleChecker-xPrIiJPI.js → styleChecker-CRa-GZpX.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFlexGapSupport-C5Yd-SCR.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
  142. package/src/assets/web-panel/assets/{useFs-D_MDS8HI.js → useFs-v5O2ZcJm.js} +1 -1
  143. package/src/assets/web-panel/assets/{usePersonalDataHub-BqJh-usA.js → usePersonalDataHub--z2FU0Px.js} +1 -1
  144. package/src/assets/web-panel/assets/{vnode-chm_p-gz.js → vnode-1emidVKd.js} +1 -1
  145. package/src/assets/web-panel/assets/{zoom-3-YDNz0Z.js → zoom-CcJaRyHs.js} +1 -1
  146. package/src/assets/web-panel/index.html +1 -1
  147. package/src/commands/a2a.js +19 -4
  148. package/src/commands/activitypub.js +20 -3
  149. package/src/commands/agent.js +27 -5
  150. package/src/commands/audit.js +40 -24
  151. package/src/commands/codeintel.js +68 -0
  152. package/src/commands/collab.js +15 -2
  153. package/src/commands/compliance.js +5 -0
  154. package/src/commands/config.js +4 -1
  155. package/src/commands/dao.js +86 -14
  156. package/src/commands/dev.js +2 -0
  157. package/src/commands/did.js +7 -1
  158. package/src/commands/dlp.js +23 -1
  159. package/src/commands/economy.js +29 -3
  160. package/src/commands/eval.js +7 -0
  161. package/src/commands/evomap.js +26 -0
  162. package/src/commands/governance.js +17 -3
  163. package/src/commands/hardening.js +18 -0
  164. package/src/commands/incentive.js +5 -0
  165. package/src/commands/init.js +46 -2
  166. package/src/commands/kg.js +4 -0
  167. package/src/commands/loop.js +6 -1
  168. package/src/commands/lowcode.js +15 -2
  169. package/src/commands/marketplace.js +40 -6
  170. package/src/commands/matrix.js +20 -1
  171. package/src/commands/memory.js +4 -1
  172. package/src/commands/mtc.js +7 -1
  173. package/src/commands/nostr.js +31 -4
  174. package/src/commands/note.js +7 -4
  175. package/src/commands/plugin.js +125 -5
  176. package/src/commands/pqc.js +5 -0
  177. package/src/commands/reputation.js +10 -1
  178. package/src/commands/scim.js +6 -0
  179. package/src/commands/session.js +32 -10
  180. package/src/commands/siem.js +11 -0
  181. package/src/commands/sla.js +18 -3
  182. package/src/commands/social.js +38 -5
  183. package/src/commands/sso.js +10 -2
  184. package/src/commands/stress.js +23 -4
  185. package/src/commands/team.js +9 -2
  186. package/src/commands/tech.js +2 -0
  187. package/src/commands/tenant.js +10 -1
  188. package/src/commands/terraform.js +6 -0
  189. package/src/commands/update.js +1 -1
  190. package/src/commands/zkp.js +20 -0
  191. package/src/gateways/ws/message-dispatcher.js +5 -2
  192. package/src/gateways/ws/remote-session-protocol.js +103 -42
  193. package/src/harness/jsonl-session-store.js +12 -5
  194. package/src/harness/plugin-manager.js +16 -4
  195. package/src/harness/remote-command-ledger.js +46 -15
  196. package/src/harness/worktree-isolator.js +16 -6
  197. package/src/lib/__tests__/logger.test.js +5 -2
  198. package/src/lib/a2a-protocol.js +25 -1
  199. package/src/lib/activitypub-bridge.js +61 -0
  200. package/src/lib/agent-core.js +2 -0
  201. package/src/lib/agent-economy.js +262 -29
  202. package/src/lib/agent-network.js +7 -1
  203. package/src/lib/agent-team/team-runner.js +25 -9
  204. package/src/lib/agent-team/team-worktree.js +36 -4
  205. package/src/lib/async-hook-supervisor.cjs +102 -16
  206. package/src/lib/audit-logger.js +7 -6
  207. package/src/lib/autonomous-developer.js +60 -0
  208. package/src/lib/chat-core.js +17 -4
  209. package/src/lib/collaboration-governance.js +56 -0
  210. package/src/lib/community-governance.js +68 -0
  211. package/src/lib/compliance-manager.js +63 -0
  212. package/src/lib/cross-chain.js +5 -0
  213. package/src/lib/dao-governance.js +151 -2
  214. package/src/lib/did-manager.js +23 -10
  215. package/src/lib/dlp-engine.js +70 -0
  216. package/src/lib/eval/runner.js +89 -1
  217. package/src/lib/eval/tasks.js +170 -0
  218. package/src/lib/eval/trend.js +16 -1
  219. package/src/lib/evolution-system.js +92 -0
  220. package/src/lib/evomap-federation.js +55 -0
  221. package/src/lib/evomap-governance.js +94 -0
  222. package/src/lib/fatal-handler.js +17 -1
  223. package/src/lib/hardening-manager.js +73 -0
  224. package/src/lib/hierarchical-memory.js +14 -8
  225. package/src/lib/knowledge-exporter.js +8 -2
  226. package/src/lib/knowledge-graph.js +79 -0
  227. package/src/lib/llm-providers.js +23 -14
  228. package/src/lib/logger.js +4 -1
  229. package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
  230. package/src/lib/lsp/__tests__/lsp-client.test.js +11 -0
  231. package/src/lib/lsp/__tests__/lsp-manager.test.js +69 -0
  232. package/src/lib/lsp/benchmark.js +257 -0
  233. package/src/lib/lsp/lsp-client.js +30 -9
  234. package/src/lib/lsp/lsp-manager.js +54 -2
  235. package/src/lib/matrix-bridge.js +84 -0
  236. package/src/lib/memory-manager.js +5 -3
  237. package/src/lib/nostr-bridge.js +53 -0
  238. package/src/lib/permission-engine.js +22 -4
  239. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
  240. package/src/lib/plugin-runtime/install.js +9 -1
  241. package/src/lib/plugin-runtime/policy.js +9 -1
  242. package/src/lib/plugin-runtime/remote-source.js +240 -0
  243. package/src/lib/pqc-manager.js +64 -0
  244. package/src/lib/reputation-optimizer.js +101 -0
  245. package/src/lib/sandbox-egress-proxy.js +159 -59
  246. package/src/lib/sandbox-fs-policy.js +112 -0
  247. package/src/lib/sandbox-network-policy.js +18 -1
  248. package/src/lib/sandbox-v2.js +14 -14
  249. package/src/lib/scim-manager.js +36 -0
  250. package/src/lib/session-manager.js +5 -2
  251. package/src/lib/settings-hooks.cjs +1 -0
  252. package/src/lib/siem-exporter.js +45 -0
  253. package/src/lib/skill-loader.js +20 -2
  254. package/src/lib/skill-marketplace.js +83 -0
  255. package/src/lib/sla-manager.js +88 -0
  256. package/src/lib/social-manager.js +74 -0
  257. package/src/lib/stress-tester.js +65 -0
  258. package/src/lib/tech-learning-engine.js +75 -0
  259. package/src/lib/tenant-saas.js +107 -0
  260. package/src/lib/terraform-manager.js +67 -0
  261. package/src/lib/token-incentive.js +180 -29
  262. package/src/lib/wallet-manager.js +81 -35
  263. package/src/lib/zkp-engine.js +87 -0
  264. package/src/repl/agent-repl.js +17 -2
  265. package/src/runtime/__tests__/auto-pin.test.js +104 -0
  266. package/src/runtime/agent-core.js +241 -68
  267. package/src/runtime/auto-pin.js +117 -0
  268. package/src/runtime/headless-runner.js +35 -0
  269. package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +0 -1
  270. package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +0 -1
  271. package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +0 -1
  272. package/src/assets/web-panel/assets/index-Cg2ldRfU.js +0 -1
  273. package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +0 -1
@@ -7,7 +7,7 @@
7
7
 
8
8
  import fs from "fs";
9
9
  import path from "path";
10
- import { likePrefix } from "./sql-like.js";
10
+ import { likePrefix, escapeLike } from "./sql-like.js";
11
11
 
12
12
  /**
13
13
  * Atomically write a UTF-8 file: a crash mid-write must not truncate MEMORY.md
@@ -98,12 +98,14 @@ export function searchMemory(db, query, options = {}) {
98
98
  if (!query || !query.trim()) return [];
99
99
 
100
100
  const limit = Math.max(1, parseInt(options.limit) || 20);
101
- const pattern = `%${query}%`;
101
+ // Escape LIKE wildcards so a query containing % / _ is matched literally
102
+ // rather than as a wildcard (`search "%"` would otherwise match every entry).
103
+ const pattern = `%${escapeLike(query)}%`;
102
104
 
103
105
  return db
104
106
  .prepare(
105
107
  `SELECT * FROM memory_entries
106
- WHERE content LIKE ?
108
+ WHERE content LIKE ? ESCAPE '\\'
107
109
  ORDER BY importance DESC, created_at DESC
108
110
  LIMIT ?`,
109
111
  )
@@ -59,6 +59,59 @@ export function ensureNostrTables(db) {
59
59
  `);
60
60
  }
61
61
 
62
+ /**
63
+ * Rehydrate the module-level _relays/_events Maps from the persisted
64
+ * nostr_relays/nostr_events tables. The `cc` CLI runs every command in a
65
+ * fresh process, so without this the Maps are empty on read and a relay/event
66
+ * added in a prior invocation is invisible (`cc nostr relays`/`events` show
67
+ * nothing; `dm-decrypt` can't find the DM). Call after ensureNostrTables(db).
68
+ * (_keypairs/_didMappings are intentionally ephemeral — no DB table.)
69
+ */
70
+ export function loadFromDb(db) {
71
+ if (!db) return 0;
72
+ _relays.clear();
73
+ _events.clear();
74
+
75
+ const relayRows = db.prepare(`SELECT * FROM nostr_relays`).all();
76
+ for (const row of relayRows) {
77
+ _relays.set(row.id, {
78
+ id: row.id,
79
+ url: row.url,
80
+ status: row.status,
81
+ lastConnected: row.last_connected ?? null,
82
+ eventCount: row.event_count ?? 0,
83
+ readEnabled: !!row.read_enabled,
84
+ writeEnabled: !!row.write_enabled,
85
+ });
86
+ }
87
+
88
+ const eventRows = db.prepare(`SELECT * FROM nostr_events`).all();
89
+ for (const row of eventRows) {
90
+ let tags = [];
91
+ try {
92
+ tags = row.tags ? JSON.parse(row.tags) : [];
93
+ } catch {
94
+ tags = [];
95
+ }
96
+ _events.set(row.id, {
97
+ id: row.id,
98
+ pubkey: row.pubkey,
99
+ kind: row.kind,
100
+ content: row.content,
101
+ tags,
102
+ sig: row.sig ?? "",
103
+ // write side stores createdAt as unix seconds; the column is an ISO
104
+ // string — convert back so the two process paths agree.
105
+ createdAt: row.created_at
106
+ ? Math.floor(new Date(row.created_at).getTime() / 1000)
107
+ : null,
108
+ relayUrl: row.relay_url ?? null,
109
+ });
110
+ }
111
+
112
+ return _relays.size + _events.size;
113
+ }
114
+
62
115
  /* ── Relay Management ─────────────────────────────────────── */
63
116
 
64
117
  export function listRelays() {
@@ -275,6 +275,24 @@ export function revokePermission(db, userDid, permission) {
275
275
  return result.changes > 0;
276
276
  }
277
277
 
278
+ /**
279
+ * Is a grant still active given its stored `expires_at`?
280
+ *
281
+ * The previous check compared `expires_at > now` as ISO STRINGS. That is
282
+ * fail-open on unvalidated input: an unpadded date ("2026-7-5") or any
283
+ * non-numeric-leading garbage ("next year") sorts lexicographically AFTER a
284
+ * canonical `now` ('7'/'n' > '0'/'2'), so an already-expired or malformed
285
+ * time-limited grant silently became a PERMANENT grant. Compare numerically and
286
+ * treat an unparseable expiry as expired (fail closed) so a typo can never widen
287
+ * access. A null/empty `expires_at` means no expiry.
288
+ */
289
+ function _grantActive(expiresAt, nowMs) {
290
+ if (!expiresAt) return true;
291
+ const t = Date.parse(expiresAt);
292
+ if (Number.isNaN(t)) return false;
293
+ return t > nowMs;
294
+ }
295
+
278
296
  /**
279
297
  * Get all roles and direct permissions for a user.
280
298
  */
@@ -287,15 +305,15 @@ export function getUserPermissions(db, userDid) {
287
305
  .all(userDid);
288
306
 
289
307
  // Filter out expired grants
290
- const now = new Date().toISOString();
291
- const grants = allGrants.filter((g) => !g.expires_at || g.expires_at > now);
308
+ const nowMs = Date.now();
309
+ const grants = allGrants.filter((g) => _grantActive(g.expires_at, nowMs));
292
310
 
293
311
  // Get direct permissions (not expired)
294
312
  const allDirectPerms = db
295
313
  .prepare("SELECT * FROM rbac_direct_permissions WHERE user_did = ?")
296
314
  .all(userDid);
297
- const directPerms = allDirectPerms.filter(
298
- (d) => !d.expires_at || d.expires_at > now,
315
+ const directPerms = allDirectPerms.filter((d) =>
316
+ _grantActive(d.expires_at, nowMs),
299
317
  );
300
318
 
301
319
  // Collect all permissions by looking up each role
@@ -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
+ });
@@ -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 };
@@ -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)) return _cache.get(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
+ }