chainlesschain 0.165.1 → 0.165.2

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 (202) hide show
  1. package/README.md +248 -2
  2. package/package.json +4 -1
  3. package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-lS1R0Cza.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-BmlgBPsj.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-CNKczv1S.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-BuZGJARQ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-Dm73Sl-O.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-BGMW7A5e.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-DBzGP0bR.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-D1mLCoOV.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-BUfcN7p8.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-Du0Yg5Pi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-B2RXziHW.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-DWhekOrR.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-HQhrkon4.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-izHNb4uz.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BDwLy41-.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-DFJhLa7K.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-DYY9RG7-.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-rr7tJTtv.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-D2sfHRLi.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-q_eGdhha.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-Cufrh2Bz.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-B-kdv10f.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-CAwRTLZH.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-vx3daNDF.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-Q6SwauyD.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-BJ1q4IQZ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-CSWjROgD.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-DVF8Mnns.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-BQP7LOA4.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-BNlJCmNc.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-DLLg5WQ4.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-CR2skNAF.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-Tyd3o2A0.js → MobileProjects-BxVavObl.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B17pgqMI.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-4UmV5cIL.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-DoMEXEcU.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-h9DMXFX4.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-Cihn-xX8.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-B2vpwmjb.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-C4z28R0-.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-BVlFkwhW.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-C28xs4lp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-Dgl_rBjM.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-3QUIhrKI.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-8Gh33OZ5.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-BNmg4tZi.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-vgMfeCf9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CUnHbNf-.js → Providers-CXoOa8eX.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-BhfTF0oS.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-C746HEAj.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-BSej8Eo0.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-f3gmnlG3.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-CyvybpkN.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-DwV2PeYn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-11GrzjZy.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-BjKXkrAY.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-eWiFb9Yk.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-CdCsMAtt.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-4Q2UTKQ5.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-v4rtYrRj.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-CRhsFvd_.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-D16zonl7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-Xxozz0i_.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-CXJpPtCT.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-XRV0L1ZF.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-CYuO1i_w.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-BXqitWsu.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BvPrDHIy.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-DhCrSTf5.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-DmsuZD6K.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-D_yrts5C.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-B01ihPrm.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-DpUjKa-N.js} +3 -3
  87. package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-CL07yON6.js} +3 -3
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CLMflnRr.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-DtUqe8h_.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-DPKrgVfy.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-BTt2GtOm.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-D_VeUc1J.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-1kndiylj.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-2eyAXE3d.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-4bna2_SR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-8nz_fmlj.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-B9KvcS7R.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-BAXfRKfG.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BHpBMWCh.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-BaZAbw9A.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-BepLZwgB.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-Bhudc_Ez.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-BlDNVgBt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-By5n8up9.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-CHG-KcS4.js} +1 -1
  107. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  108. package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-CX81qSM4.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-CauoBQkH.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-CcO04eDn.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-CtF7EN49.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-CzjkKUmR.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-D95OFht7.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DFIZ_1Sf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-DG2MZQ3f.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-DJ8vXGOa.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-Dbbymxpj.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-DdkplL9R.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-DfGl8CqG.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-DfKBRdSS.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-DsJCDUJG.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-Dwqmtk65.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-DxdTdBix.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-FzdwcSy2.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-GsoUQ2M7.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-brCInrzq.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-dVc-CXY4.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-fk2Rjacw.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-l1BETrDb.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-m31Ka8hZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-ocf06EVQ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-sDoMHTNA.js} +1 -1
  133. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  134. package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-CvyYJd7z.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-DYZkhUdz.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-slnJRVT1.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-DVc0CKmy.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-BCXAtHCq.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-DBsemz_K.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-BRtkJ-nv.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cebq7EZn.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-DT7qvId2.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-pQiITLkG.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-c-19BVKQ.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-CxGiMe5n.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-D9btVID5.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-CUYFsFEN.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-CXmn_35j.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/cowork.js +280 -5
  153. package/src/commands/plugin.js +819 -64
  154. package/src/commands/session-location.js +318 -7
  155. package/src/constants.js +2 -2
  156. package/src/data/changelog.json +14 -1
  157. package/src/gateways/ws/action-protocol.js +1 -1
  158. package/src/harness/jsonl-session-store.js +638 -2
  159. package/src/harness/prompt-compressor.js +4 -0
  160. package/src/lib/background-agent-keeper-client.js +29 -0
  161. package/src/lib/background-agent-keeper-protocol.js +4 -0
  162. package/src/lib/background-agent-supervisor.js +59 -29
  163. package/src/lib/cowork-task-runner.js +70 -0
  164. package/src/lib/cowork-workflow.js +42 -9
  165. package/src/lib/doctor-checkup.js +34 -10
  166. package/src/lib/dynamic-workflow-draft.js +535 -0
  167. package/src/lib/dynamic-workflow-facade.js +11 -11
  168. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  169. package/src/lib/execution-location-contract.js +111 -7
  170. package/src/lib/execution-location-runtime.js +3 -1
  171. package/src/lib/execution-location-target.js +910 -0
  172. package/src/lib/llm-providers.js +1 -0
  173. package/src/lib/mcp-call-ledger-store.js +35 -0
  174. package/src/lib/mcp-call-ledger.js +30 -0
  175. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  176. package/src/lib/plugin-runtime/install.js +4956 -657
  177. package/src/lib/plugin-runtime/manifest.js +3 -1
  178. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  179. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  180. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  181. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  182. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  183. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  184. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  185. package/src/lib/plugin-runtime/policy.js +21 -6
  186. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  187. package/src/lib/plugin-runtime/remote-source.js +323 -27
  188. package/src/lib/plugin-runtime/scopes.js +317 -41
  189. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  190. package/src/lib/public-ide-capability-manifest.js +1 -1
  191. package/src/lib/remote-membership-coordinator.js +8 -6
  192. package/src/lib/sub-agent-context.js +268 -2
  193. package/src/lib/task-model-selector.js +1 -1
  194. package/src/lib/with-file-lock.js +95 -5
  195. package/src/runtime/agent-core.js +595 -41
  196. package/src/workers/background-agent-keeper.js +40 -10
  197. package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +0 -1
  198. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
  199. package/src/assets/web-panel/assets/SpeechSettings-B5pLcXUw.js +0 -1
  200. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
  201. package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
  202. package/src/assets/web-panel/assets/index-xYybvEic.js +0 -1
