chainlesschain 0.163.8 → 0.164.0

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 (228) hide show
  1. package/package.json +4 -1
  2. package/scripts/macos-mcp-launcher-build.mjs +317 -0
  3. package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
  4. package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
  5. package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-D2MEn7fI.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-lCW_4Gf0.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Bs7i-nsS.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-CK7B6S_j.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-C8ILjpyo.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-Bb9cNaWc.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-sC9ooXIf.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-D-DrsplT.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-uCNZpwSK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-CsDK1uE9.js} +6 -6
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-JhTXM1-K.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-BbzZLnyD.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-D4VW4m4j.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-BXrYawxV.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-DCihGy1u.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-DlyZ-lk8.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-DvGZrhbO.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DUncZi8h.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-C_hLuUcm.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-b6YSUqxr.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-uvM15Cfb.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-D1-Nd4qY.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BbI3HGpv.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-DSoEKuFu.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-DIs9Zhf3.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-jzZXrxcl.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-JUPjf2ZI.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-iXiz8BEE.js} +2 -2
  38. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-Y3Xch312.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-D7kGGbO7.js} +3 -3
  41. package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-Do_hVxzh.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-CtrR0Tas.js} +1 -1
  43. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
  44. package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-qo5QCTGd.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-cq5mnjYj.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Bq0MAQHw.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-DdWE6Vi2.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-BxMBFcIp.js} +4 -4
  49. package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-RTrAcbUe.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-Bb1O3Ofr.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-SQzIPdqC.js} +2 -2
  52. package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-02yECh22.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-BNg4pNCM.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BxPJZsD3.js} +5 -5
  55. package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-CLnDnPAz.js} +3 -3
  56. package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-C5m0AjGf.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-VwOf5Dhv.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-CC0W2hZd.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-Dz893Bob.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-CxQkvl0k.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-DeB9IkxK.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-OCeoaswn.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-DenotaAj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-Cies_jjI.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-Biyn2-Ro.js → RemoteSession-Dxzl5n2T.js} +3 -3
  67. package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-CW0mvxgZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-SEd32qZU.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-BSwnJ3Lm.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-xe2m8bkC.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-Mq2F9Nro.js} +3 -3
  72. package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-BKGw2V3P.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-DW95-Rxf.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CHgChCv_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-tKh6k3R7.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-D6UL3dA5.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-D0Fw8fVL.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-20x1GB3D.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-DJ-lts-a.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-BFdGpbs7.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-Xpx61vHL.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-Dellq08f.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-VKw8d0J-.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-DNftdKT5.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-DX9VAKJI.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-B2h3jO8q.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-CjGkof7f.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-2Zfcu55P.js} +4 -4
  90. package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-CQNfD-YO.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-D4usyFT2.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-s4TpjvjX.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-BRCSvYxj.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-AdkUbkV-.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index--00U6vXZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-B2V-eKfK.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-B4JJCfQ2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-BIoG5yYf.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-BMcimU-2.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-BPhkFZ1I.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-BQ3DXBtD.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-Bad4VNvz.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-BgiXR_-_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-BtobShro.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-Bw2MEnUk.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-C1mJtE6q.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-C484EJiS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-C5KAQFPQ.js} +1 -1
  111. package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
  112. package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-C7Y8Aluc.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-CCsM_Ybx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-CJCFe2Qf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-CJVEOa4i.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-CJdQrZth.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-CKC1t3Ic.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-CQhAVU5t.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-CYdoQsa1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Cduu2Hhr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-CkwiSLhU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-Cvyt-zhm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-D2GjI2oe.js} +1 -1
  124. package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
  125. package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-DARsAyUp.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DVKfQ0Ux.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-DZT36ZYq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-D_XBHopw.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-DdCB70SS.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-DzRjHxEv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-MMN9V6JF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-RfMr79MW.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-_IRVsV9X.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-pPL66J85.js → index-nXyk8GZ9.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-s_buFMEG.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-Blmpk7HL.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-DPBJMv1n.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-ButdzCM7.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-cyRC_QCe.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-1epSV7hz.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DuPY8xJs.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-6XZqarhe.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-B40pXBWM.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-jbZ9Wu4n.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-BKQQcVy4.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-NiKfWjUT.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-CUGji7ph.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-4veyN2N2.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-1-nLug-Z.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-dhCeRDMo.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +3 -3
  154. package/src/commands/cowork.js +174 -19
  155. package/src/commands/permissions.js +240 -16
  156. package/src/commands/plugin.js +1293 -56
  157. package/src/commands/session-location.js +276 -0
  158. package/src/commands/session.js +2 -0
  159. package/src/data/changelog.json +15 -2
  160. package/src/gateways/ws/action-protocol.js +35 -10
  161. package/src/gateways/ws/remote-session-protocol.js +24 -1
  162. package/src/gateways/ws/ws-agent-handler.js +12 -0
  163. package/src/gateways/ws/ws-server.js +48 -12
  164. package/src/harness/jsonl-session-store.js +341 -24
  165. package/src/harness/mcp-client.js +8 -1
  166. package/src/harness/remote-session-audit.js +1 -0
  167. package/src/harness/remote-session-crypto.js +210 -2
  168. package/src/harness/remote-session-registry.js +159 -6
  169. package/src/harness/session-list-index.js +233 -14
  170. package/src/lib/agent-authority.js +6 -0
  171. package/src/lib/background-agent-keeper-client.js +248 -0
  172. package/src/lib/background-agent-keeper-protocol.js +147 -0
  173. package/src/lib/background-agent-supervisor.js +520 -40
  174. package/src/lib/background-interaction-resolver.js +12 -0
  175. package/src/lib/background-turn-bootstrap-protocol.js +126 -0
  176. package/src/lib/cowork-workflow.js +336 -16
  177. package/src/lib/dynamic-workflow-facade.js +648 -0
  178. package/src/lib/execution-location-contract.js +551 -0
  179. package/src/lib/execution-location-runtime.js +130 -0
  180. package/src/lib/ide-context.js +94 -6
  181. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  182. package/src/lib/packer/pkg-config-generator.js +2 -0
  183. package/src/lib/permission-authority.js +77 -0
  184. package/src/lib/permission-side-effect-center.js +495 -0
  185. package/src/lib/plugin-runtime/install.js +514 -13
  186. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  187. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  188. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  189. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  190. package/src/lib/plugin-security.js +60 -3
  191. package/src/lib/process-execution-broker/index.js +1095 -57
  192. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  193. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  194. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  195. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  196. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  197. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  198. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  199. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  200. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  201. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  202. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  203. package/src/lib/public-ide-capability-manifest.js +3 -0
  204. package/src/lib/remote-approval-bridge.js +123 -17
  205. package/src/lib/remote-membership-authority.js +632 -0
  206. package/src/lib/remote-membership-coordinator.js +2409 -0
  207. package/src/lib/remote-membership-host-store.js +1750 -0
  208. package/src/lib/scoped-permission-store.js +369 -0
  209. package/src/lib/session-anti-rollback-anchor.js +262 -5
  210. package/src/lib/sub-agent-context.js +4 -0
  211. package/src/lib/workflow-definition-contract.js +149 -0
  212. package/src/repl/agent-repl.js +41 -7
  213. package/src/repl/chat-repl.js +2 -0
  214. package/src/repl/clipboard-image.js +735 -16
  215. package/src/repl/ide-status.js +1 -1
  216. package/src/repl/prompt-interactions.js +59 -6
  217. package/src/runtime/agent-core.js +83 -3
  218. package/src/runtime/headless-runner.js +30 -25
  219. package/src/runtime/headless-stream.js +32 -25
  220. package/src/workers/background-agent-keeper.js +498 -0
  221. package/src/workers/background-agent-worker.js +358 -13
  222. package/src/workers/background-turn-bootstrap.js +143 -0
  223. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
  224. package/src/assets/web-panel/assets/MobileProjects-DlPwcCbq.js +0 -1
  225. package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
  227. package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
  228. package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
