chainlesschain 0.163.8 → 0.165.1

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 (240) hide show
  1. package/package.json +7 -4
  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/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
  67. package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
  68. package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
  91. package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
  107. package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
  136. package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
  137. package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/commands/cowork.js +418 -41
  156. package/src/commands/permissions.js +240 -16
  157. package/src/commands/plugin.js +1293 -56
  158. package/src/commands/session-location.js +276 -0
  159. package/src/commands/session.js +2 -0
  160. package/src/data/changelog.json +28 -2
  161. package/src/gateways/ws/action-protocol.js +316 -17
  162. package/src/gateways/ws/message-dispatcher.js +30 -0
  163. package/src/gateways/ws/remote-session-protocol.js +565 -35
  164. package/src/gateways/ws/ws-agent-handler.js +12 -0
  165. package/src/gateways/ws/ws-approval-gate.js +5 -0
  166. package/src/gateways/ws/ws-server.js +510 -50
  167. package/src/harness/jsonl-session-store.js +341 -24
  168. package/src/harness/mcp-client.js +8 -1
  169. package/src/harness/remote-session-audit.js +2 -0
  170. package/src/harness/remote-session-crypto.js +929 -21
  171. package/src/harness/remote-session-registry.js +945 -24
  172. package/src/harness/session-list-index.js +233 -14
  173. package/src/lib/agent-authority.js +6 -0
  174. package/src/lib/auto-mode-config.js +98 -3
  175. package/src/lib/background-agent-keeper-client.js +248 -0
  176. package/src/lib/background-agent-keeper-protocol.js +291 -0
  177. package/src/lib/background-agent-supervisor.js +1357 -85
  178. package/src/lib/background-interaction-resolver.js +12 -0
  179. package/src/lib/background-session-transport.js +23 -6
  180. package/src/lib/background-turn-bootstrap-protocol.js +190 -0
  181. package/src/lib/cowork-workflow.js +1188 -76
  182. package/src/lib/delivery-production-adapter.js +38 -2
  183. package/src/lib/dynamic-workflow-facade.js +1736 -0
  184. package/src/lib/execution-location-contract.js +551 -0
  185. package/src/lib/execution-location-runtime.js +130 -0
  186. package/src/lib/ide-context.js +94 -6
  187. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  188. package/src/lib/packer/pkg-config-generator.js +2 -0
  189. package/src/lib/permission-authority.js +77 -0
  190. package/src/lib/permission-side-effect-center.js +495 -0
  191. package/src/lib/plugin-runtime/install.js +514 -13
  192. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  193. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  194. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  195. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  196. package/src/lib/plugin-security.js +60 -3
  197. package/src/lib/process-execution-broker/index.js +1095 -57
  198. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  199. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  200. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  201. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  202. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  203. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  204. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  205. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  206. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  207. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  208. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  209. package/src/lib/public-ide-capability-manifest.js +3 -0
  210. package/src/lib/remote-approval-bridge.js +777 -34
  211. package/src/lib/remote-control.js +2 -0
  212. package/src/lib/remote-membership-authority.js +632 -0
  213. package/src/lib/remote-membership-coordinator.js +2973 -0
  214. package/src/lib/remote-membership-host-store.js +1799 -0
  215. package/src/lib/runtime-provenance-ledger.js +171 -46
  216. package/src/lib/scoped-permission-store.js +369 -0
  217. package/src/lib/session-anti-rollback-anchor.js +262 -5
  218. package/src/lib/shell-approval.js +169 -6
  219. package/src/lib/sub-agent-context.js +4 -0
  220. package/src/lib/with-file-lock.js +73 -38
  221. package/src/lib/workflow-definition-contract.js +149 -0
  222. package/src/repl/agent-repl.js +111 -36
  223. package/src/repl/chat-repl.js +2 -0
  224. package/src/repl/clipboard-image.js +735 -16
  225. package/src/repl/ide-status.js +1 -1
  226. package/src/repl/prompt-interactions.js +59 -6
  227. package/src/repl/remote-approval.js +29 -7
  228. package/src/runtime/agent-core.js +1182 -155
  229. package/src/runtime/headless-runner.js +52 -30
  230. package/src/runtime/headless-stream.js +51 -28
  231. package/src/workers/background-agent-keeper.js +689 -0
  232. package/src/workers/background-agent-worker.js +365 -13
  233. package/src/workers/background-turn-bootstrap.js +170 -0
  234. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
  235. package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
  236. package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
  237. package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
  238. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
  239. package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
  240. package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
@@ -28,6 +28,395 @@ import {
28
28
  getPluginSkills,
29
29
  } from "../harness/plugin-manager.js";
30
30
 