@@ -28,6 +28,7 @@ import os from "os";
28
28
  import path from "path";
29
29
  import crypto from "crypto";
30
30
  import { getElectronUserDataDir } from "../paths.js";
31
+ import { createMarketplaceNetworkTransport } from "./marketplace-network.js";
31
32
 
32
33
  export const _deps = {
33
34
  // Node 22+ global fetch; injectable for tests.
@@ -36,9 +37,21 @@ export const _deps = {
36
37
  writeFileSync: fs.writeFileSync,
37
38
  existsSync: fs.existsSync,
38
39
  mkdirSync: fs.mkdirSync,
40
+ lstatSync: fs.lstatSync,
41
+ readdirSync: fs.readdirSync,
42
+ openSync: fs.openSync,
43
+ fstatSync: fs.fstatSync,
44
+ readSync: fs.readSync,
45
+ closeSync: fs.closeSync,
46
+ fsyncSync: fs.fsyncSync,
47
+ linkSync: fs.linkSync,
48
+ unlinkSync: fs.unlinkSync,
39
49
  };
40
50
 
41
51
  const DEFAULT_TIMEOUT_MS = 15_000;
52
+ export const MAX_REGISTRY_DOCUMENT_BYTES = 4 * 1024 * 1024;
53
+ export const MAX_REGISTRY_CACHE_CANDIDATES = 64;
54
+ const SHA256_RE = /^[a-f0-9]{64}$/;
42
55
 
43
56
  /** True when `raw` looks like a remote registry/manifest URL (http(s) + .json). */
44
57
  export function isRemoteSource(raw) {
@@ -79,8 +92,8 @@ export function assertRegistryUrlSafe(url, { allowInsecure = false } = {}) {
79
92
  );
80
93
  }
81
94
 
82
- /** Where a fetched registry is cached (content-addressed by its URL). */
83
- export function registryCachePath(url, cacheDir) {
95
+ /** Directory for immutable registry documents, addressed by the complete URL. */
96
+ export function registryCacheDirectory(url, cacheDir) {
84
97
  const dir =
85
98
  cacheDir || path.join(getElectronUserDataDir(), "plugin-registry-cache");
86
99
  const hash = crypto
@@ -88,7 +101,16 @@ export function registryCachePath(url, cacheDir) {
88
101
  .update(String(url))
89
102
  .digest("hex")
90
103
  .slice(0, 32);
91
- return path.join(dir, `${hash}.json`);
104
+ return path.join(dir, hash);
105
+ }
106
+
107
+ /** Path for one immutable registry document bound to its SHA-256 digest. */
108
+ export function registryCachePath(url, cacheDir, documentSha256) {
109
+ const digest = normalizeRegistrySha256(
110
+ documentSha256,
111
+ "registry document SHA-256",
112
+ );
113
+ return path.join(registryCacheDirectory(url, cacheDir), `${digest}.json`);
92
114
  }
93
115
 
94
116
  /**
@@ -112,12 +134,13 @@ export function resolveRegistryToken(url, { token, config } = {}) {
112
134
  }
113
135
 
114
136
  /**
115
- * GET a registry/manifest URL as JSON, with optional bearer auth. On success the
116
- * body is written to the offline cache; on a network/HTTP failure we fall back
117
- * to that cache (so a previously-seen registry still installs offline). Throws
118
- * only when neither the network NOR the cache can produce a valid document.
137
+ * GET a registry/manifest URL as bounded JSON, with optional bearer auth. On
138
+ * success the body is written to an immutable content-addressed cache. Explicit
139
+ * offline mode never attempts network access. An unpinned cache read is allowed
140
+ * only when exactly one valid historical document exists for the URL.
119
141
  *
120
- * @returns {Promise<{ registry: object, fromCache: boolean }>}
142
+ * @returns {Promise<{ registry: object, fromCache: boolean,
143
+ * documentSha256: string }>}
121
144
  */
122
145
  export async function fetchRegistry(url, opts = {}) {
123
146
  const {
@@ -125,19 +148,47 @@ export async function fetchRegistry(url, opts = {}) {
125
148
  cacheDir,
126
149
  timeoutMs = DEFAULT_TIMEOUT_MS,
127
150
  allowCache = true,
151
+ offline = false,
128
152
  } = opts;
129
153
  assertRegistryUrlSafe(url, { allowInsecure: opts.allowInsecure });
130
- const cachePath = registryCachePath(url, cacheDir);
154
+ const expectedSha256 = normalizeRegistrySha256(
155
+ opts.expectedSha256,
156
+ "expected registry document SHA-256",
157
+ { optional: true },
158
+ );
131
159
  const headers = { Accept: "application/json" };
132
160
  if (token) headers.Authorization = `Bearer ${token}`;
133
161
 
162
+ if (offline === true) {
163
+ if (!allowCache) {
164
+ throw new Error("offline registry mode requires immutable cache access");
165
+ }
166
+ const cached = readCachedRegistry({
167
+ url,
168
+ cacheDir,
169
+ expectedSha256,
170
+ });
171
+ if (cached) return cached;
172
+ throw new Error(
173
+ `registry ${url} is unavailable in the verified immutable cache` +
174
+ (expectedSha256 ? ` at SHA-256 ${expectedSha256}` : ""),
175
+ );
176
+ }
177
+
178
+ const networkTransport = createMarketplaceNetworkTransport({
179
+ proxyUrl: opts.proxyUrl,
180
+ pacFile: opts.pacFile,
181
+ caFile: opts.caFile,
182
+ });
183
+ const fetchImpl = networkTransport?.fetch || _deps.fetch;
134
184
  let networkErr = null;
185
+ let cacheFallbackAllowed = false;
135
186
  try {
136
187
  const controller = new AbortController();
137
188
  const timer = setTimeout(() => controller.abort(), timeoutMs);
138
189
  let res;
139
190
  try {
140
- res = await _deps.fetch(url, {
191
+ res = await fetchImpl(url, {
141
192
  headers,
142
193
  redirect: "follow",
143
194
  signal: controller.signal,
@@ -146,43 +197,282 @@ export async function fetchRegistry(url, opts = {}) {
146
197
  clearTimeout(timer);
147
198
  }
148
199
  if (!res.ok) {
149
- throw new Error(
200
+ const error = new Error(
150
201
  `registry fetch failed: HTTP ${res.status} ${res.statusText || ""}`.trim(),
151
202
  );
203
+ error.cacheFallbackAllowed = Number(res.status) >= 500;
204
+ throw error;
152
205
  }
153
- const text = await res.text();
206
+ const bytes = await readBoundedRegistryResponse(res);
207
+ const documentSha256 = sha256(bytes);
208
+ if (expectedSha256 && documentSha256 !== expectedSha256) {
209
+ throw new Error(
210
+ `registry document SHA-256 mismatch: expected ${expectedSha256}, got ${documentSha256}`,
211
+ );
212
+ }
213
+ const text = decodeRegistryUtf8(bytes);
154
214
  const registry = validateRegistry(JSON.parse(text), url);
155
- if (allowCache) writeCache(cachePath, text);
156
- return { registry, fromCache: false };
215
+ if (allowCache) writeImmutableCache(url, cacheDir, bytes, documentSha256);
216
+ return {
217
+ registry,
218
+ fromCache: false,
219
+ documentSha256,
220
+ ...(networkTransport?.authority
221
+ ? { networkAuthority: networkTransport.authority }
222
+ : {}),
223
+ };
157
224
  } catch (err) {
158
225
  networkErr = err;
226
+ cacheFallbackAllowed =
227
+ err?.cacheFallbackAllowed === true ||
228
+ err?.name === "AbortError" ||
229
+ err instanceof TypeError;
230
+ } finally {
231
+ await networkTransport?.close();
159
232
  }
160
233
 
161
- // Network failed try the offline cache.
162
- if (allowCache && _deps.existsSync(cachePath)) {
234
+ // Only transport failures and server-side outages may fall back. Authentication,
235
+ // authorization, malformed content, and digest mismatches remain authoritative.
236
+ if (allowCache && cacheFallbackAllowed) {
163
237
  try {
164
- const text = _deps.readFileSync(cachePath, "utf8");
165
- const registry = validateRegistry(JSON.parse(text), url);
166
- return { registry, fromCache: true };
167
- } catch {
168
- /* cache unreadable — fall through to throw the network error */
238
+ const cached = readCachedRegistry({
239
+ url,
240
+ cacheDir,
241
+ expectedSha256,
242
+ });
243
+ if (cached) return cached;
244
+ } catch (cacheError) {
245
+ throw new Error(
246
+ `could not fetch registry ${url}: ${networkErr.message}; verified immutable cache rejected: ${cacheError.message}`,
247
+ );
169
248
  }
170
249
  }
171
250
  throw new Error(
172
251
  `could not fetch registry ${url}: ${networkErr ? networkErr.message : "unknown error"}` +
173
- (allowCache ? " (no offline cache available)" : ""),
252
+ (allowCache && cacheFallbackAllowed
253
+ ? " (no verified immutable cache available)"
254
+ : ""),
174
255
  );
175
256
  }
176
257
 
177
- function writeCache(cachePath, text) {
258
+ async function readBoundedRegistryResponse(response) {
259
+ const contentLength = Number(response?.headers?.get?.("content-length"));
260
+ if (
261
+ Number.isFinite(contentLength) &&
262
+ contentLength > MAX_REGISTRY_DOCUMENT_BYTES
263
+ ) {
264
+ throw new Error(
265
+ `registry document exceeds ${MAX_REGISTRY_DOCUMENT_BYTES} bytes`,
266
+ );
267
+ }
268
+ if (response?.body?.getReader) {
269
+ const reader = response.body.getReader();
270
+ const chunks = [];
271
+ let total = 0;
272
+ try {
273
+ while (true) {
274
+ const { done, value } = await reader.read();
275
+ if (done) break;
276
+ const chunk = Buffer.from(value);
277
+ total += chunk.length;
278
+ if (total > MAX_REGISTRY_DOCUMENT_BYTES) {
279
+ await reader.cancel();
280
+ throw new Error(
281
+ `registry document exceeds ${MAX_REGISTRY_DOCUMENT_BYTES} bytes`,
282
+ );
283
+ }
284
+ chunks.push(chunk);
285
+ }
286
+ return Buffer.concat(chunks, total);
287
+ } finally {
288
+ reader.releaseLock?.();
289
+ }
290
+ }
291
+ const text = await response.text();
292
+ const bytes = Buffer.from(text, "utf8");
293
+ if (bytes.length > MAX_REGISTRY_DOCUMENT_BYTES) {
294
+ throw new Error(
295
+ `registry document exceeds ${MAX_REGISTRY_DOCUMENT_BYTES} bytes`,
296
+ );
297
+ }
298
+ return bytes;
299
+ }
300
+
301
+ function writeImmutableCache(url, cacheDir, bytes, digest) {
302
+ const cachePath = registryCachePath(url, cacheDir, digest);
303
+ const directory = path.dirname(cachePath);
304
+ let tempPath = null;
305
+ let descriptor = null;
178
306
  try {
179
- _deps.mkdirSync(path.dirname(cachePath), { recursive: true });
180
- _deps.writeFileSync(cachePath, text, "utf8");
307
+ _deps.mkdirSync(directory, { recursive: true, mode: 0o700 });
308
+ assertCacheDirectorySafe(directory);
309
+ tempPath = path.join(
310
+ directory,
311
+ `.tmp-${process.pid}-${crypto.randomBytes(12).toString("hex")}`,
312
+ );
313
+ descriptor = _deps.openSync(tempPath, "wx", 0o600);
314
+ _deps.writeFileSync(descriptor, bytes);
315
+ _deps.fsyncSync(descriptor);
316
+ _deps.closeSync(descriptor);
317
+ descriptor = null;
318
+ try {
319
+ _deps.linkSync(tempPath, cachePath);
320
+ } catch (error) {
321
+ if (error?.code !== "EEXIST") throw error;
322
+ const existing = readBoundedRegularFile(cachePath);
323
+ if (sha256(existing) !== digest || !existing.equals(bytes)) throw error;
324
+ }
181
325
  } catch {
182
- /* caching is best-effort; a read-only cache dir must not break install */
326
+ /* caching remains best-effort; verification never trusts a partial write */
327
+ } finally {
328
+ if (descriptor != null) {
329
+ try {
330
+ _deps.closeSync(descriptor);
331
+ } catch {
332
+ /* best-effort cleanup */
333
+ }
334
+ }
335
+ if (tempPath) {
336
+ try {
337
+ _deps.unlinkSync(tempPath);
338
+ } catch {
339
+ /* hidden temporary files are never cache candidates */
340
+ }
341
+ }
183
342
  }
184
343
  }
185
344
 
345
+ function readCachedRegistry({ url, cacheDir, expectedSha256 }) {
346
+ const directory = registryCacheDirectory(url, cacheDir);
347
+ if (!_deps.existsSync(directory)) return null;
348
+ assertCacheDirectorySafe(directory);
349
+ const names = expectedSha256
350
+ ? [`${expectedSha256}.json`]
351
+ : _deps
352
+ .readdirSync(directory, { withFileTypes: true })
353
+ .filter(
354
+ (entry) => entry.isFile() && /^[a-f0-9]{64}\.json$/.test(entry.name),
355
+ )
356
+ .map((entry) => entry.name)
357
+ .sort();
358
+ if (names.length > MAX_REGISTRY_CACHE_CANDIDATES) {
359
+ throw new Error(
360
+ `registry immutable cache has more than ${MAX_REGISTRY_CACHE_CANDIDATES} candidates`,
361
+ );
362
+ }
363
+ const matches = [];
364
+ for (const name of names) {
365
+ const digest = name.slice(0, 64);
366
+ const cachePath = path.join(directory, name);
367
+ if (!_deps.existsSync(cachePath)) continue;
368
+ try {
369
+ const bytes = readBoundedRegularFile(cachePath);
370
+ if (sha256(bytes) !== digest) {
371
+ throw new Error("registry immutable cache digest mismatch");
372
+ }
373
+ const registry = validateRegistry(
374
+ JSON.parse(decodeRegistryUtf8(bytes)),
375
+ url,
376
+ );
377
+ matches.push({
378
+ registry,
379
+ fromCache: true,
380
+ documentSha256: digest,
381
+ });
382
+ } catch (error) {
383
+ if (expectedSha256) throw error;
384
+ }
385
+ }
386
+ if (matches.length > 1) {
387
+ throw new Error(
388
+ "registry immutable cache is ambiguous; pin the expected registry document SHA-256",
389
+ );
390
+ }
391
+ return matches[0] || null;
392
+ }
393
+
394
+ function assertCacheDirectorySafe(directory) {
395
+ const stat = _deps.lstatSync(directory);
396
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
397
+ throw new Error("registry immutable cache path is not a directory");
398
+ }
399
+ }
400
+
401
+ function readBoundedRegularFile(file) {
402
+ let descriptor = null;
403
+ try {
404
+ const linkStat = _deps.lstatSync(file);
405
+ if (
406
+ !linkStat.isFile() ||
407
+ linkStat.isSymbolicLink() ||
408
+ linkStat.nlink !== 1 ||
409
+ linkStat.size <= 0 ||
410
+ linkStat.size > MAX_REGISTRY_DOCUMENT_BYTES
411
+ ) {
412
+ throw new Error(
413
+ "registry immutable cache entry is not a bounded single-link regular file",
414
+ );
415
+ }
416
+ descriptor = _deps.openSync(
417
+ file,
418
+ fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0),
419
+ );
420
+ const fileStat = _deps.fstatSync(descriptor);
421
+ if (
422
+ !fileStat.isFile() ||
423
+ fileStat.nlink !== 1 ||
424
+ fileStat.size <= 0 ||
425
+ fileStat.size > MAX_REGISTRY_DOCUMENT_BYTES
426
+ ) {
427
+ throw new Error(
428
+ "registry immutable cache entry changed during inspection",
429
+ );
430
+ }
431
+ const chunks = [];
432
+ let total = 0;
433
+ while (total <= MAX_REGISTRY_DOCUMENT_BYTES) {
434
+ const chunk = Buffer.allocUnsafe(
435
+ Math.min(64 * 1024, MAX_REGISTRY_DOCUMENT_BYTES + 1 - total),
436
+ );
437
+ const count = _deps.readSync(descriptor, chunk, 0, chunk.length, null);
438
+ if (count === 0) break;
439
+ chunks.push(chunk.subarray(0, count));
440
+ total += count;
441
+ }
442
+ if (total > MAX_REGISTRY_DOCUMENT_BYTES) {
443
+ throw new Error(
444
+ `registry immutable cache entry exceeds ${MAX_REGISTRY_DOCUMENT_BYTES} bytes`,
445
+ );
446
+ }
447
+ return Buffer.concat(chunks, total);
448
+ } finally {
449
+ if (descriptor != null) _deps.closeSync(descriptor);
450
+ }
451
+ }
452
+
453
+ function decodeRegistryUtf8(bytes) {
454
+ try {
455
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
456
+ } catch {
457
+ throw new Error("registry document is not valid UTF-8");
458
+ }
459
+ }
460
+
461
+ function normalizeRegistrySha256(value, label, { optional = false } = {}) {
462
+ if ((value == null || value === "") && optional) return null;
463
+ const digest = String(value || "")
464
+ .trim()
465
+ .toLowerCase();
466
+ if (!SHA256_RE.test(digest)) {
467
+ throw new Error(`${label} must be 64 lowercase hexadecimal characters`);
468
+ }
469
+ return digest;
470
+ }
471
+
472
+ function sha256(value) {
473
+ return crypto.createHash("sha256").update(value).digest("hex");
474
+ }
475
+
186
476
  /** Validate + normalize a fetched document into a registry object. Throws on garbage. */
187
477
  export function validateRegistry(doc, url = "") {
188
478
  if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
@@ -254,7 +544,11 @@ export function resolvePluginEntry(registry, name) {
254
544
  */
255
545
  export async function resolveRemoteSource(url, opts = {}) {
256
546
  const token = resolveRegistryToken(url, opts);
257
- const { registry, fromCache } = await fetchRegistry(url, { ...opts, token });
547
+ const { registry, fromCache, documentSha256, networkAuthority } =
548
+ await fetchRegistry(url, {
549
+ ...opts,
550
+ token,
551
+ });
258
552
  const entry = resolvePluginEntry(registry, opts.name);
259
553
  // Carry a `#ref` on the source string so installFromSource pins the checkout,
260
554
  // matching its existing `owner/repo#ref` / `url#ref` convention.
@@ -265,6 +559,8 @@ export async function resolveRemoteSource(url, opts = {}) {
265
559
  sha256: entry.sha256 || null,
266
560
  entry,
267
561
  fromCache,
562
+ documentSha256,
563
+ ...(networkAuthority ? { networkAuthority } : {}),
268
564
  };
269
565
  }
270
566