@@ -0,0 +1,1181 @@
1
+ /**
2
+ * Read-only marketplace governance projection.
3
+ *
4
+ * Registry metadata is publisher-supplied and therefore never presented as
5
+ * verified evidence. This module makes that distinction explicit while still
6
+ * giving humans and policy automation one bounded, versioned shape for
7
+ * comparing candidates before any plugin bytes are cloned or executed.
8
+ */
9
+
10
+ import crypto from "node:crypto";
11
+ import semver from "semver";
12
+ import {
13
+ checkPluginDependencies,
14
+ parsePluginDependencies,
15
+ } from "./governance.js";
16
+ import { describeCapabilities, normalizeCapabilities } from "./capabilities.js";
17
+
18
+ export const PLUGIN_MARKETPLACE_CATALOG_SCHEMA =
19
+ "cc-plugin-marketplace-catalog/v1";
20
+ export const PLUGIN_MARKETPLACE_INSTALL_PREFLIGHT_SCHEMA =
21
+ "cc-plugin-marketplace-install-preflight/v1";
22
+ export const PLUGIN_MARKETPLACE_CANDIDATE_SELECTION_SCHEMA =
23
+ "cc-plugin-marketplace-candidate-selection/v1";
24
+ export const MAX_MARKETPLACE_CATALOG_SOURCES = 16;
25
+ export const MAX_MARKETPLACE_CANDIDATES_PER_SOURCE = 2048;
26
+ export const MAX_MARKETPLACE_DEPENDENCIES_PER_CANDIDATE = 128;
27
+ export const MAX_MARKETPLACE_GRAPH_EDGES = 65_536;
28
+ export const MAX_MARKETPLACE_SELECTION_CANDIDATES = 1024;
29
+
30
+ const SHA256_RE = /^[a-f0-9]{64}$/i;
31
+ const HEALTH_STATES = new Set(["healthy", "degraded", "unhealthy", "unknown"]);
32
+ const INSTALL_DEFERRED_BLOCKERS = new Set(["INVALID_VERSION"]);
33
+
34
+ /**
35
+ * Derive the fail-closed pre-clone authority for one registry-selected entry.
36
+ * A legacy registry may omit version because the fetched plugin manifest is
37
+ * the actual version authority; that one catalog blocker is therefore
38
+ * deferred. Integrity, dependency, compatibility, source, and health blockers
39
+ * are enforced before any clone/process execution.
40
+ */
41
+ export function buildPluginMarketplaceInstallPreflight({
42
+ registryUrl,
43
+ entry,
44
+ fromCache = false,
45
+ installed = {},
46
+ hostVersion = null,
47
+ strict = false,
48
+ observedAt = new Date().toISOString(),
49
+ } = {}) {
50
+ const catalog = buildPluginMarketplaceCatalog({
51
+ sources: [
52
+ {
53
+ url: registryUrl,
54
+ registry: { plugins: [entry] },
55
+ fromCache,
56
+ },
57
+ ],
58
+ installed,
59
+ hostVersion,
60
+ strict,
61
+ generatedAt: observedAt,
62
+ });
63
+ const candidate = catalog.candidates[0];
64
+ if (!candidate) {
65
+ throw new Error("registry candidate preflight produced no candidate");
66
+ }
67
+ return buildInstallPreflight({ catalog, candidate, observedAt });
68
+ }
69
+
70
+ /**
71
+ * Resolve one exact plugin name across a previously-built multi-registry
72
+ * catalog. Selection is deterministic: the catalog's version-descending,
73
+ * source-priority order is authoritative. An unavailable requested source or
74
+ * a blocked highest-ranked candidate fails closed instead of silently falling
75
+ * back to a lower version/source.
76
+ */
77
+ export function buildPluginMarketplaceCandidateSelection({
78
+ catalog,
79
+ name,
80
+ observedAt = new Date().toISOString(),
81
+ } = {}) {
82
+ if (catalog?.schemaVersion !== PLUGIN_MARKETPLACE_CATALOG_SCHEMA) {
83
+ throw new Error("candidate selection requires a marketplace catalog/v1");
84
+ }
85
+ const requestedName = boundedString(name, 256);
86
+ if (!requestedName) throw new Error("candidate selection requires a name");
87
+
88
+ const matching = catalog.candidates.filter(
89
+ (candidate) => candidate.name === requestedName,
90
+ );
91
+ const selected = matching[0] || null;
92
+ const blockers = [];
93
+ const unavailableSourceIds = catalog.sources
94
+ .filter((source) => source.status === "unavailable")
95
+ .map((source) => source.sourceId)
96
+ .sort();
97
+ if (unavailableSourceIds.length) {
98
+ blockers.push(
99
+ issue("REGISTRY_SET_INCOMPLETE", unavailableSourceIds.join(", ")),
100
+ );
101
+ }
102
+ if (matching.length === 0) blockers.push(issue("PLUGIN_NOT_FOUND"));
103
+ if (matching.length > MAX_MARKETPLACE_SELECTION_CANDIDATES) {
104
+ blockers.push(
105
+ issue(
106
+ "SELECTION_CANDIDATE_LIMIT",
107
+ `${matching.length} > ${MAX_MARKETPLACE_SELECTION_CANDIDATES}`,
108
+ ),
109
+ );
110
+ }
111
+ if (selected) {
112
+ for (const blocker of effectiveInstallBlockers(selected)) {
113
+ blockers.push({
114
+ code: blocker.code,
115
+ ...(blocker.detail ? { detail: blocker.detail } : {}),
116
+ });
117
+ }
118
+ }
119
+ const uniqueBlockers = dedupeIssues(blockers);
120
+ const alternatives = matching
121
+ .slice(0, MAX_MARKETPLACE_SELECTION_CANDIDATES)
122
+ .map((candidate) => ({
123
+ candidateId: candidate.candidateId,
124
+ candidateDigest: candidate.candidateDigest,
125
+ contentDigest: candidate.contentDigest,
126
+ version: candidate.version,
127
+ registry: candidate.registry,
128
+ installability: candidate.installability.status,
129
+ selected: candidate.candidateId === selected?.candidateId,
130
+ }));
131
+ const authority = {
132
+ schemaVersion: PLUGIN_MARKETPLACE_CANDIDATE_SELECTION_SCHEMA,
133
+ algorithm: "highest-version-then-registry-priority/v1",
134
+ name: requestedName,
135
+ catalogDigest: catalog.catalogDigest,
136
+ sourceIds: catalog.sources.map((source) => source.sourceId),
137
+ selectedCandidateId: selected?.candidateId || null,
138
+ selectedCandidateDigest: selected?.candidateDigest || null,
139
+ alternatives: alternatives.map((candidate) => ({
140
+ candidateId: candidate.candidateId,
141
+ candidateDigest: candidate.candidateDigest,
142
+ })),
143
+ };
144
+ return {
145
+ schemaVersion: PLUGIN_MARKETPLACE_CANDIDATE_SELECTION_SCHEMA,
146
+ observedAt,
147
+ status: uniqueBlockers.length ? "blocked" : "allowed",
148
+ algorithm: authority.algorithm,
149
+ name: requestedName,
150
+ catalogSchemaVersion: catalog.schemaVersion,
151
+ catalogDigest: catalog.catalogDigest,
152
+ selectionDigest: sha256Canonical(authority),
153
+ sourceCount: catalog.sources.length,
154
+ candidateCount: matching.length,
155
+ selected,
156
+ alternatives,
157
+ blockers: uniqueBlockers,
158
+ claims: {
159
+ registryMetadataVerified: false,
160
+ candidateBytesFetched: false,
161
+ candidateCodeExecuted: false,
162
+ unavailableRequestedSourcesIgnored: false,
163
+ lowerRankedFallbackAllowed: false,
164
+ },
165
+ };
166
+ }
167
+
168
+ /** Bind a multi-registry selection to the existing pre-clone authority. */
169
+ export function buildPluginMarketplaceInstallPreflightFromSelection({
170
+ catalog,
171
+ selection,
172
+ observedAt = new Date().toISOString(),
173
+ } = {}) {
174
+ if (
175
+ selection?.schemaVersion !==
176
+ PLUGIN_MARKETPLACE_CANDIDATE_SELECTION_SCHEMA ||
177
+ selection.catalogDigest !== catalog?.catalogDigest
178
+ ) {
179
+ throw new Error("install preflight selection/catalog authority mismatch");
180
+ }
181
+ if (!selection.selected) {
182
+ throw new Error("install preflight selection produced no candidate");
183
+ }
184
+ return buildInstallPreflight({
185
+ catalog,
186
+ candidate: selection.selected,
187
+ observedAt,
188
+ selection,
189
+ });
190
+ }
191
+
192
+ function buildInstallPreflight({ catalog, candidate, observedAt, selection }) {
193
+ const deferred = candidate.installability.blockers.filter(
194
+ (blocker) =>
195
+ INSTALL_DEFERRED_BLOCKERS.has(blocker.code) && !candidate.version,
196
+ );
197
+ const blockers = candidate.installability.blockers.filter(
198
+ (blocker) =>
199
+ !INSTALL_DEFERRED_BLOCKERS.has(blocker.code) ||
200
+ Boolean(candidate.version),
201
+ );
202
+ const selectionBlockers = selection?.blockers || [];
203
+ const effectiveBlockers = selection
204
+ ? dedupeIssues([...blockers, ...selectionBlockers])
205
+ : blockers;
206
+ const preflight = {
207
+ schemaVersion: PLUGIN_MARKETPLACE_INSTALL_PREFLIGHT_SCHEMA,
208
+ observedAt,
209
+ status: effectiveBlockers.length ? "blocked" : "allowed",
210
+ catalogSchemaVersion: catalog.schemaVersion,
211
+ catalogDigest: catalog.catalogDigest,
212
+ ...(selection
213
+ ? {
214
+ selectionSchemaVersion: selection.schemaVersion,
215
+ selectionDigest: selection.selectionDigest,
216
+ selectionAlgorithm: selection.algorithm,
217
+ selectionSourceCount: selection.sourceCount,
218
+ }
219
+ : {}),
220
+ candidateId: candidate.candidateId,
221
+ candidateDigest: candidate.candidateDigest,
222
+ contentDigest: candidate.contentDigest,
223
+ name: candidate.name,
224
+ registry: candidate.registry,
225
+ package: candidate.package,
226
+ registryVersion: candidate.version,
227
+ versionAuthority: candidate.version
228
+ ? "registry-declared-unverified"
229
+ : "deferred-to-plugin-manifest",
230
+ governance: candidate.governance,
231
+ integrity: candidate.integrity,
232
+ license: candidate.license,
233
+ capabilities: candidate.capabilities,
234
+ compatibility: candidate.compatibility,
235
+ dependencies: candidate.dependencies,
236
+ health: candidate.health,
237
+ blockers: effectiveBlockers,
238
+ deferred,
239
+ warnings: candidate.warnings,
240
+ claims: catalog.claims,
241
+ };
242
+ return { catalog, candidate, preflight };
243
+ }
244
+
245
+ /**
246
+ * Build a deterministic governance projection from already-fetched registries.
247
+ * A source is `{ url, registry, fromCache?, error? }`.
248
+ */
249
+ export function buildPluginMarketplaceCatalog({
250
+ sources,
251
+ installed = {},
252
+ hostVersion = null,
253
+ query = "",
254
+ strict = false,
255
+ generatedAt = new Date().toISOString(),
256
+ } = {}) {
257
+ if (!Array.isArray(sources) || sources.length === 0) {
258
+ throw new Error("at least one marketplace registry source is required");
259
+ }
260
+ if (sources.length > MAX_MARKETPLACE_CATALOG_SOURCES) {
261
+ throw new Error(
262
+ `marketplace catalog accepts at most ${MAX_MARKETPLACE_CATALOG_SOURCES} sources`,
263
+ );
264
+ }
265
+
266
+ const needle = boundedString(query, 256).toLowerCase();
267
+ const installedVersions =
268
+ installed && typeof installed === "object" && !Array.isArray(installed)
269
+ ? installed
270
+ : {};
271
+ const sourceRows = [];
272
+ const candidates = [];
273
+ const seenSourceIds = new Set();
274
+
275
+ for (const [sourceIndex, input] of sources.entries()) {
276
+ const source = normalizeCatalogSource(input, sourceIndex);
277
+ sourceRows.push(source.projection);
278
+ if (seenSourceIds.has(source.projection.sourceId)) {
279
+ source.registry = null;
280
+ source.projection.status = "unavailable";
281
+ source.projection.error = {
282
+ code: "DUPLICATE_REGISTRY_SOURCE",
283
+ message: "the same sanitized registry URL was supplied more than once",
284
+ };
285
+ }
286
+ seenSourceIds.add(source.projection.sourceId);
287
+ if (!source.registry) continue;
288
+
289
+ const entries = source.registry.plugins;
290
+ if (!Array.isArray(entries)) {
291
+ source.projection.status = "unavailable";
292
+ source.projection.error = {
293
+ code: "INVALID_REGISTRY_DOCUMENT",
294
+ message: "registry document does not contain a plugins array",
295
+ };
296
+ continue;
297
+ }
298
+ if (entries.length > MAX_MARKETPLACE_CANDIDATES_PER_SOURCE) {
299
+ source.projection.status = "unavailable";
300
+ source.projection.error = {
301
+ code: "REGISTRY_CANDIDATE_LIMIT",
302
+ message: `registry exceeds the ${MAX_MARKETPLACE_CANDIDATES_PER_SOURCE}-candidate limit`,
303
+ };
304
+ continue;
305
+ }
306
+
307
+ for (const [entryIndex, entry] of entries.entries()) {
308
+ const candidate = normalizeCandidate(entry, {
309
+ source: source.projection,
310
+ entryIndex,
311
+ installed: installedVersions,
312
+ hostVersion,
313
+ strict,
314
+ });
315
+ if (
316
+ needle &&
317
+ !`${candidate.name} ${candidate.description}`
318
+ .toLowerCase()
319
+ .includes(needle)
320
+ ) {
321
+ continue;
322
+ }
323
+ candidates.push(candidate);
324
+ }
325
+ source.projection.candidateCount = candidates.filter(
326
+ (candidate) => candidate.registry.sourceId === source.projection.sourceId,
327
+ ).length;
328
+ }
329
+
330
+ applySourceConflicts(candidates);
331
+ candidates.sort(compareCandidates);
332
+ sourceRows.sort((a, b) => a.priority - b.priority);
333
+ const dependencyGraph = buildDependencyGraph(candidates, installedVersions);
334
+
335
+ const summary = {
336
+ sourceCount: sourceRows.length,
337
+ availableSourceCount: sourceRows.filter(
338
+ (source) => source.status !== "unavailable",
339
+ ).length,
340
+ unavailableSourceCount: sourceRows.filter(
341
+ (source) => source.status === "unavailable",
342
+ ).length,
343
+ cachedSourceCount: sourceRows.filter((source) => source.status === "cached")
344
+ .length,
345
+ candidateCount: candidates.length,
346
+ installableCandidateCount: candidates.filter(
347
+ (candidate) => candidate.installability.status === "allowed",
348
+ ).length,
349
+ blockedCandidateCount: candidates.filter(
350
+ (candidate) => candidate.installability.status === "blocked",
351
+ ).length,
352
+ incompleteCandidateCount: candidates.filter(
353
+ (candidate) => candidate.governance.status !== "complete",
354
+ ).length,
355
+ conflictCount: candidates.filter((candidate) => candidate.conflict).length,
356
+ };
357
+
358
+ const authority = {
359
+ schemaVersion: PLUGIN_MARKETPLACE_CATALOG_SCHEMA,
360
+ strict: strict === true,
361
+ hostVersion: boundedString(hostVersion, 128) || null,
362
+ sources: sourceRows.map(({ error: _error, ...source }) => source),
363
+ candidates,
364
+ dependencyGraph,
365
+ };
366
+
367
+ return {
368
+ schemaVersion: PLUGIN_MARKETPLACE_CATALOG_SCHEMA,
369
+ generatedAt,
370
+ catalogDigest: sha256Canonical(authority),
371
+ mode: strict ? "strict" : "advisory",
372
+ host: { version: boundedString(hostVersion, 128) || null },
373
+ sources: sourceRows,
374
+ candidates,
375
+ dependencyGraph,
376
+ summary,
377
+ claims: {
378
+ registryMetadataVerified: false,
379
+ pluginBytesFetched: false,
380
+ pluginCodeExecuted: false,
381
+ signatureVerificationStage: "install/load",
382
+ },
383
+ };
384
+ }
385
+
386
+ function normalizeCatalogSource(input, priority) {
387
+ const raw = input && typeof input === "object" ? input : {};
388
+ const sanitizedUrl = sanitizeUrl(raw.url);
389
+ const url = sanitizedUrl || `invalid-source-${priority + 1}`;
390
+ const error = raw.error
391
+ ? {
392
+ code: boundedString(raw.error.code, 64) || "REGISTRY_FETCH_FAILED",
393
+ message: scrubError(raw.error.message || raw.error),
394
+ }
395
+ : !sanitizedUrl
396
+ ? {
397
+ code: "INVALID_REGISTRY_URL",
398
+ message: "registry source URL is missing or invalid",
399
+ }
400
+ : null;
401
+ const registry =
402
+ raw.registry && typeof raw.registry === "object" ? raw.registry : null;
403
+ return {
404
+ registry: error ? null : registry,
405
+ projection: {
406
+ sourceId: `registry-${sha256(url).slice(0, 16)}`,
407
+ priority,
408
+ url,
409
+ name: boundedString(registry?.name, 256) || null,
410
+ status: error ? "unavailable" : raw.fromCache ? "cached" : "online",
411
+ candidateCount: 0,
412
+ ...(error ? { error } : {}),
413
+ },
414
+ };
415
+ }
416
+
417
+ function normalizeCandidate(entry, context) {
418
+ const raw = entry && typeof entry === "object" ? entry : {};
419
+ const rawName =
420
+ typeof raw.name === "string" ? boundedString(raw.name, 256) : "";
421
+ const name = rawName || "(invalid-name)";
422
+ const version =
423
+ typeof raw.version === "string"
424
+ ? boundedString(raw.version, 128) || null
425
+ : null;
426
+ const description = boundedString(raw.description, 2048);
427
+ const sourceValue =
428
+ typeof raw.source === "string" ? boundedString(raw.source, 4096) : "";
429
+ const sourceUrl = sanitizeUrl(sourceValue) || sourceValue || null;
430
+ const ref = boundedString(raw.ref, 256) || null;
431
+ const blockers = [];
432
+ const warnings = [];
433
+
434
+ if (!rawName || !/^[a-zA-Z0-9._@/-]+$/.test(rawName)) {
435
+ blockers.push(issue("INVALID_NAME"));
436
+ }
437
+ if (!version || !semver.valid(version))
438
+ blockers.push(issue("INVALID_VERSION"));
439
+ if (!sourceValue) blockers.push(issue("MISSING_PACKAGE_SOURCE"));
440
+ if (hasUrlCredentials(sourceValue)) {
441
+ blockers.push(issue("SOURCE_CREDENTIALS_EMBEDDED"));
442
+ }
443
+
444
+ const integrity = normalizeIntegrity(raw, blockers, warnings);
445
+ const license = normalizeLicense(raw.license);
446
+ const capabilitiesDeclared = hasCapabilityDeclaration(raw);
447
+ const capabilities = normalizeCapabilities(
448
+ raw.permissions ?? raw.capabilities ?? null,
449
+ );
450
+ const capabilitySummary = describeCapabilities(capabilities);
451
+
452
+ const compatibilityRange =
453
+ boundedString(raw.compatibility?.cc, 256) ||
454
+ boundedString(raw.engines?.cc, 256) ||
455
+ null;
456
+ const dependencyObject =
457
+ raw.dependencies &&
458
+ typeof raw.dependencies === "object" &&
459
+ !Array.isArray(raw.dependencies)
460
+ ? raw.dependencies
461
+ : null;
462
+ const dependencyEntries = dependencyObject
463
+ ? Object.entries(dependencyObject).sort(([a], [b]) => a.localeCompare(b))
464
+ : [];
465
+ if (dependencyEntries.length > MAX_MARKETPLACE_DEPENDENCIES_PER_CANDIDATE) {
466
+ blockers.push(issue("DEPENDENCY_LIMIT_EXCEEDED"));
467
+ }
468
+ const dependencyManifest = {
469
+ dependencies: dependencyObject
470
+ ? Object.fromEntries(
471
+ dependencyEntries.slice(
472
+ 0,
473
+ MAX_MARKETPLACE_DEPENDENCIES_PER_CANDIDATE,
474
+ ),
475
+ )
476
+ : raw.dependencies,
477
+ };
478
+ if (compatibilityRange && dependencyManifest.dependencies) {
479
+ dependencyManifest.dependencies = {
480
+ ...dependencyManifest.dependencies,
481
+ host: compatibilityRange,
482
+ };
483
+ } else if (compatibilityRange) {
484
+ dependencyManifest.dependencies = { host: compatibilityRange };
485
+ }
486
+ const parsedDependencies = parsePluginDependencies(dependencyManifest);
487
+ const dependencyCheck = checkPluginDependencies(dependencyManifest, {
488
+ installed: context.installed,
489
+ hostVersion: context.hostVersion,
490
+ });
491
+ if (parsedDependencies.errors.length) {
492
+ blockers.push(
493
+ issue("INVALID_DEPENDENCIES", parsedDependencies.errors.join("; ")),
494
+ );
495
+ }
496
+ if (dependencyCheck.missing.length)
497
+ blockers.push(issue("MISSING_DEPENDENCY"));
498
+ if (dependencyCheck.mismatched.length) {
499
+ blockers.push(issue("DEPENDENCY_VERSION_MISMATCH"));
500
+ }
501
+ if (dependencyCheck.hostMismatch) blockers.push(issue("HOST_INCOMPATIBLE"));
502
+ if (compatibilityRange && !semver.validRange(compatibilityRange)) {
503
+ blockers.push(issue("INVALID_HOST_COMPATIBILITY"));
504
+ }
505
+
506
+ const publisherHealth = normalizePublisherHealth(raw.health);
507
+ if (publisherHealth.status === "unhealthy") {
508
+ blockers.push(issue("PUBLISHER_HEALTH_UNHEALTHY"));
509
+ }
510
+
511
+ const missingGovernance = [];
512
+ if (integrity.digest.status !== "declared") missingGovernance.push("digest");
513
+ if (integrity.signature.status !== "declared") {
514
+ missingGovernance.push("signature");
515
+ }
516
+ if (integrity.sbom.status !== "declared") missingGovernance.push("sbom");
517
+ if (integrity.signature.remoteVerification === "incomplete") {
518
+ missingGovernance.push("signature-artifact");
519
+ }
520
+ if (integrity.sbom.remoteVerification === "incomplete") {
521
+ missingGovernance.push(
522
+ integrity.sbom.url ? "sbom-document-digest" : "sbom-artifact-url",
523
+ );
524
+ }
525
+ if (license.status !== "declared") missingGovernance.push("license");
526
+ if (!capabilitiesDeclared) missingGovernance.push("capabilities");
527
+ if (missingGovernance.length) {
528
+ warnings.push(
529
+ issue("GOVERNANCE_METADATA_INCOMPLETE", missingGovernance.join(", ")),
530
+ );
531
+ if (context.strict) {
532
+ blockers.push(
533
+ issue(
534
+ "STRICT_GOVERNANCE_METADATA_REQUIRED",
535
+ missingGovernance.join(", "),
536
+ ),
537
+ );
538
+ }
539
+ }
540
+
541
+ const identity = `${name}@${version || "unknown"}`;
542
+ const candidateId = `candidate-${sha256(
543
+ `${context.source.sourceId}\0${identity}\0${context.entryIndex}`,
544
+ ).slice(0, 20)}`;
545
+ const candidate = {
546
+ candidateId,
547
+ name,
548
+ version,
549
+ description,
550
+ registry: {
551
+ sourceId: context.source.sourceId,
552
+ priority: context.source.priority,
553
+ url: context.source.url,
554
+ status: context.source.status,
555
+ },
556
+ package: { source: sourceUrl, ref },
557
+ integrity,
558
+ license,
559
+ capabilities: {
560
+ declared: capabilitiesDeclared,
561
+ summary: capabilitySummary,
562
+ normalized: capabilities,
563
+ },
564
+ compatibility: {
565
+ hostVersion: context.hostVersion || null,
566
+ range: parsedDependencies.hostRange || compatibilityRange,
567
+ status: parsedDependencies.errors.some((message) =>
568
+ message.includes("host"),
569
+ )
570
+ ? "invalid"
571
+ : dependencyCheck.hostMismatch
572
+ ? "incompatible"
573
+ : parsedDependencies.hostRange || compatibilityRange
574
+ ? context.hostVersion
575
+ ? "compatible"
576
+ : "unknown"
577
+ : "unknown",
578
+ },
579
+ dependencies: {
580
+ declared: parsedDependencies.deps,
581
+ status: parsedDependencies.errors.length
582
+ ? "invalid"
583
+ : dependencyCheck.missing.length || dependencyCheck.mismatched.length
584
+ ? "unsatisfied"
585
+ : Object.keys(parsedDependencies.deps).length
586
+ ? "satisfied"
587
+ : "none",
588
+ missing: dependencyCheck.missing,
589
+ mismatched: dependencyCheck.mismatched,
590
+ errors: parsedDependencies.errors,
591
+ },
592
+ health: {
593
+ registry: context.source.status,
594
+ publisher: publisherHealth,
595
+ status:
596
+ publisherHealth.status === "unhealthy"
597
+ ? "unhealthy"
598
+ : context.source.status === "cached"
599
+ ? "cached"
600
+ : publisherHealth.status,
601
+ },
602
+ governance: {
603
+ status: missingGovernance.length ? "incomplete" : "complete",
604
+ missing: missingGovernance,
605
+ metadataAuthority: "unverified-registry-assertion",
606
+ },
607
+ conflict: null,
608
+ warnings,
609
+ installability: { status: "allowed", blockers },
610
+ };
611
+ candidate.installability.status = blockers.length ? "blocked" : "allowed";
612
+ const contentAuthority = {
613
+ schemaVersion: PLUGIN_MARKETPLACE_CATALOG_SCHEMA,
614
+ name: candidate.name,
615
+ version: candidate.version,
616
+ package: candidate.package,
617
+ integrity: candidate.integrity,
618
+ license: candidate.license,
619
+ capabilities: candidate.capabilities,
620
+ compatibility: {
621
+ range: candidate.compatibility.range,
622
+ },
623
+ dependencies: {
624
+ declared: candidate.dependencies.declared,
625
+ errors: candidate.dependencies.errors,
626
+ },
627
+ publisherHealth: candidate.health.publisher,
628
+ governance: candidate.governance,
629
+ };
630
+ candidate.contentDigest = sha256Canonical(contentAuthority);
631
+ candidate.candidateDigest = sha256Canonical({
632
+ schemaVersion: PLUGIN_MARKETPLACE_CATALOG_SCHEMA,
633
+ sourceId: candidate.registry.sourceId,
634
+ registryUrl: candidate.registry.url,
635
+ contentDigest: candidate.contentDigest,
636
+ });
637
+ Object.defineProperty(candidate.registry, "entryIndex", {
638
+ value: context.entryIndex,
639
+ enumerable: false,
640
+ });
641
+ Object.defineProperty(candidate, "_identity", {
642
+ value: identity,
643
+ enumerable: false,
644
+ });
645
+ Object.defineProperty(candidate, "_fingerprint", {
646
+ value: candidate.contentDigest,
647
+ enumerable: false,
648
+ });
649
+ return candidate;
650
+ }
651
+
652
+ function normalizeIntegrity(raw, blockers, warnings) {
653
+ const digestValue = normalizeDigestAliases(
654
+ [
655
+ ["sha256", raw.sha256],
656
+ ["digest.sha256", raw.digest?.sha256],
657
+ ],
658
+ blockers,
659
+ "CONFLICTING_MANIFEST_DIGEST_DECLARATIONS",
660
+ );
661
+ const digestStatus = !digestValue
662
+ ? "missing"
663
+ : SHA256_RE.test(digestValue)
664
+ ? "declared"
665
+ : "invalid";
666
+ if (digestStatus === "invalid") blockers.push(issue("INVALID_DIGEST"));
667
+
668
+ const signatureRaw = raw.signature;
669
+ const signatureObject =
670
+ signatureRaw &&
671
+ typeof signatureRaw === "object" &&
672
+ !Array.isArray(signatureRaw);
673
+ const signatureAlgorithm = boundedString(signatureRaw?.algorithm, 64) || null;
674
+ const signatureKeyId = boundedString(signatureRaw?.keyId, 256) || null;
675
+ const signatureKeyDigest =
676
+ boundedString(signatureRaw?.publicKeySha256, 128) || null;
677
+ const signatureUrl = sanitizeHttpUrl(signatureRaw?.url) || null;
678
+ const signaturePublicKeyUrl =
679
+ sanitizeHttpUrl(
680
+ signatureRaw?.publicKeyUrl ?? signatureRaw?.publicKey?.url,
681
+ ) || null;
682
+ const signatureDocumentSha256 = normalizeDigestAliases(
683
+ [
684
+ ["documentSha256", signatureRaw?.documentSha256],
685
+ ["sha256", signatureRaw?.sha256],
686
+ ["digest", signatureRaw?.digest],
687
+ ],
688
+ blockers,
689
+ "CONFLICTING_SIGNATURE_DIGEST_DECLARATIONS",
690
+ );
691
+ const signaturePublicKeyDocumentSha256 = normalizeDigestAliases(
692
+ [
693
+ ["publicKeyDocumentSha256", signatureRaw?.publicKeyDocumentSha256],
694
+ ["publicKey.documentSha256", signatureRaw?.publicKey?.documentSha256],
695
+ ["publicKey.sha256", signatureRaw?.publicKey?.sha256],
696
+ ],
697
+ blockers,
698
+ "CONFLICTING_PUBLIC_KEY_DOCUMENT_DIGEST_DECLARATIONS",
699
+ );
700
+ const signatureDeclared = Boolean(
701
+ signatureObject &&
702
+ signatureAlgorithm &&
703
+ (signatureKeyId ||
704
+ signatureKeyDigest ||
705
+ signatureUrl ||
706
+ signaturePublicKeyUrl),
707
+ );
708
+ const signature = {
709
+ status: signatureDeclared ? "declared" : "missing",
710
+ verification: "not-verified-at-catalog-time",
711
+ subject: "plugin-manifest",
712
+ algorithm: signatureAlgorithm,
713
+ keyId: signatureKeyId,
714
+ publicKeySha256: signatureKeyDigest,
715
+ url: signatureUrl,
716
+ publicKeyUrl: signaturePublicKeyUrl,
717
+ documentSha256: signatureDocumentSha256,
718
+ publicKeyDocumentSha256: signaturePublicKeyDocumentSha256,
719
+ remoteVerification:
720
+ signatureUrl && signaturePublicKeyUrl && signatureKeyDigest
721
+ ? "complete"
722
+ : signatureUrl || signaturePublicKeyUrl
723
+ ? "incomplete"
724
+ : "not-requested",
725
+ };
726
+ if (signature.publicKeySha256 && !SHA256_RE.test(signature.publicKeySha256)) {
727
+ signature.status = "invalid";
728
+ blockers.push(issue("INVALID_SIGNATURE_KEY_DIGEST"));
729
+ }
730
+ if (signatureDeclared && signature.algorithm?.toLowerCase() !== "ed25519") {
731
+ signature.status = "invalid";
732
+ blockers.push(issue("UNSUPPORTED_SIGNATURE_ALGORITHM"));
733
+ }
734
+ if (signature.documentSha256 && !SHA256_RE.test(signature.documentSha256)) {
735
+ signature.status = "invalid";
736
+ blockers.push(issue("INVALID_SIGNATURE_DIGEST"));
737
+ }
738
+ if (
739
+ signature.publicKeyDocumentSha256 &&
740
+ !SHA256_RE.test(signature.publicKeyDocumentSha256)
741
+ ) {
742
+ signature.status = "invalid";
743
+ blockers.push(issue("INVALID_PUBLIC_KEY_DOCUMENT_DIGEST"));
744
+ }
745
+ if (signature.remoteVerification === "incomplete") {
746
+ warnings.push(issue("REMOTE_SIGNATURE_ARTIFACT_INCOMPLETE"));
747
+ }
748
+ if (
749
+ (signatureRaw?.url && !signature.url) ||
750
+ ((signatureRaw?.publicKeyUrl || signatureRaw?.publicKey?.url) &&
751
+ !signature.publicKeyUrl)
752
+ ) {
753
+ signature.status = "invalid";
754
+ blockers.push(issue("INVALID_REMOTE_ARTIFACT_URL"));
755
+ }
756
+ if (
757
+ hasUrlCredentials(signatureRaw?.url) ||
758
+ hasUrlCredentials(
759
+ signatureRaw?.publicKeyUrl ?? signatureRaw?.publicKey?.url,
760
+ )
761
+ ) {
762
+ signature.status = "invalid";
763
+ blockers.push(issue("REMOTE_ARTIFACT_URL_CREDENTIALS_EMBEDDED"));
764
+ }
765
+
766
+ const sbomRaw = raw.sbom;
767
+ const sbomObject =
768
+ sbomRaw && typeof sbomRaw === "object" && !Array.isArray(sbomRaw);
769
+ const sbomFormat = boundedString(sbomRaw?.format, 128) || null;
770
+ const sbomUrl = sanitizeHttpUrl(sbomRaw?.url) || null;
771
+ const sbomDocumentSha256 = normalizeDigestAliases(
772
+ [
773
+ ["documentSha256", sbomRaw?.documentSha256],
774
+ ["documentDigest", sbomRaw?.documentDigest],
775
+ ],
776
+ blockers,
777
+ "CONFLICTING_SBOM_DOCUMENT_DIGEST_DECLARATIONS",
778
+ );
779
+ const sbomPayloadSha256 = normalizeDigestAliases(
780
+ [
781
+ ["payloadSha256", sbomRaw?.payloadSha256],
782
+ ["payloadDigest", sbomRaw?.payloadDigest],
783
+ ["digest", sbomRaw?.digest],
784
+ ["sha256", sbomRaw?.sha256],
785
+ ],
786
+ blockers,
787
+ "CONFLICTING_SBOM_PAYLOAD_DIGEST_DECLARATIONS",
788
+ );
789
+ const sbomDigest = sbomPayloadSha256;
790
+ const sbomDeclared = Boolean(
791
+ sbomObject && (sbomDigest || sbomUrl || sbomDocumentSha256),
792
+ );
793
+ const sbom = {
794
+ status: sbomDeclared ? "declared" : "missing",
795
+ verification: "not-verified-at-catalog-time",
796
+ subject: "plugin-files",
797
+ format: sbomFormat,
798
+ digest: sbomDigest || null,
799
+ documentSha256: sbomDocumentSha256,
800
+ payloadSha256: sbomPayloadSha256,
801
+ url: sbomUrl,
802
+ remoteVerification:
803
+ sbomUrl && sbomDocumentSha256
804
+ ? "complete"
805
+ : sbomUrl || (sbomDocumentSha256 && !sbomPayloadSha256)
806
+ ? "incomplete"
807
+ : "not-requested",
808
+ };
809
+ if (
810
+ [sbomDocumentSha256, sbomPayloadSha256].some(
811
+ (digest) => digest && !SHA256_RE.test(digest),
812
+ )
813
+ ) {
814
+ sbom.status = "invalid";
815
+ blockers.push(issue("INVALID_SBOM_DIGEST"));
816
+ }
817
+ if (sbom.remoteVerification === "incomplete") {
818
+ warnings.push(issue("REMOTE_SBOM_ARTIFACT_INCOMPLETE"));
819
+ }
820
+ if (sbomRaw?.url && !sbom.url) {
821
+ sbom.status = "invalid";
822
+ blockers.push(issue("INVALID_REMOTE_ARTIFACT_URL"));
823
+ }
824
+ if (hasUrlCredentials(sbomRaw?.url)) {
825
+ sbom.status = "invalid";
826
+ blockers.push(issue("REMOTE_ARTIFACT_URL_CREDENTIALS_EMBEDDED"));
827
+ }
828
+
829
+ return {
830
+ digest: {
831
+ status: digestStatus,
832
+ algorithm: "sha256",
833
+ subject: "plugin-manifest",
834
+ value: digestStatus === "declared" ? digestValue.toLowerCase() : null,
835
+ authority: "registry-declared",
836
+ },
837
+ signature,
838
+ sbom,
839
+ };
840
+ }
841
+
842
+ function normalizeLicense(value) {
843
+ const expression = boundedString(value, 256) || null;
844
+ return {
845
+ status: expression ? "declared" : "missing",
846
+ expression,
847
+ verification: "not-verified-at-catalog-time",
848
+ };
849
+ }
850
+
851
+ function normalizePublisherHealth(value) {
852
+ const raw = value && typeof value === "object" ? value : {};
853
+ const stated = boundedString(raw.status ?? value, 32).toLowerCase();
854
+ return {
855
+ status: HEALTH_STATES.has(stated) ? stated : "unknown",
856
+ checkedAt: boundedString(raw.checkedAt, 64) || null,
857
+ authority: "registry-declared",
858
+ };
859
+ }
860
+
861
+ function buildDependencyGraph(candidates, installed) {
862
+ const nodes = candidates.map((candidate) => ({
863
+ nodeId: candidate.candidateId,
864
+ type: "candidate",
865
+ name: candidate.name,
866
+ version: candidate.version,
867
+ sourceId: candidate.registry.sourceId,
868
+ }));
869
+ const candidatesByName = new Map();
870
+ for (const candidate of candidates) {
871
+ const rows = candidatesByName.get(candidate.name) || [];
872
+ rows.push(candidate);
873
+ candidatesByName.set(candidate.name, rows);
874
+ }
875
+
876
+ const installedNodes = new Map();
877
+ const edges = [];
878
+ let truncated = false;
879
+ outer: for (const candidate of candidates) {
880
+ for (const [dependencyName, range] of Object.entries(
881
+ candidate.dependencies.declared,
882
+ )) {
883
+ if (edges.length >= MAX_MARKETPLACE_GRAPH_EDGES) {
884
+ truncated = true;
885
+ break outer;
886
+ }
887
+ const targetCandidateIds = (candidatesByName.get(dependencyName) || [])
888
+ .filter(
889
+ (target) =>
890
+ semver.valid(target.version) &&
891
+ semver.satisfies(target.version, range, {
892
+ includePrerelease: true,
893
+ }),
894
+ )
895
+ .map((target) => target.candidateId)
896
+ .sort();
897
+ const installedVersion =
898
+ boundedString(installed[dependencyName], 128) || null;
899
+ const installedSatisfied = Boolean(
900
+ installedVersion &&
901
+ (semver.valid(installedVersion) || semver.coerce(installedVersion)) &&
902
+ semver.satisfies(
903
+ semver.valid(installedVersion) ||
904
+ semver.coerce(installedVersion).version,
905
+ range,
906
+ { includePrerelease: true },
907
+ ),
908
+ );
909
+ let installedNodeId = null;
910
+ if (installedVersion) {
911
+ installedNodeId = `installed-${sha256(
912
+ `${dependencyName}\0${installedVersion}`,
913
+ ).slice(0, 20)}`;
914
+ if (!installedNodes.has(installedNodeId)) {
915
+ installedNodes.set(installedNodeId, {
916
+ nodeId: installedNodeId,
917
+ type: "installed",
918
+ name: dependencyName,
919
+ version: installedVersion,
920
+ });
921
+ }
922
+ }
923
+ edges.push({
924
+ from: candidate.candidateId,
925
+ dependency: dependencyName,
926
+ range,
927
+ installedNodeId,
928
+ targetCandidateIds,
929
+ status: installedSatisfied
930
+ ? "installed-satisfied"
931
+ : targetCandidateIds.length
932
+ ? "catalog-available"
933
+ : installedVersion
934
+ ? "installed-mismatch"
935
+ : "unresolved",
936
+ });
937
+ }
938
+ }
939
+ nodes.push(...installedNodes.values());
940
+ nodes.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
941
+ edges.sort(
942
+ (a, b) =>
943
+ a.from.localeCompare(b.from) || a.dependency.localeCompare(b.dependency),
944
+ );
945
+
946
+ return {
947
+ nodes,
948
+ edges,
949
+ cycles: findDependencyCycles(candidates, edges),
950
+ truncated,
951
+ maxEdges: MAX_MARKETPLACE_GRAPH_EDGES,
952
+ };
953
+ }
954
+
955
+ function findDependencyCycles(candidates, edges) {
956
+ const candidateIds = new Set(
957
+ candidates.map((candidate) => candidate.candidateId),
958
+ );
959
+ const adjacency = new Map();
960
+ for (const edge of edges) {
961
+ const targets = adjacency.get(edge.from) || [];
962
+ for (const target of edge.targetCandidateIds) {
963
+ if (candidateIds.has(target)) targets.push(target);
964
+ }
965
+ adjacency.set(edge.from, [...new Set(targets)].sort());
966
+ }
967
+
968
+ const state = new Map();
969
+ const found = new Map();
970
+ for (const candidate of candidates) {
971
+ const root = candidate.candidateId;
972
+ if (state.has(root)) continue;
973
+ const path = [root];
974
+ const pathIndex = new Map([[root, 0]]);
975
+ const stack = [
976
+ { nodeId: root, targets: adjacency.get(root) || [], nextTarget: 0 },
977
+ ];
978
+ state.set(root, 1);
979
+ while (stack.length) {
980
+ const frame = stack[stack.length - 1];
981
+ if (frame.nextTarget >= frame.targets.length) {
982
+ state.set(frame.nodeId, 2);
983
+ stack.pop();
984
+ pathIndex.delete(frame.nodeId);
985
+ path.pop();
986
+ continue;
987
+ }
988
+ const target = frame.targets[frame.nextTarget++];
989
+ if (!state.has(target)) {
990
+ state.set(target, 1);
991
+ pathIndex.set(target, path.length);
992
+ path.push(target);
993
+ stack.push({
994
+ nodeId: target,
995
+ targets: adjacency.get(target) || [],
996
+ nextTarget: 0,
997
+ });
998
+ } else if (state.get(target) === 1 && pathIndex.has(target)) {
999
+ const cycle = canonicalCycle(path.slice(pathIndex.get(target)));
1000
+ found.set(cycle.join("\0"), cycle);
1001
+ }
1002
+ }
1003
+ }
1004
+ return [...found.values()].sort((a, b) =>
1005
+ a.join("\0").localeCompare(b.join("\0")),
1006
+ );
1007
+ }
1008
+
1009
+ function canonicalCycle(cycle) {
1010
+ if (cycle.length < 2) return cycle;
1011
+ const rotations = cycle.map((_, index) => [
1012
+ ...cycle.slice(index),
1013
+ ...cycle.slice(0, index),
1014
+ ]);
1015
+ rotations.sort((a, b) => a.join("\0").localeCompare(b.join("\0")));
1016
+ return rotations[0];
1017
+ }
1018
+
1019
+ function applySourceConflicts(candidates) {
1020
+ const groups = new Map();
1021
+ for (const candidate of candidates) {
1022
+ const group = groups.get(candidate._identity) || [];
1023
+ group.push(candidate);
1024
+ groups.set(candidate._identity, group);
1025
+ }
1026
+ for (const group of groups.values()) {
1027
+ const fingerprints = new Set(
1028
+ group.map((candidate) => candidate._fingerprint),
1029
+ );
1030
+ if (fingerprints.size <= 1) continue;
1031
+ const sourceIds = group
1032
+ .map((candidate) => candidate.registry.sourceId)
1033
+ .sort();
1034
+ for (const candidate of group) {
1035
+ candidate.conflict = {
1036
+ code: "SOURCE_CONFLICT",
1037
+ sourceIds,
1038
+ message:
1039
+ "same name/version resolves to different package or integrity metadata",
1040
+ };
1041
+ candidate.installability.blockers.push(issue("SOURCE_CONFLICT"));
1042
+ candidate.installability.status = "blocked";
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ function hasCapabilityDeclaration(raw) {
1048
+ const value = raw.permissions ?? raw.capabilities;
1049
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
1050
+ }
1051
+
1052
+ function compareCandidates(a, b) {
1053
+ const name = a.name.localeCompare(b.name);
1054
+ if (name) return name;
1055
+ const av = semver.valid(a.version);
1056
+ const bv = semver.valid(b.version);
1057
+ if (av && bv && av !== bv) return semver.rcompare(av, bv);
1058
+ const version = String(b.version || "").localeCompare(
1059
+ String(a.version || ""),
1060
+ );
1061
+ return version || a.registry.priority - b.registry.priority;
1062
+ }
1063
+
1064
+ function effectiveInstallBlockers(candidate) {
1065
+ return candidate.installability.blockers.filter(
1066
+ (blocker) =>
1067
+ !INSTALL_DEFERRED_BLOCKERS.has(blocker.code) ||
1068
+ Boolean(candidate.version),
1069
+ );
1070
+ }
1071
+
1072
+ function dedupeIssues(issues) {
1073
+ const seen = new Set();
1074
+ const result = [];
1075
+ for (const candidate of issues) {
1076
+ const normalized = issue(candidate.code, candidate.detail);
1077
+ const key = `${normalized.code}\0${normalized.detail || ""}`;
1078
+ if (seen.has(key)) continue;
1079
+ seen.add(key);
1080
+ result.push(normalized);
1081
+ }
1082
+ return result.sort((a, b) =>
1083
+ `${a.code}\0${a.detail || ""}`.localeCompare(
1084
+ `${b.code}\0${b.detail || ""}`,
1085
+ ),
1086
+ );
1087
+ }
1088
+
1089
+ function issue(code, detail = null) {
1090
+ return { code, ...(detail ? { detail: boundedString(detail, 2048) } : {}) };
1091
+ }
1092
+
1093
+ function boundedString(value, max) {
1094
+ if (value == null) return "";
1095
+ const clean = String(value)
1096
+ .replace(/\p{Cc}/gu, "")
1097
+ .trim();
1098
+ return clean.slice(0, max);
1099
+ }
1100
+
1101
+ function normalizeDigestAliases(entries, blockers, conflictCode) {
1102
+ const declared = entries
1103
+ .map(([name, value]) => [name, boundedString(value, 128)])
1104
+ .filter(([, value]) => Boolean(value));
1105
+ const distinct = new Set(declared.map(([, value]) => value.toLowerCase()));
1106
+ if (distinct.size > 1) {
1107
+ blockers.push(
1108
+ issue(conflictCode, declared.map(([name]) => name).join(", ")),
1109
+ );
1110
+ }
1111
+ return declared[0]?.[1] || null;
1112
+ }
1113
+
1114
+ function sanitizeUrl(value) {
1115
+ const raw = String(value == null ? "" : value)
1116
+ .replace(/\p{Cc}/gu, "")
1117
+ .trim();
1118
+ if (!raw) return null;
1119
+ if (raw.length > 4096) return null;
1120
+ try {
1121
+ const parsed = new URL(raw);
1122
+ parsed.username = "";
1123
+ parsed.password = "";
1124
+ parsed.search = "";
1125
+ parsed.hash = "";
1126
+ return parsed.toString();
1127
+ } catch {
1128
+ return null;
1129
+ }
1130
+ }
1131
+
1132
+ function sanitizeHttpUrl(value) {
1133
+ const sanitized = sanitizeUrl(value);
1134
+ if (!sanitized) return null;
1135
+ try {
1136
+ const parsed = new URL(sanitized);
1137
+ return parsed.protocol === "https:" || parsed.protocol === "http:"
1138
+ ? parsed.toString()
1139
+ : null;
1140
+ } catch {
1141
+ return null;
1142
+ }
1143
+ }
1144
+
1145
+ function hasUrlCredentials(value) {
1146
+ try {
1147
+ const parsed = new URL(String(value));
1148
+ return Boolean(parsed.username || parsed.password);
1149
+ } catch {
1150
+ return false;
1151
+ }
1152
+ }
1153
+
1154
+ function scrubError(value) {
1155
+ const text = boundedString(value, 1024).replace(
1156
+ /Bearer\s+\S+/gi,
1157
+ "Bearer [REDACTED]",
1158
+ );
1159
+ return text.replace(
1160
+ /https?:\/\/[^\s)]+/gi,
1161
+ (match) => sanitizeUrl(match) || "[URL]",
1162
+ );
1163
+ }
1164
+
1165
+ function sha256(value) {
1166
+ return crypto.createHash("sha256").update(String(value)).digest("hex");
1167
+ }
1168
+
1169
+ function sha256Canonical(value) {
1170
+ return sha256(JSON.stringify(sortCanonical(value)));
1171
+ }
1172
+
1173
+ function sortCanonical(value) {
1174
+ if (Array.isArray(value)) return value.map(sortCanonical);
1175
+ if (!value || typeof value !== "object") return value;
1176
+ return Object.fromEntries(
1177
+ Object.keys(value)
1178
+ .sort()
1179
+ .map((key) => [key, sortCanonical(value[key])]),
1180
+ );
1181
+ }