31
+ function collectRepeatableOption(value, previous = []) {
32
+ return [...previous, value];
33
+ }
34
+
35
+ function normalizeRegistryUrls(value) {
36
+ if (Array.isArray(value)) return value.filter(Boolean);
37
+ return value ? [value] : [];
38
+ }
39
+
40
+ async function loadOptionalPluginConfig() {
41
+ try {
42
+ const cm = await import("../lib/config-manager.js");
43
+ return cm.loadConfig();
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ async function fetchRegistryRemoteArtifacts(
50
+ registryUrl,
51
+ entry,
52
+ { token, allowInsecure = false } = {},
53
+ ) {
54
+ if (!registryUrl || !entry || typeof entry !== "object") return null;
55
+ const rawSignature =
56
+ entry.signature && typeof entry.signature === "object"
57
+ ? entry.signature
58
+ : null;
59
+ const signatureUrl = rawSignature?.url;
60
+ const publicKeyUrl =
61
+ rawSignature?.publicKeyUrl ?? rawSignature?.publicKey?.url;
62
+ const publicKeySha256 = rawSignature?.publicKeySha256;
63
+ const signature =
64
+ signatureUrl && publicKeyUrl && publicKeySha256
65
+ ? {
66
+ algorithm: rawSignature.algorithm,
67
+ url: signatureUrl,
68
+ sha256:
69
+ rawSignature.documentSha256 ??
70
+ rawSignature.sha256 ??
71
+ rawSignature.digest,
72
+ publicKeyUrl,
73
+ publicKeySha256,
74
+ publicKeyDocumentSha256:
75
+ rawSignature.publicKeyDocumentSha256 ??
76
+ rawSignature.publicKey?.documentSha256 ??
77
+ rawSignature.publicKey?.sha256,
78
+ }
79
+ : null;
80
+ const rawSbom =
81
+ entry.sbom && typeof entry.sbom === "object" ? entry.sbom : null;
82
+ const sbomDocumentSha256 = rawSbom?.documentSha256 ?? rawSbom?.documentDigest;
83
+ const sbom =
84
+ rawSbom?.url && sbomDocumentSha256
85
+ ? {
86
+ format: rawSbom.format,
87
+ url: rawSbom.url,
88
+ digest: sbomDocumentSha256,
89
+ }
90
+ : null;
91
+ if (!signature && !sbom) return null;
92
+ const config = await loadOptionalPluginConfig();
93
+ const { resolveRegistryToken } =
94
+ await import("../lib/plugin-runtime/remote-source.js");
95
+ const { fetchPluginMarketplaceRemoteArtifacts } =
96
+ await import("../lib/plugin-runtime/marketplace-remote-artifacts.js");
97
+ return fetchPluginMarketplaceRemoteArtifacts({
98
+ registryUrl,
99
+ token: resolveRegistryToken(registryUrl, { token, config }),
100
+ allowInsecure,
101
+ signature,
102
+ sbom,
103
+ });
104
+ }
105
+
106
+ function cleanupRegistryRemoteArtifacts(remoteArtifacts) {
107
+ if (!remoteArtifacts?.cleanup) return;
108
+ try {
109
+ remoteArtifacts.cleanup();
110
+ } catch {
111
+ logger.warn(
112
+ "Verified marketplace artifact temporary files could not be removed; cleanup can be retried safely.",
113
+ );
114
+ }
115
+ }
116
+
117
+ function registryManifestDigestConflict(registrySha256, optionSha256) {
118
+ if (!registrySha256 || !optionSha256) return false;
119
+ return (
120
+ String(registrySha256).trim().toLowerCase() !==
121
+ String(optionSha256).trim().toLowerCase()
122
+ );
123
+ }
124
+
125
+ async function discoverInstalledPluginVersions(cwd) {
126
+ const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
127
+ const installed = {};
128
+ try {
129
+ for (const row of discoverPlugins({ cwd, skipPolicy: true })) {
130
+ installed[row.name] = row.version;
131
+ }
132
+ } catch {
133
+ /* an empty inventory conservatively leaves declared dependencies missing */
134
+ }
135
+ return installed;
136
+ }
137
+
138
+ async function buildRegistrySetSelection(
139
+ registryUrls,
140
+ name,
141
+ cwd,
142
+ { token, allowInsecure = false, strict = false } = {},
143
+ ) {
144
+ const { fetchRegistry, resolveRegistryToken } =
145
+ await import("../lib/plugin-runtime/remote-source.js");
146
+ const {
147
+ buildPluginMarketplaceCatalog,
148
+ buildPluginMarketplaceCandidateSelection,
149
+ buildPluginMarketplaceInstallPreflightFromSelection,
150
+ MAX_MARKETPLACE_CATALOG_SOURCES,
151
+ } = await import("../lib/plugin-runtime/marketplace-catalog.js");
152
+ if (registryUrls.length === 0) {
153
+ throw new Error("at least one registry source is required");
154
+ }
155
+ if (registryUrls.length > MAX_MARKETPLACE_CATALOG_SOURCES) {
156
+ throw new Error(
157
+ `at most ${MAX_MARKETPLACE_CATALOG_SOURCES} registry sources are allowed`,
158
+ );
159
+ }
160
+ const config = await loadOptionalPluginConfig();
161
+ const sources = await Promise.all(
162
+ registryUrls.map(async (url) => {
163
+ try {
164
+ const resolvedToken = resolveRegistryToken(url, { token, config });
165
+ const resolved = await fetchRegistry(url, {
166
+ token: resolvedToken,
167
+ allowInsecure,
168
+ });
169
+ return { url, ...resolved };
170
+ } catch (error) {
171
+ return {
172
+ url,
173
+ error: {
174
+ code: "REGISTRY_FETCH_FAILED",
175
+ message: error.message,
176
+ },
177
+ };
178
+ }
179
+ }),
180
+ );
181
+ const { VERSION } = await import("../constants.js");
182
+ const catalog = buildPluginMarketplaceCatalog({
183
+ sources,
184
+ installed: await discoverInstalledPluginVersions(cwd),
185
+ hostVersion: VERSION,
186
+ strict,
187
+ });
188
+ const selection = buildPluginMarketplaceCandidateSelection({
189
+ catalog,
190
+ name,
191
+ });
192
+ if (!selection.selected) {
193
+ return { catalog, selection, preflight: null, resolved: null };
194
+ }
195
+ const preflight = buildPluginMarketplaceInstallPreflightFromSelection({
196
+ catalog,
197
+ selection,
198
+ }).preflight;
199
+ const sourceIndex = selection.selected.registry.priority;
200
+ const entryIndex = selection.selected.registry.entryIndex;
201
+ const source = sources[sourceIndex];
202
+ const entry = source?.registry?.plugins?.[entryIndex];
203
+ if (!entry || entry.name !== selection.selected.name) {
204
+ throw new Error(
205
+ "selected registry entry no longer matches catalog authority",
206
+ );
207
+ }
208
+ return {
209
+ catalog,
210
+ selection,
211
+ preflight,
212
+ resolved: {
213
+ registryUrl: registryUrls[sourceIndex],
214
+ source: entry.ref ? `${entry.source}#${entry.ref}` : entry.source,
215
+ ref: entry.ref || null,
216
+ sha256: entry.sha256 || null,
217
+ entry,
218
+ fromCache: source.fromCache === true,
219
+ },
220
+ };
221
+ }
222
+
223
+ async function buildRegistryInstallPreflight(url, resolved, cwd) {
224
+ const { VERSION } = await import("../constants.js");
225
+ const { buildPluginMarketplaceInstallPreflight } =
226
+ await import("../lib/plugin-runtime/marketplace-catalog.js");
227
+ const installed = await discoverInstalledPluginVersions(cwd);
228
+ return buildPluginMarketplaceInstallPreflight({
229
+ registryUrl: url,
230
+ entry: resolved.entry,
231
+ fromCache: resolved.fromCache === true,
232
+ installed,
233
+ hostVersion: VERSION,
234
+ }).preflight;
235
+ }
236
+
237
+ function catalogAuthorityFromPreflight(
238
+ preflight,
239
+ impact = null,
240
+ remoteArtifactEvidence = null,
241
+ ) {
242
+ return {
243
+ catalogDigest: preflight.catalogDigest,
244
+ candidateId: preflight.candidateId,
245
+ candidateDigest: preflight.candidateDigest,
246
+ governanceStatus: preflight.governance.status,
247
+ registryStatus: preflight.registry.status,
248
+ versionAuthority: preflight.versionAuthority,
249
+ artifactExpectations: {
250
+ manifest: {
251
+ status: preflight.integrity.digest.status,
252
+ sha256: preflight.integrity.digest.value,
253
+ },
254
+ signature: {
255
+ status: preflight.integrity.signature.status,
256
+ algorithm: preflight.integrity.signature.algorithm,
257
+ publicKeySha256: preflight.integrity.signature.publicKeySha256,
258
+ url: preflight.integrity.signature.url,
259
+ publicKeyUrl: preflight.integrity.signature.publicKeyUrl,
260
+ documentSha256: preflight.integrity.signature.documentSha256,
261
+ publicKeyDocumentSha256:
262
+ preflight.integrity.signature.publicKeyDocumentSha256,
263
+ },
264
+ sbom: {
265
+ status: preflight.integrity.sbom.status,
266
+ format: preflight.integrity.sbom.format,
267
+ sha256: preflight.integrity.sbom.payloadSha256,
268
+ payloadSha256: preflight.integrity.sbom.payloadSha256,
269
+ documentSha256: preflight.integrity.sbom.documentSha256,
270
+ url: preflight.integrity.sbom.url,
271
+ },
272
+ license: {
273
+ status: preflight.license.status,
274
+ expression: preflight.license.expression,
275
+ },
276
+ },
277
+ ...(preflight.selectionDigest
278
+ ? {
279
+ selectionDigest: preflight.selectionDigest,
280
+ selectionSourceCount: preflight.selectionSourceCount,
281
+ }
282
+ : {}),
283
+ ...(impact ? { updateImpactDigest: impact.impactDigest } : {}),
284
+ ...(remoteArtifactEvidence ? { remoteArtifactEvidence } : {}),
285
+ };
286
+ }
287
+
288
+ function marketplacePreflightBlockerMessage(preflight) {
289
+ return preflight.blockers
290
+ .map((blocker) =>
291
+ blocker.detail ? `${blocker.code} (${blocker.detail})` : blocker.code,
292
+ )
293
+ .join(", ");
294
+ }
295
+
296
+ function assertExpectedSelectionDigest(expectedDigest, preflight) {
297
+ if (!expectedDigest) return;
298
+ if (!/^[a-f0-9]{64}$/.test(expectedDigest)) {
299
+ throw new Error(
300
+ "registry candidate preflight blocked: INVALID_EXPECTED_SELECTION_DIGEST",
301
+ );
302
+ }
303
+ if (!preflight?.selectionDigest) {
304
+ throw new Error(
305
+ "registry candidate preflight blocked: EXPECTED_SELECTION_DIGEST_REQUIRES_REGISTRY_SET",
306
+ );
307
+ }
308
+ if (expectedDigest !== preflight.selectionDigest) {
309
+ throw new Error(
310
+ `registry candidate preflight blocked: MARKETPLACE_SELECTION_DIGEST_MISMATCH (expected ${expectedDigest}, actual ${preflight.selectionDigest})`,
311
+ );
312
+ }
313
+ }
314
+
315
+ async function installedCatalogAuthorityMatches(
316
+ name,
317
+ scope,
318
+ cwd,
319
+ preflight,
320
+ impact = null,
321
+ remoteArtifactEvidence = null,
322
+ ) {
323
+ if (!preflight) return null;
324
+ try {
325
+ const { listInstalled } = await import("../lib/plugin-runtime/install.js");
326
+ const installed = listInstalled({ cwd, scopes: [scope] }).find(
327
+ (row) => row.name === name && row.scope === scope,
328
+ );
329
+ const authority = installed?.source?.catalogAuthority;
330
+ return (
331
+ authority?.catalogDigest === preflight.catalogDigest &&
332
+ authority?.candidateId === preflight.candidateId &&
333
+ authority?.candidateDigest === preflight.candidateDigest &&
334
+ (!preflight.selectionDigest ||
335
+ authority?.selectionDigest === preflight.selectionDigest) &&
336
+ (!impact || authority?.updateImpactDigest === impact.impactDigest) &&
337
+ (!remoteArtifactEvidence ||
338
+ authority?.remoteArtifactEvidence?.evidenceDigest ===
339
+ remoteArtifactEvidence.evidenceDigest) &&
340
+ authority?.preflightStatus === "allowed"
341
+ );
342
+ } catch {
343
+ return false;
344
+ }
345
+ }
346
+
347
+ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
348
+ const { listInstalled } = await import("../lib/plugin-runtime/install.js");
349
+ const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
350
+ const row = listInstalled({ cwd, scopes: [scope] }).find(
351
+ (candidate) => candidate.name === name && candidate.scope === scope,
352
+ );
353
+ if (!row) return null;
354
+ const discovered = discoverPlugins({
355
+ cwd,
356
+ scopes: [scope],
357
+ skipPolicy: true,
358
+ includeDisabled: true,
359
+ }).find((candidate) => candidate.name === name && candidate.scope === scope);
360
+ let dependencies = {};
361
+ let payloadSbom = null;
362
+ if (discovered?.manifest?.manifestPath) {
363
+ try {
364
+ const fs = await import("node:fs");
365
+ const raw = JSON.parse(
366
+ fs.readFileSync(discovered.manifest.manifestPath, "utf8"),
367
+ );
368
+ if (
369
+ raw.dependencies &&
370
+ typeof raw.dependencies === "object" &&
371
+ !Array.isArray(raw.dependencies)
372
+ ) {
373
+ dependencies = Object.fromEntries(
374
+ Object.entries(raw.dependencies).filter(
375
+ ([dependency]) => dependency !== "host" && dependency !== "cc",
376
+ ),
377
+ );
378
+ }
379
+ } catch {
380
+ dependencies = {};
381
+ }
382
+ }
383
+ if (row.dir) {
384
+ try {
385
+ const { buildMarketplacePayloadSbom } =
386
+ await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
387
+ payloadSbom = buildMarketplacePayloadSbom(row.dir);
388
+ } catch {
389
+ payloadSbom = null;
390
+ }
391
+ }
392
+ return {
393
+ name: row.name,
394
+ version: row.version,
395
+ scope: row.scope,
396
+ source: row.source,
397
+ integrity: {
398
+ ...row.integrity,
399
+ sbom: {
400
+ ...row.integrity?.sbom,
401
+ payloadSha256: payloadSbom?.digest || null,
402
+ payloadSchema: payloadSbom?.schemaVersion || null,
403
+ },
404
+ },
405
+ license: {
406
+ expression: discovered?.manifest?.metadata?.license || null,
407
+ },
408
+ capabilities: discovered?.manifest?.capabilities || null,
409
+ dependencies,
410
+ };
411
+ }
412
+
413
+ async function buildRegistryUpdateImpact(preflight, name, scope, cwd) {
414
+ const { buildPluginMarketplaceUpdateImpact } =
415
+ await import("../lib/plugin-runtime/marketplace-impact.js");
416
+ const installed = await buildInstalledMarketplaceSnapshot(name, scope, cwd);
417
+ return buildPluginMarketplaceUpdateImpact({ preflight, installed });
418
+ }
419
+
31
420
  /**
32
421
  * After an install/upgrade, resolve the just-installed plugin's DECLARED
33
422
  * capabilities and its current consent status, so `cc plugin add`/`upgrade`
@@ -889,7 +1278,9 @@ export function registerPluginCommand(program) {
889
1278
  .option("--public-key <path>", "Public key for signature verification")
890
1279
  .option(
891
1280
  "--registry <url>",
892
- "Resolve <source> as a plugin NAME in this registry URL",
1281
+ "Registry URL used to resolve <source> as a plugin name (repeatable)",
1282
+ collectRepeatableOption,
1283
+ [],
893
1284
  )
894
1285
  .option(
895
1286
  "--name <plugin>",
@@ -900,13 +1291,17 @@ export function registerPluginCommand(program) {
900
1291
  "--allow-insecure-registry",
901
1292
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
902
1293
  )
1294
+ .option(
1295
+ "--expected-selection-digest <sha256>",
1296
+ "Require the exact digest from a prior multi-registry `cc plugin select` review",
1297
+ )
903
1298
  .option(
904
1299
  "--grant-capabilities",
905
1300
  "Grant the plugin's declared capabilities at install time (no separate consent step)",
906
1301
  )
907
1302
  .option("--json", "Output as JSON")
908
1303
  .action(async (source, options) => {
909
- const { installFromSource } =
1304
+ const { installFromSource, finalizePluginUpdate, rollbackPluginUpdate } =
910
1305
  await import("../lib/plugin-runtime/install.js");
911
1306
 
912
1307
  // Remote resolution: a registry/manifest URL (or --registry <url> with a
@@ -914,29 +1309,84 @@ export function registerPluginCommand(program) {
914
1309
  let installSource = source;
915
1310
  let integritySha = null;
916
1311
  let sourceMetadata = null;
1312
+ let expectedIdentity = null;
1313
+ let marketplacePreflight = null;
1314
+ let remoteArtifactRequest = null;
1315
+ let remoteArtifacts = null;
1316
+ const registryUrls = normalizeRegistryUrls(options.registry);
917
1317
  const { isRemoteSource, resolveRemoteSource } =
918
1318
  await import("../lib/plugin-runtime/remote-source.js");
919
- if (options.registry || isRemoteSource(source)) {
920
- const url = options.registry || source;
921
- // With --registry the positional arg is the plugin NAME; otherwise the
922
- // URL is positional and --name selects.
923
- const name = options.registry ? source : options.name;
924
- let config = null;
925
- try {
926
- ({ loadConfig: config } = await import("../lib/config-manager.js"));
927
- config = config();
928
- } catch {
929
- config = null; // config is optional — token can come from --token/env
930
- }
1319
+ if (registryUrls.length || isRemoteSource(source)) {
931
1320
  try {
932
- const resolved = await resolveRemoteSource(url, {
933
- name,
934
- token: options.token,
935
- config,
936
- allowInsecure: options.allowInsecureRegistry === true,
937
- });
1321
+ let resolved;
1322
+ let url;
1323
+ let name;
1324
+ if (registryUrls.length) {
1325
+ name = source;
1326
+ const selected = await buildRegistrySetSelection(
1327
+ registryUrls,
1328
+ name,
1329
+ process.cwd(),
1330
+ {
1331
+ token: options.token,
1332
+ allowInsecure: options.allowInsecureRegistry === true,
1333
+ },
1334
+ );
1335
+ resolved = selected.resolved;
1336
+ marketplacePreflight = selected.preflight;
1337
+ url = resolved?.registryUrl;
1338
+ if (!marketplacePreflight || !resolved) {
1339
+ throw new Error(
1340
+ `registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
1341
+ );
1342
+ }
1343
+ } else {
1344
+ url = source;
1345
+ name = options.name;
1346
+ const config = await loadOptionalPluginConfig();
1347
+ resolved = await resolveRemoteSource(url, {
1348
+ name,
1349
+ token: options.token,
1350
+ config,
1351
+ allowInsecure: options.allowInsecureRegistry === true,
1352
+ });
1353
+ marketplacePreflight = await buildRegistryInstallPreflight(
1354
+ url,
1355
+ resolved,
1356
+ process.cwd(),
1357
+ );
1358
+ }
938
1359
  installSource = resolved.source;
939
1360
  integritySha = resolved.sha256;
1361
+ if (registryManifestDigestConflict(integritySha, options.sha256)) {
1362
+ throw new Error(
1363
+ "registry artifact verification failed: --sha256 does not match the registry-declared manifest SHA-256",
1364
+ );
1365
+ }
1366
+ if (marketplacePreflight.status !== "allowed") {
1367
+ throw new Error(
1368
+ `registry candidate preflight blocked: ${marketplacePreflightBlockerMessage(marketplacePreflight)}`,
1369
+ );
1370
+ }
1371
+ assertExpectedSelectionDigest(
1372
+ options.expectedSelectionDigest,
1373
+ marketplacePreflight,
1374
+ );
1375
+ expectedIdentity = {
1376
+ name:
1377
+ typeof resolved.entry.name === "string"
1378
+ ? resolved.entry.name
1379
+ : null,
1380
+ version:
1381
+ marketplacePreflight.versionAuthority ===
1382
+ "registry-declared-unverified"
1383
+ ? marketplacePreflight.registryVersion
1384
+ : null,
1385
+ };
1386
+ remoteArtifactRequest = {
1387
+ registryUrl: url,
1388
+ entry: resolved.entry,
1389
+ };
940
1390
  sourceMetadata = {
941
1391
  type: "registry",
942
1392
  source: url,
@@ -949,6 +1399,8 @@ export function registerPluginCommand(program) {
949
1399
  resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
950
1400
  null,
951
1401
  offline: resolved.fromCache === true,
1402
+ catalogAuthority:
1403
+ catalogAuthorityFromPreflight(marketplacePreflight),
952
1404
  };
953
1405
  if (resolved.fromCache) {
954
1406
  logger.warn(
@@ -975,32 +1427,101 @@ export function registerPluginCommand(program) {
975
1427
  const requiresManagedSignature =
976
1428
  managed?.requireSignedPlugins === true ||
977
1429
  managed?.requireSignedPlugins === "require";
1430
+ try {
1431
+ if (remoteArtifactRequest) {
1432
+ remoteArtifacts = await fetchRegistryRemoteArtifacts(
1433
+ remoteArtifactRequest.registryUrl,
1434
+ remoteArtifactRequest.entry,
1435
+ {
1436
+ token: options.token,
1437
+ allowInsecure: options.allowInsecureRegistry === true,
1438
+ },
1439
+ );
1440
+ if (
1441
+ remoteArtifacts?.signatureFile &&
1442
+ (options.signature || options.publicKey)
1443
+ ) {
1444
+ throw new Error(
1445
+ "registry remote signature artifacts cannot be combined with --signature/--public-key",
1446
+ );
1447
+ }
1448
+ if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
1449
+ sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
1450
+ marketplacePreflight,
1451
+ null,
1452
+ remoteArtifacts.authority,
1453
+ );
1454
+ }
1455
+ }
1456
+ } catch (err) {
1457
+ cleanupRegistryRemoteArtifacts(remoteArtifacts);
1458
+ logger.error(`Registry artifact verification failed: ${err.message}`);
1459
+ process.exitCode = 1;
1460
+ return;
1461
+ }
978
1462
  const signature =
979
1463
  options.sha256 ||
980
1464
  options.signature ||
981
1465
  options.publicKey ||
1466
+ remoteArtifacts?.signatureFile ||
1467
+ remoteArtifacts?.publicKeyFile ||
982
1468
  integritySha ||
983
1469
  requiresManagedSignature
984
1470
  ? {
985
1471
  sha256: options.sha256 || integritySha,
986
- signatureFile: options.signature,
987
- publicKeyFile: options.publicKey,
1472
+ signatureFile:
1473
+ remoteArtifacts?.signatureFile || options.signature,
1474
+ publicKeyFile:
1475
+ remoteArtifacts?.publicKeyFile || options.publicKey,
1476
+ expectedSignatureSha256:
1477
+ remoteArtifacts?.authority?.signature?.signatureSha256 || null,
1478
+ expectedPublicKeyDocumentSha256:
1479
+ remoteArtifacts?.authority?.signature?.publicKey
1480
+ ?.documentSha256 || null,
1481
+ expectedPublicKeySha256:
1482
+ remoteArtifacts?.authority?.signature?.publicKey?.spkiSha256 ||
1483
+ null,
988
1484
  requireSignature:
989
- Boolean(options.signature) || requiresManagedSignature,
1485
+ Boolean(remoteArtifacts?.signatureFile || options.signature) ||
1486
+ requiresManagedSignature,
990
1487
  trustedKeySha256: managed?.trustedPluginKeySha256 || null,
991
1488
  requireTrustedKey: requiresManagedSignature,
992
1489
  }
993
1490
  : null;
1491
+ let res = null;
1492
+ let marketplaceTransactionFinalized = false;
1493
+ let marketplaceCleanupPending = false;
994
1494
  try {
995
- const res = installFromSource(installSource, {
1495
+ res = installFromSource(installSource, {
996
1496
  scope: options.scope,
997
1497
  cwd: process.cwd(),
998
1498
  force: options.force === true,
999
1499
  signature,
1000
1500
  sourceMetadata,
1501
+ expectedIdentity,
1001
1502
  managedPolicy: managed,
1002
- policySource: options.registry || source,
1503
+ policySource: sourceMetadata?.registry || source,
1504
+ transactional: Boolean(marketplacePreflight),
1003
1505
  });
1506
+ const marketplaceAuthorityPersisted = marketplacePreflight
1507
+ ? res.sourceMetadata?.catalogAuthority?.catalogDigest ===
1508
+ marketplacePreflight.catalogDigest &&
1509
+ res.sourceMetadata?.catalogAuthority?.candidateId ===
1510
+ marketplacePreflight.candidateId &&
1511
+ res.sourceMetadata?.catalogAuthority?.candidateDigest ===
1512
+ marketplacePreflight.candidateDigest &&
1513
+ (!marketplacePreflight.selectionDigest ||
1514
+ res.sourceMetadata?.catalogAuthority?.selectionDigest ===
1515
+ marketplacePreflight.selectionDigest) &&
1516
+ (!remoteArtifacts?.authority ||
1517
+ res.sourceMetadata?.catalogAuthority?.remoteArtifactEvidence
1518
+ ?.evidenceDigest === remoteArtifacts.authority.evidenceDigest)
1519
+ : null;
1520
+ if (marketplacePreflight && !marketplaceAuthorityPersisted) {
1521
+ throw new Error(
1522
+ "marketplace install rejected because catalog and remote artifact authority was not persisted",
1523
+ );
1524
+ }
1004
1525
  const capNotice = await resolvePluginCapabilityNotice(
1005
1526
  res.name,
1006
1527
  res.scope,
@@ -1017,21 +1538,31 @@ export function registerPluginCommand(program) {
1017
1538
  res.scope,
1018
1539
  process.cwd(),
1019
1540
  );
1541
+ if (marketplacePreflight) {
1542
+ const finalization = finalizePluginUpdate(res);
1543
+ if (!finalization.finalized) {
1544
+ throw new Error(
1545
+ "marketplace install transaction could not be finalized",
1546
+ );
1547
+ }
1548
+ marketplaceCleanupPending = finalization.cleanupPending === true;
1549
+ marketplaceTransactionFinalized = true;
1550
+ }
1020
1551
  console.log(
1021
1552
  JSON.stringify(
1022
- { ...res, capabilities: capNotice, capabilitiesGranted },
1553
+ {
1554
+ ...res,
1555
+ marketplacePreflight,
1556
+ marketplaceAuthorityPersisted,
1557
+ marketplaceCleanupPending,
1558
+ capabilities: capNotice,
1559
+ capabilitiesGranted,
1560
+ },
1023
1561
  null,
1024
1562
  2,
1025
1563
  ),
1026
1564
  );
1027
1565
  } else {
1028
- logger.success(
1029
- `Installed ${res.name} v${res.version} (${res.scope} scope)` +
1030
- (res.signatureVerified ? chalk.green(" ✔ signed") : ""),
1031
- );
1032
- logger.log(chalk.gray(` → ${res.dir}`));
1033
- for (const w of res.warnings || [])
1034
- logger.log(chalk.yellow(` ⚠ ${w}`));
1035
1566
  await applyCapabilityConsentGate(
1036
1567
  res.name,
1037
1568
  res.scope,
@@ -1042,10 +1573,56 @@ export function registerPluginCommand(program) {
1042
1573
  interactive: Boolean(process.stdin.isTTY),
1043
1574
  },
1044
1575
  );
1576
+ if (marketplacePreflight) {
1577
+ const finalization = finalizePluginUpdate(res);
1578
+ if (!finalization.finalized) {
1579
+ throw new Error(
1580
+ "marketplace install transaction could not be finalized",
1581
+ );
1582
+ }
1583
+ marketplaceCleanupPending = finalization.cleanupPending === true;
1584
+ marketplaceTransactionFinalized = true;
1585
+ }
1586
+ logger.success(
1587
+ `Installed ${res.name} v${res.version} (${res.scope} scope)` +
1588
+ (res.signatureVerified ? chalk.green(" ✔ signed") : ""),
1589
+ );
1590
+ logger.log(chalk.gray(` → ${res.dir}`));
1591
+ if (marketplacePreflight) {
1592
+ logger.log(
1593
+ chalk.gray(
1594
+ ` catalog: ${marketplacePreflight.catalogDigest} (${marketplacePreflight.governance.status}; authority persisted)`,
1595
+ ),
1596
+ );
1597
+ if (marketplacePreflight.governance.missing.length) {
1598
+ logger.log(
1599
+ chalk.yellow(
1600
+ ` ⚠ registry governance metadata missing: ${marketplacePreflight.governance.missing.join(", ")}`,
1601
+ ),
1602
+ );
1603
+ }
1604
+ }
1605
+ for (const w of res.warnings || [])
1606
+ logger.log(chalk.yellow(` ⚠ ${w}`));
1045
1607
  }
1046
1608
  } catch (err) {
1047
- logger.error(`Install failed: ${err.message}`);
1609
+ let recoveryNote = "";
1610
+ if (res && marketplacePreflight && !marketplaceTransactionFinalized) {
1611
+ try {
1612
+ const recovery = rollbackPluginUpdate(res);
1613
+ if (recovery.rolledBack) {
1614
+ recoveryNote = recovery.version
1615
+ ? `; restored v${recovery.version}`
1616
+ : "; removed the rejected install";
1617
+ }
1618
+ } catch (recoveryError) {
1619
+ recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
1620
+ }
1621
+ }
1622
+ logger.error(`Install failed: ${err.message}${recoveryNote}`);
1048
1623
  process.exitCode = 1;
1624
+ } finally {
1625
+ cleanupRegistryRemoteArtifacts(remoteArtifacts);
1049
1626
  }
1050
1627
  });
1051
1628
 
@@ -1123,6 +1700,362 @@ export function registerPluginCommand(program) {
1123
1700
  }
1124
1701
  });
1125
1702
 
1703
+ // plugin catalog — merge one or more registries into a bounded, versioned
1704
+ // governance projection. Registry assertions remain explicitly unverified;
1705
+ // signature/SBOM verification still happens only after bytes are fetched.
1706
+ plugin
1707
+ .command("catalog [query]")
1708
+ .description(
1709
+ "Review digest, signature, SBOM, license, capability, dependency, and health metadata across registries",
1710
+ )
1711
+ .option(
1712
+ "--registry <url>",
1713
+ "Registry URL to include (repeatable; earlier sources have higher priority)",
1714
+ collectRepeatableOption,
1715
+ [],
1716
+ )
1717
+ .option("--token <token>", "Bearer token for private registries")
1718
+ .option(
1719
+ "--allow-insecure-registry",
1720
+ "Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
1721
+ )
1722
+ .option(
1723
+ "--strict",
1724
+ "Block candidates missing digest, signature, SBOM, license, or capabilities",
1725
+ )
1726
+ .option("--json", "Output the versioned catalog projection as JSON")
1727
+ .action(async (query, options) => {
1728
+ const registryUrls = Array.isArray(options.registry)
1729
+ ? options.registry
1730
+ : [];
1731
+ if (registryUrls.length === 0) {
1732
+ logger.error("At least one --registry <url> is required");
1733
+ process.exitCode = 1;
1734
+ return;
1735
+ }
1736
+
1737
+ const { fetchRegistry, resolveRegistryToken } =
1738
+ await import("../lib/plugin-runtime/remote-source.js");
1739
+ const { buildPluginMarketplaceCatalog, MAX_MARKETPLACE_CATALOG_SOURCES } =
1740
+ await import("../lib/plugin-runtime/marketplace-catalog.js");
1741
+ if (registryUrls.length > MAX_MARKETPLACE_CATALOG_SOURCES) {
1742
+ logger.error(
1743
+ `At most ${MAX_MARKETPLACE_CATALOG_SOURCES} --registry sources are allowed`,
1744
+ );
1745
+ process.exitCode = 1;
1746
+ return;
1747
+ }
1748
+ const { discoverPlugins } =
1749
+ await import("../lib/plugin-runtime/scopes.js");
1750
+ const { VERSION } = await import("../constants.js");
1751
+ let config = null;
1752
+ try {
1753
+ const cm = await import("../lib/config-manager.js");
1754
+ config = cm.loadConfig();
1755
+ } catch {
1756
+ config = null;
1757
+ }
1758
+
1759
+ const sources = await Promise.all(
1760
+ registryUrls.map(async (url) => {
1761
+ try {
1762
+ const token = resolveRegistryToken(url, {
1763
+ token: options.token,
1764
+ config,
1765
+ });
1766
+ const resolved = await fetchRegistry(url, {
1767
+ token,
1768
+ allowInsecure: options.allowInsecureRegistry === true,
1769
+ });
1770
+ return { url, ...resolved };
1771
+ } catch (error) {
1772
+ return {
1773
+ url,
1774
+ error: {
1775
+ code: "REGISTRY_FETCH_FAILED",
1776
+ message: error.message,
1777
+ },
1778
+ };
1779
+ }
1780
+ }),
1781
+ );
1782
+
1783
+ const installed = {};
1784
+ try {
1785
+ for (const pluginRow of discoverPlugins({
1786
+ cwd: process.cwd(),
1787
+ skipPolicy: true,
1788
+ })) {
1789
+ installed[pluginRow.name] = pluginRow.version;
1790
+ }
1791
+ } catch {
1792
+ /* dependency projection stays conservative with an empty inventory */
1793
+ }
1794
+
1795
+ const catalog = buildPluginMarketplaceCatalog({
1796
+ sources,
1797
+ installed,
1798
+ hostVersion: VERSION,
1799
+ query,
1800
+ strict: options.strict === true,
1801
+ });
1802
+ if (options.json) {
1803
+ console.log(JSON.stringify(catalog, null, 2));
1804
+ } else {
1805
+ logger.log(
1806
+ chalk.bold(
1807
+ `Marketplace candidates (${catalog.summary.candidateCount}) — ${catalog.mode}`,
1808
+ ),
1809
+ );
1810
+ for (const source of catalog.sources) {
1811
+ const mark =
1812
+ source.status === "online"
1813
+ ? chalk.green("online")
1814
+ : source.status === "cached"
1815
+ ? chalk.yellow("cached")
1816
+ : chalk.red("unavailable");
1817
+ logger.log(
1818
+ ` source ${chalk.cyan(source.sourceId)} [${mark}] ${source.url}`,
1819
+ );
1820
+ if (source.error) {
1821
+ logger.log(
1822
+ chalk.red(` ${source.error.code}: ${source.error.message}`),
1823
+ );
1824
+ }
1825
+ }
1826
+ for (const candidate of catalog.candidates) {
1827
+ const allowed =
1828
+ candidate.installability.status === "allowed"
1829
+ ? chalk.green("allowed")
1830
+ : chalk.red("blocked");
1831
+ logger.log(
1832
+ `\n ${chalk.cyan(candidate.name)} v${candidate.version || "?"} [${allowed}]`,
1833
+ );
1834
+ logger.log(
1835
+ ` source: ${candidate.registry.sourceId} → ${candidate.package.source || "missing"}${candidate.package.ref ? `#${candidate.package.ref}` : ""}`,
1836
+ );
1837
+ logger.log(
1838
+ ` digest (${candidate.integrity.digest.subject}): ${candidate.integrity.digest.status}${candidate.integrity.digest.value ? ` sha256:${candidate.integrity.digest.value}` : ""}`,
1839
+ );
1840
+ logger.log(
1841
+ ` signature: ${candidate.integrity.signature.status} (${candidate.integrity.signature.verification})`,
1842
+ );
1843
+ logger.log(
1844
+ ` SBOM: ${candidate.integrity.sbom.status} (${candidate.integrity.sbom.verification})`,
1845
+ );
1846
+ logger.log(
1847
+ ` license: ${candidate.license.expression || candidate.license.status}`,
1848
+ );
1849
+ logger.log(
1850
+ ` capabilities: ${candidate.capabilities.declared ? candidate.capabilities.summary.join("; ") || "declared none" : "missing"}`,
1851
+ );
1852
+ logger.log(
1853
+ ` compatibility: ${candidate.compatibility.status}${candidate.compatibility.range ? ` (${candidate.compatibility.range})` : ""}; dependencies: ${candidate.dependencies.status}; health: ${candidate.health.status}`,
1854
+ );
1855
+ if (candidate.installability.blockers.length) {
1856
+ logger.log(
1857
+ chalk.red(
1858
+ ` blockers: ${candidate.installability.blockers.map((item) => item.code).join(", ")}`,
1859
+ ),
1860
+ );
1861
+ }
1862
+ if (candidate.governance.missing.length) {
1863
+ logger.log(
1864
+ chalk.yellow(
1865
+ ` missing governance metadata: ${candidate.governance.missing.join(", ")}`,
1866
+ ),
1867
+ );
1868
+ }
1869
+ }
1870
+ logger.log(
1871
+ chalk.gray(
1872
+ `\nCatalog ${catalog.catalogDigest}; registry metadata is unverified until install/load verification.`,
1873
+ ),
1874
+ );
1875
+ }
1876
+
1877
+ if (catalog.summary.availableSourceCount === 0) {
1878
+ process.exitCode = 1;
1879
+ } else if (
1880
+ options.strict &&
1881
+ (catalog.summary.unavailableSourceCount > 0 ||
1882
+ catalog.summary.blockedCandidateCount > 0 ||
1883
+ catalog.summary.incompleteCandidateCount > 0)
1884
+ ) {
1885
+ process.exitCode = 2;
1886
+ }
1887
+ });
1888
+
1889
+ // plugin select — deterministically select one exact plugin across a
1890
+ // requested registry set and expose the stable pre-clone authority digest.
1891
+ plugin
1892
+ .command("select <name>")
1893
+ .description(
1894
+ "Select the highest-ranked install candidate across an exact registry set",
1895
+ )
1896
+ .option(
1897
+ "--registry <url>",
1898
+ "Registry URL to include (repeatable; earlier sources win version ties)",
1899
+ collectRepeatableOption,
1900
+ [],
1901
+ )
1902
+ .option("--token <token>", "Bearer token for private registries")
1903
+ .option(
1904
+ "--allow-insecure-registry",
1905
+ "Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
1906
+ )
1907
+ .option(
1908
+ "--strict",
1909
+ "Block candidates missing digest, signature, SBOM, license, or capabilities",
1910
+ )
1911
+ .option("--json", "Output the versioned candidate selection as JSON")
1912
+ .action(async (name, options) => {
1913
+ try {
1914
+ const result = await buildRegistrySetSelection(
1915
+ normalizeRegistryUrls(options.registry),
1916
+ name,
1917
+ process.cwd(),
1918
+ {
1919
+ token: options.token,
1920
+ allowInsecure: options.allowInsecureRegistry === true,
1921
+ strict: options.strict === true,
1922
+ },
1923
+ );
1924
+ const { selection } = result;
1925
+ if (options.json) {
1926
+ console.log(JSON.stringify(selection, null, 2));
1927
+ } else {
1928
+ logger.log(
1929
+ chalk.bold(
1930
+ `${name}: ${selection.status} across ${selection.sourceCount} registries`,
1931
+ ),
1932
+ );
1933
+ if (selection.selected) {
1934
+ logger.log(
1935
+ ` selected: v${selection.selected.version || "manifest-deferred"} from ${selection.selected.registry.sourceId}`,
1936
+ );
1937
+ logger.log(
1938
+ ` package: ${selection.selected.package.source || "missing"}${selection.selected.package.ref ? `#${selection.selected.package.ref}` : ""}`,
1939
+ );
1940
+ }
1941
+ if (selection.blockers.length) {
1942
+ logger.log(
1943
+ chalk.red(
1944
+ ` blockers: ${selection.blockers.map((blocker) => blocker.code).join(", ")}`,
1945
+ ),
1946
+ );
1947
+ }
1948
+ logger.log(
1949
+ chalk.gray(` selection digest: ${selection.selectionDigest}`),
1950
+ );
1951
+ logger.log(
1952
+ chalk.gray(
1953
+ " Registry metadata is unverified; no candidate bytes were fetched or executed.",
1954
+ ),
1955
+ );
1956
+ }
1957
+ if (selection.status === "blocked") process.exitCode = 2;
1958
+ } catch (error) {
1959
+ logger.error(`Marketplace selection failed: ${error.message}`);
1960
+ process.exitCode = 1;
1961
+ }
1962
+ });
1963
+
1964
+ // plugin impact — compare one registry candidate with the active immutable
1965
+ // install before upgrade. This never clones or executes candidate bytes.
1966
+ plugin
1967
+ .command("impact <name>")
1968
+ .description(
1969
+ "Preview version, source, integrity, license, capability, and dependency impact before a registry upgrade",
1970
+ )
1971
+ .option(
1972
+ "--registry <url>",
1973
+ "Registry URL containing the candidate (repeatable)",
1974
+ collectRepeatableOption,
1975
+ [],
1976
+ )
1977
+ .option("--scope <scope>", "Installed plugin scope", "user")
1978
+ .option("--token <token>", "Bearer token for a private registry")
1979
+ .option(
1980
+ "--allow-insecure-registry",
1981
+ "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1982
+ )
1983
+ .option("--json", "Output the versioned update-impact projection as JSON")
1984
+ .action(async (name, options) => {
1985
+ try {
1986
+ const registryUrls = normalizeRegistryUrls(options.registry);
1987
+ const selected = await buildRegistrySetSelection(
1988
+ registryUrls,
1989
+ name,
1990
+ process.cwd(),
1991
+ {
1992
+ token: options.token,
1993
+ allowInsecure: options.allowInsecureRegistry === true,
1994
+ },
1995
+ );
1996
+ if (!selected.preflight) {
1997
+ throw new Error(
1998
+ `registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
1999
+ );
2000
+ }
2001
+ const preflight = selected.preflight;
2002
+ const impact = await buildRegistryUpdateImpact(
2003
+ preflight,
2004
+ name,
2005
+ options.scope,
2006
+ process.cwd(),
2007
+ );
2008
+ if (options.json) {
2009
+ console.log(JSON.stringify(impact, null, 2));
2010
+ } else {
2011
+ logger.log(
2012
+ chalk.bold(
2013
+ `${name}: ${impact.status} (${impact.changeCount} material changes)`,
2014
+ ),
2015
+ );
2016
+ logger.log(
2017
+ ` version: ${impact.changes.version.from || "not installed"} → ${impact.changes.version.to || "manifest-deferred"} [${impact.changes.version.kind}]`,
2018
+ );
2019
+ logger.log(
2020
+ ` source: ${impact.changes.source.kind}${impact.changes.source.requiresApproval ? " (explicit approval required)" : ""}`,
2021
+ );
2022
+ logger.log(
2023
+ ` license: ${impact.changes.license.from || "unknown"} → ${impact.changes.license.to || "unknown"}`,
2024
+ );
2025
+ logger.log(
2026
+ ` capabilities: +${impact.changes.capabilities.added.join(", ") || "none"}; -${impact.changes.capabilities.removed.join(", ") || "none"}`,
2027
+ );
2028
+ logger.log(
2029
+ ` dependencies: +${impact.changes.dependencies.added.length} / -${impact.changes.dependencies.removed.length} / changed ${impact.changes.dependencies.changed.length}`,
2030
+ );
2031
+ if (impact.blockers.length) {
2032
+ logger.log(
2033
+ chalk.red(
2034
+ ` blockers: ${impact.blockers.map((blocker) => blocker.code).join(", ")}`,
2035
+ ),
2036
+ );
2037
+ }
2038
+ if (impact.requiredApprovals.length) {
2039
+ logger.log(
2040
+ chalk.yellow(
2041
+ ` approvals: ${impact.requiredApprovals.map((approval) => approval.code).join(", ")}`,
2042
+ ),
2043
+ );
2044
+ }
2045
+ logger.log(chalk.gray(` impact digest: ${impact.impactDigest}`));
2046
+ logger.log(
2047
+ chalk.gray(
2048
+ " Candidate registry metadata is unverified; no candidate bytes were fetched or executed.",
2049
+ ),
2050
+ );
2051
+ }
2052
+ if (impact.status === "blocked") process.exitCode = 2;
2053
+ } catch (error) {
2054
+ logger.error(`Marketplace impact failed: ${error.message}`);
2055
+ process.exitCode = 1;
2056
+ }
2057
+ });
2058
+
1126
2059
  // plugin installed — list runtime-installed plugins across scopes
1127
2060
  plugin
1128
2061
  .command("installed")
@@ -1167,6 +2100,103 @@ export function registerPluginCommand(program) {
1167
2100
  }
1168
2101
  });
1169
2102
 
2103
+ // plugin evidence — compare persisted registry artifact expectations with
2104
+ // actual bytes in the active immutable install. Remote artifacts are not
2105
+ // re-fetched here. The signature record is bound to the locally re-verified
2106
+ // lock; the external SBOM digest remains an install-time record because its
2107
+ // document bytes are not retained.
2108
+ plugin
2109
+ .command("evidence <name>")
2110
+ .description(
2111
+ "Read back installed manifest, license, signature, and payload-SBOM evidence",
2112
+ )
2113
+ .option("--scope <scope>", "Installed plugin scope", "user")
2114
+ .option(
2115
+ "--strict",
2116
+ "Return a non-zero status when any artifact expectation is partial",
2117
+ )
2118
+ .option("--json", "Output the versioned artifact readback as JSON")
2119
+ .action(async (name, options) => {
2120
+ try {
2121
+ const { listInstalled } =
2122
+ await import("../lib/plugin-runtime/install.js");
2123
+ const installed = listInstalled({
2124
+ cwd: process.cwd(),
2125
+ scopes: [options.scope],
2126
+ }).find(
2127
+ (candidate) =>
2128
+ candidate.name === name && candidate.scope === options.scope,
2129
+ );
2130
+ if (!installed) {
2131
+ throw new Error(`${name} is not installed at ${options.scope} scope`);
2132
+ }
2133
+ const { buildPluginMarketplaceArtifactReadback } =
2134
+ await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
2135
+ const evidence = buildPluginMarketplaceArtifactReadback({
2136
+ root: installed.dir,
2137
+ scope: installed.scope,
2138
+ source: installed.source,
2139
+ });
2140
+ if (options.json) {
2141
+ console.log(JSON.stringify(evidence, null, 2));
2142
+ } else {
2143
+ logger.log(
2144
+ chalk.bold(
2145
+ `${name}: marketplace artifact evidence ${evidence.status}`,
2146
+ ),
2147
+ );
2148
+ for (const [kind, comparison] of Object.entries(
2149
+ evidence.comparisons,
2150
+ )) {
2151
+ logger.log(
2152
+ ` ${kind}: ${comparison.status}${comparison.comparable ? " (exact comparison)" : ""}`,
2153
+ );
2154
+ }
2155
+ logger.log(
2156
+ ` signature: ${evidence.actual.signature.verified ? "cryptographically verified" : evidence.actual.signature.reason}`,
2157
+ );
2158
+ logger.log(
2159
+ ` payload SBOM: ${evidence.actual.payloadSbom.digest} (${evidence.actual.payloadSbom.fileCount} files)`,
2160
+ );
2161
+ logger.log(
2162
+ chalk.gray(` evidence digest: ${evidence.evidenceDigest}`),
2163
+ );
2164
+ const remoteKinds = [
2165
+ evidence.claims.remoteSignatureFetched ? "signature" : null,
2166
+ evidence.claims.remoteSbomFetched ? "SBOM" : null,
2167
+ ].filter(Boolean);
2168
+ const remoteDetail = [
2169
+ evidence.claims.remoteSignatureFetched
2170
+ ? `signature bytes/key ${
2171
+ evidence.claims.remoteSignatureBoundToInstalledLock
2172
+ ? "are bound to the current installed lock"
2173
+ : "are not fully bound to the current installed lock"
2174
+ }`
2175
+ : null,
2176
+ evidence.claims.remoteSbomFetched
2177
+ ? "the SBOM digest is an install-time record and was not re-hashed here"
2178
+ : null,
2179
+ ].filter(Boolean);
2180
+ logger.log(
2181
+ chalk.gray(
2182
+ remoteKinds.length
2183
+ ? ` Install-time remote ${remoteKinds.join("/")} evidence is present: ${remoteDetail.join("; ")}. Remote documents were not re-fetched; publisher identity remains unverified.`
2184
+ : " No valid install-time remote signature/SBOM evidence is available; publisher identity remains unverified.",
2185
+ ),
2186
+ );
2187
+ }
2188
+ if (
2189
+ evidence.status === "failed" ||
2190
+ (options.strict && evidence.status !== "matched")
2191
+ ) {
2192
+ process.exitCode = 2;
2193
+ }
2194
+ } catch (error) {
2195
+ logger.error(`Marketplace artifact evidence failed: ${error.message}`);
2196
+ process.exitCode = 1;
2197
+ }
2198
+ });
2199
+
1170
2200
  // plugin trust <name> — allow a plugin's code-bearing components to run
1171
2201
  plugin
1172
2202
  .command("trust <name>")
@@ -1574,7 +2604,9 @@ export function registerPluginCommand(program) {
1574
2604
  .option("--public-key <path>", "Public key for signature verification")
1575
2605
  .option(
1576
2606
  "--registry <url>",
1577
- "Resolve <source> as a plugin NAME in this registry URL",
2607
+ "Registry URL used to resolve <source> as a plugin name (repeatable)",
2608
+ collectRepeatableOption,
2609
+ [],
1578
2610
  )
1579
2611
  .option(
1580
2612
  "--name <plugin>",
@@ -1585,6 +2617,19 @@ export function registerPluginCommand(program) {
1585
2617
  "--allow-insecure-registry",
1586
2618
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1587
2619
  )
2620
+ .option(
2621
+ "--allow-source-switch",
2622
+ "Explicitly approve changing registry/source authority",
2623
+ )
2624
+ .option("--allow-downgrade", "Explicitly approve a version downgrade")
2625
+ .option(
2626
+ "--expected-impact-digest <sha256>",
2627
+ "Require the exact digest from a prior `cc plugin impact` review",
2628
+ )
2629
+ .option(
2630
+ "--expected-selection-digest <sha256>",
2631
+ "Require the exact digest from a prior multi-registry `cc plugin select` review",
2632
+ )
1588
2633
  .option(
1589
2634
  "--grant-capabilities",
1590
2635
  "Grant any newly declared capabilities during the upgrade (no separate consent step)",
@@ -1596,27 +2641,123 @@ export function registerPluginCommand(program) {
1596
2641
  let installSource = source;
1597
2642
  let integritySha = null;
1598
2643
  let sourceMetadata = null;
2644
+ let expectedIdentity = null;
2645
+ let marketplacePreflight = null;
2646
+ let marketplaceImpact = null;
2647
+ let remoteArtifactRequest = null;
2648
+ let remoteArtifacts = null;
2649
+ const registryUrls = normalizeRegistryUrls(options.registry);
1599
2650
  const { isRemoteSource, resolveRemoteSource } =
1600
2651
  await import("../lib/plugin-runtime/remote-source.js");
1601
- if (options.registry || isRemoteSource(source)) {
1602
- const url = options.registry || source;
1603
- const name = options.registry ? source : options.name;
1604
- let config = null;
1605
- try {
1606
- const cm = await import("../lib/config-manager.js");
1607
- config = cm.loadConfig();
1608
- } catch {
1609
- config = null;
1610
- }
2652
+ if (registryUrls.length || isRemoteSource(source)) {
1611
2653
  try {
1612
- const resolved = await resolveRemoteSource(url, {
1613
- name,
1614
- token: options.token,
1615
- config,
1616
- allowInsecure: options.allowInsecureRegistry === true,
1617
- });
2654
+ let resolved;
2655
+ let url;
2656
+ let name;
2657
+ if (registryUrls.length) {
2658
+ name = source;
2659
+ const selected = await buildRegistrySetSelection(
2660
+ registryUrls,
2661
+ name,
2662
+ process.cwd(),
2663
+ {
2664
+ token: options.token,
2665
+ allowInsecure: options.allowInsecureRegistry === true,
2666
+ },
2667
+ );
2668
+ resolved = selected.resolved;
2669
+ marketplacePreflight = selected.preflight;
2670
+ url = resolved?.registryUrl;
2671
+ if (!marketplacePreflight || !resolved) {
2672
+ throw new Error(
2673
+ `registry candidate preflight blocked: ${selected.selection.blockers.map((blocker) => blocker.code).join(", ") || "PLUGIN_NOT_FOUND"}`,
2674
+ );
2675
+ }
2676
+ } else {
2677
+ url = source;
2678
+ name = options.name;
2679
+ const config = await loadOptionalPluginConfig();
2680
+ resolved = await resolveRemoteSource(url, {
2681
+ name,
2682
+ token: options.token,
2683
+ config,
2684
+ allowInsecure: options.allowInsecureRegistry === true,
2685
+ });
2686
+ marketplacePreflight = await buildRegistryInstallPreflight(
2687
+ url,
2688
+ resolved,
2689
+ process.cwd(),
2690
+ );
2691
+ }
1618
2692
  installSource = resolved.source;
1619
2693
  integritySha = resolved.sha256;
2694
+ if (registryManifestDigestConflict(integritySha, options.sha256)) {
2695
+ throw new Error(
2696
+ "registry artifact verification failed: --sha256 does not match the registry-declared manifest SHA-256",
2697
+ );
2698
+ }
2699
+ if (marketplacePreflight.status !== "allowed") {
2700
+ throw new Error(
2701
+ `registry candidate preflight blocked: ${marketplacePreflightBlockerMessage(marketplacePreflight)}`,
2702
+ );
2703
+ }
2704
+ assertExpectedSelectionDigest(
2705
+ options.expectedSelectionDigest,
2706
+ marketplacePreflight,
2707
+ );
2708
+ marketplaceImpact = await buildRegistryUpdateImpact(
2709
+ marketplacePreflight,
2710
+ resolved.entry.name,
2711
+ options.scope,
2712
+ process.cwd(),
2713
+ );
2714
+ if (
2715
+ marketplaceImpact.changes.source.requiresApproval &&
2716
+ options.allowSourceSwitch !== true
2717
+ ) {
2718
+ throw new Error(
2719
+ `registry candidate preflight blocked: SOURCE_SWITCH_APPROVAL_REQUIRED (${marketplaceImpact.changes.source.kind}); pass --allow-source-switch after reviewing cc plugin impact`,
2720
+ );
2721
+ }
2722
+ if (
2723
+ marketplaceImpact.changes.version.kind === "downgrade" &&
2724
+ options.allowDowngrade !== true
2725
+ ) {
2726
+ throw new Error(
2727
+ "registry candidate preflight blocked: VERSION_DOWNGRADE_APPROVAL_REQUIRED; pass --allow-downgrade after reviewing cc plugin impact",
2728
+ );
2729
+ }
2730
+ if (
2731
+ options.expectedImpactDigest &&
2732
+ !/^[a-f0-9]{64}$/.test(options.expectedImpactDigest)
2733
+ ) {
2734
+ throw new Error(
2735
+ "registry candidate preflight blocked: INVALID_EXPECTED_IMPACT_DIGEST",
2736
+ );
2737
+ }
2738
+ if (
2739
+ options.expectedImpactDigest &&
2740
+ options.expectedImpactDigest !== marketplaceImpact.impactDigest
2741
+ ) {
2742
+ throw new Error(
2743
+ `registry candidate preflight blocked: UPDATE_IMPACT_DIGEST_MISMATCH (expected ${options.expectedImpactDigest}, actual ${marketplaceImpact.impactDigest})`,
2744
+ );
2745
+ }
2746
+ expectedIdentity = {
2747
+ name:
2748
+ typeof resolved.entry.name === "string"
2749
+ ? resolved.entry.name
2750
+ : null,
2751
+ version:
2752
+ marketplacePreflight.versionAuthority ===
2753
+ "registry-declared-unverified"
2754
+ ? marketplacePreflight.registryVersion
2755
+ : null,
2756
+ };
2757
+ remoteArtifactRequest = {
2758
+ registryUrl: url,
2759
+ entry: resolved.entry,
2760
+ };
1620
2761
  sourceMetadata = {
1621
2762
  type: "registry",
1622
2763
  source: url,
@@ -1629,6 +2770,10 @@ export function registerPluginCommand(program) {
1629
2770
  resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
1630
2771
  null,
1631
2772
  offline: resolved.fromCache === true,
2773
+ catalogAuthority: catalogAuthorityFromPreflight(
2774
+ marketplacePreflight,
2775
+ marketplaceImpact,
2776
+ ),
1632
2777
  };
1633
2778
  if (resolved.fromCache && !options.json) {
1634
2779
  logger.warn(
@@ -1655,34 +2800,94 @@ export function registerPluginCommand(program) {
1655
2800
  const requiresManagedSignature =
1656
2801
  managed?.requireSignedPlugins === true ||
1657
2802
  managed?.requireSignedPlugins === "require";
2803
+ try {
2804
+ if (remoteArtifactRequest) {
2805
+ remoteArtifacts = await fetchRegistryRemoteArtifacts(
2806
+ remoteArtifactRequest.registryUrl,
2807
+ remoteArtifactRequest.entry,
2808
+ {
2809
+ token: options.token,
2810
+ allowInsecure: options.allowInsecureRegistry === true,
2811
+ },
2812
+ );
2813
+ if (
2814
+ remoteArtifacts?.signatureFile &&
2815
+ (options.signature || options.publicKey)
2816
+ ) {
2817
+ throw new Error(
2818
+ "registry remote signature artifacts cannot be combined with --signature/--public-key",
2819
+ );
2820
+ }
2821
+ if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
2822
+ sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
2823
+ marketplacePreflight,
2824
+ marketplaceImpact,
2825
+ remoteArtifacts.authority,
2826
+ );
2827
+ }
2828
+ }
2829
+ } catch (err) {
2830
+ cleanupRegistryRemoteArtifacts(remoteArtifacts);
2831
+ logger.error(`Registry artifact verification failed: ${err.message}`);
2832
+ process.exitCode = 1;
2833
+ return;
2834
+ }
1658
2835
  const signature =
1659
2836
  options.sha256 ||
1660
2837
  options.signature ||
1661
2838
  options.publicKey ||
2839
+ remoteArtifacts?.signatureFile ||
2840
+ remoteArtifacts?.publicKeyFile ||
1662
2841
  integritySha ||
1663
2842
  requiresManagedSignature
1664
2843
  ? {
1665
2844
  sha256: options.sha256 || integritySha,
1666
- signatureFile: options.signature,
1667
- publicKeyFile: options.publicKey,
2845
+ signatureFile:
2846
+ remoteArtifacts?.signatureFile || options.signature,
2847
+ publicKeyFile:
2848
+ remoteArtifacts?.publicKeyFile || options.publicKey,
2849
+ expectedSignatureSha256:
2850
+ remoteArtifacts?.authority?.signature?.signatureSha256 || null,
2851
+ expectedPublicKeyDocumentSha256:
2852
+ remoteArtifacts?.authority?.signature?.publicKey
2853
+ ?.documentSha256 || null,
2854
+ expectedPublicKeySha256:
2855
+ remoteArtifacts?.authority?.signature?.publicKey?.spkiSha256 ||
2856
+ null,
1668
2857
  requireSignature:
1669
- Boolean(options.signature) || requiresManagedSignature,
2858
+ Boolean(remoteArtifacts?.signatureFile || options.signature) ||
2859
+ requiresManagedSignature,
1670
2860
  trustedKeySha256: managed?.trustedPluginKeySha256 || null,
1671
2861
  requireTrustedKey: requiresManagedSignature,
1672
2862
  }
1673
2863
  : null;
1674
2864
  let res = null;
2865
+ let marketplaceAuthorityPersisted = null;
1675
2866
  try {
1676
2867
  res = updatePlugin(installSource, {
1677
2868
  scope: options.scope,
1678
2869
  cwd: process.cwd(),
1679
- force: options.force,
2870
+ force: options.force === true || Boolean(remoteArtifacts?.authority),
1680
2871
  signature,
1681
2872
  sourceMetadata,
2873
+ expectedIdentity,
1682
2874
  managedPolicy: managed,
1683
- policySource: options.registry || source,
2875
+ policySource: sourceMetadata?.registry || source,
1684
2876
  transactional: true,
1685
2877
  });
2878
+ marketplaceAuthorityPersisted = await installedCatalogAuthorityMatches(
2879
+ res.name,
2880
+ options.scope,
2881
+ process.cwd(),
2882
+ marketplacePreflight,
2883
+ marketplaceImpact,
2884
+ remoteArtifacts?.authority || null,
2885
+ );
2886
+ if (marketplacePreflight && !marketplaceAuthorityPersisted) {
2887
+ throw new Error(
2888
+ "registry candidate matched existing immutable bytes without the exact catalog authority; retry with --force to verify and persist this source",
2889
+ );
2890
+ }
1686
2891
  const upgradeNotice = await resolvePluginCapabilityNotice(
1687
2892
  res.name,
1688
2893
  options.scope,
@@ -1739,6 +2944,15 @@ export function registerPluginCommand(program) {
1739
2944
  }
1740
2945
  activationStatus = "rolled_back";
1741
2946
  rollbackVersion = recovery.version;
2947
+ marketplaceAuthorityPersisted =
2948
+ await installedCatalogAuthorityMatches(
2949
+ res.name,
2950
+ options.scope,
2951
+ process.cwd(),
2952
+ marketplacePreflight,
2953
+ marketplaceImpact,
2954
+ remoteArtifacts?.authority || null,
2955
+ );
1742
2956
  } else {
1743
2957
  cleanupPending = finalizePluginUpdate(res).cleanupPending === true;
1744
2958
  }
@@ -1749,6 +2963,9 @@ export function registerPluginCommand(program) {
1749
2963
  {
1750
2964
  ...res,
1751
2965
  scope: options.scope,
2966
+ marketplacePreflight,
2967
+ marketplaceImpact,
2968
+ marketplaceAuthorityPersisted,
1752
2969
  capabilities: upgradeNotice,
1753
2970
  capabilitiesGranted,
1754
2971
  activationStatus,
@@ -1763,6 +2980,24 @@ export function registerPluginCommand(program) {
1763
2980
  return;
1764
2981
  }
1765
2982
 
2983
+ if (marketplacePreflight) {
2984
+ logger.log(
2985
+ chalk.gray(
2986
+ ` catalog: ${marketplacePreflight.catalogDigest} (${marketplacePreflight.governance.status}; ${marketplaceAuthorityPersisted ? "authority active" : "activation rolled back"})`,
2987
+ ),
2988
+ );
2989
+ if (marketplacePreflight.governance.missing.length) {
2990
+ logger.log(
2991
+ chalk.yellow(
2992
+ ` ⚠ registry governance metadata missing: ${marketplacePreflight.governance.missing.join(", ")}`,
2993
+ ),
2994
+ );
2995
+ }
2996
+ logger.log(
2997
+ chalk.gray(` reviewed impact: ${marketplaceImpact.impactDigest}`),
2998
+ );
2999
+ }
3000
+
1766
3001
  if (activationStatus === "rolled_back") {
1767
3002
  logger.warn(
1768
3003
  `Upgrade not activated: ${rollbackReason.replaceAll("_", " ")}; ` +
@@ -1799,6 +3034,8 @@ export function registerPluginCommand(program) {
1799
3034
  }
1800
3035
  logger.error(`Upgrade failed: ${err.message}${recoveryNote}`);
1801
3036
  process.exitCode = 1;
3037
+ } finally {
3038
+ cleanupRegistryRemoteArtifacts(remoteArtifacts);
1802
3039
  }
1803
3040
  });
1804
3041