chainlesschain 0.164.0 → 0.165.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +248 -2
  2. package/package.json +7 -4
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  109. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
  134. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/cowork.js +559 -62
  154. package/src/commands/plugin.js +819 -64
  155. package/src/commands/session-location.js +318 -7
  156. package/src/constants.js +2 -2
  157. package/src/data/changelog.json +28 -2
  158. package/src/gateways/ws/action-protocol.js +293 -19
  159. package/src/gateways/ws/message-dispatcher.js +30 -0
  160. package/src/gateways/ws/remote-session-protocol.js +542 -35
  161. package/src/gateways/ws/ws-approval-gate.js +5 -0
  162. package/src/gateways/ws/ws-server.js +462 -38
  163. package/src/harness/jsonl-session-store.js +638 -2
  164. package/src/harness/prompt-compressor.js +4 -0
  165. package/src/harness/remote-session-audit.js +1 -0
  166. package/src/harness/remote-session-crypto.js +750 -50
  167. package/src/harness/remote-session-registry.js +795 -27
  168. package/src/lib/auto-mode-config.js +98 -3
  169. package/src/lib/background-agent-keeper-client.js +29 -0
  170. package/src/lib/background-agent-keeper-protocol.js +158 -10
  171. package/src/lib/background-agent-supervisor.js +870 -48
  172. package/src/lib/background-session-transport.js +23 -6
  173. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  174. package/src/lib/cowork-task-runner.js +70 -0
  175. package/src/lib/cowork-workflow.js +887 -62
  176. package/src/lib/delivery-production-adapter.js +38 -2
  177. package/src/lib/doctor-checkup.js +34 -10
  178. package/src/lib/dynamic-workflow-draft.js +535 -0
  179. package/src/lib/dynamic-workflow-facade.js +1105 -17
  180. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  181. package/src/lib/execution-location-contract.js +111 -7
  182. package/src/lib/execution-location-runtime.js +3 -1
  183. package/src/lib/execution-location-target.js +910 -0
  184. package/src/lib/llm-providers.js +1 -0
  185. package/src/lib/mcp-call-ledger-store.js +35 -0
  186. package/src/lib/mcp-call-ledger.js +30 -0
  187. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  188. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  189. package/src/lib/plugin-runtime/install.js +4956 -657
  190. package/src/lib/plugin-runtime/manifest.js +3 -1
  191. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  192. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  193. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  194. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  195. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  196. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  197. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  198. package/src/lib/plugin-runtime/policy.js +21 -6
  199. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  200. package/src/lib/plugin-runtime/remote-source.js +323 -27
  201. package/src/lib/plugin-runtime/scopes.js +317 -41
  202. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  203. package/src/lib/public-ide-capability-manifest.js +1 -1
  204. package/src/lib/remote-approval-bridge.js +745 -108
  205. package/src/lib/remote-control.js +2 -0
  206. package/src/lib/remote-membership-coordinator.js +612 -46
  207. package/src/lib/remote-membership-host-store.js +59 -10
  208. package/src/lib/runtime-provenance-ledger.js +171 -46
  209. package/src/lib/shell-approval.js +169 -6
  210. package/src/lib/sub-agent-context.js +268 -2
  211. package/src/lib/task-model-selector.js +1 -1
  212. package/src/lib/with-file-lock.js +167 -42
  213. package/src/repl/agent-repl.js +70 -29
  214. package/src/repl/remote-approval.js +29 -7
  215. package/src/runtime/agent-core.js +1695 -194
  216. package/src/runtime/headless-runner.js +22 -5
  217. package/src/runtime/headless-stream.js +19 -3
  218. package/src/workers/background-agent-keeper.js +279 -58
  219. package/src/workers/background-agent-worker.js +7 -0
  220. package/src/workers/background-turn-bootstrap.js +29 -2
  221. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
  222. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  223. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
  224. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  225. package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  227. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  228. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import chalk from "chalk";
7
+ import fs from "node:fs";
7
8
  import { logger } from "../lib/logger.js";
8
9
  import {
9
10
  enforcePluginPolicy,
@@ -27,6 +28,11 @@ import {
27
28
  removePluginSkills,
28
29
  getPluginSkills,
29
30
  } from "../harness/plugin-manager.js";
31
+ import {
32
+ PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA,
33
+ PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA,
34
+ isMarketplacePayloadSbomFormat,
35
+ } from "../lib/plugin-runtime/marketplace-artifact-readback.js";
30
36
 
31
37
  function collectRepeatableOption(value, previous = []) {
32
38
  return [...previous, value];
@@ -37,6 +43,71 @@ function normalizeRegistryUrls(value) {
37
43
  return value ? [value] : [];
38
44
  }
39
45
 
46
+ function normalizeRegistryDigestPins(value, registryUrls) {
47
+ const pins = new Map();
48
+ const allowed = new Set(registryUrls);
49
+ for (const raw of Array.isArray(value) ? value : value ? [value] : []) {
50
+ const separator = String(raw).lastIndexOf("=");
51
+ const url = separator > 0 ? String(raw).slice(0, separator) : "";
52
+ const digest = separator > 0 ? String(raw).slice(separator + 1) : "";
53
+ if (!allowed.has(url)) {
54
+ throw new Error(
55
+ `registry digest pin must name an exact requested registry URL: ${url || raw}`,
56
+ );
57
+ }
58
+ if (!/^[a-f0-9]{64}$/.test(digest)) {
59
+ throw new Error(
60
+ `registry digest pin for ${url} must be 64 lowercase hexadecimal characters`,
61
+ );
62
+ }
63
+ if (pins.has(url) && pins.get(url) !== digest) {
64
+ throw new Error(`conflicting registry digest pins for ${url}`);
65
+ }
66
+ pins.set(url, digest);
67
+ }
68
+ return pins;
69
+ }
70
+
71
+ function registryNetworkOptions(options = {}) {
72
+ return {
73
+ proxyUrl:
74
+ options.proxy || process.env.CC_PLUGIN_REGISTRY_PROXY || undefined,
75
+ pacFile:
76
+ options.pacFile || process.env.CC_PLUGIN_REGISTRY_PAC_FILE || undefined,
77
+ caFile:
78
+ options.caFile || process.env.CC_PLUGIN_REGISTRY_CA_FILE || undefined,
79
+ };
80
+ }
81
+
82
+ function assertOfflinePluginSourceLocal(source, ref = null) {
83
+ if (
84
+ ref ||
85
+ typeof source !== "string" ||
86
+ !fs.existsSync(source) ||
87
+ !fs.lstatSync(source).isDirectory()
88
+ ) {
89
+ throw new Error(
90
+ "offline install without --registry requires an existing local plugin directory; remote Git caching is available only for registry candidates with manifest and semantic payload anchors",
91
+ );
92
+ }
93
+ }
94
+
95
+ function readBoundedPluginJson(file, label) {
96
+ const stat = fs.lstatSync(file);
97
+ if (
98
+ stat.isSymbolicLink() ||
99
+ !stat.isFile() ||
100
+ stat.nlink !== 1 ||
101
+ stat.size <= 0 ||
102
+ stat.size > 256 * 1024
103
+ ) {
104
+ throw new Error(
105
+ `${label} must be a single-link regular file under 256 KiB`,
106
+ );
107
+ }
108
+ return JSON.parse(fs.readFileSync(file, "utf8"));
109
+ }
110
+
40
111
  async function loadOptionalPluginConfig() {
41
112
  try {
42
113
  const cm = await import("../lib/config-manager.js");
@@ -49,7 +120,14 @@ async function loadOptionalPluginConfig() {
49
120
  async function fetchRegistryRemoteArtifacts(
50
121
  registryUrl,
51
122
  entry,
52
- { token, allowInsecure = false } = {},
123
+ {
124
+ token,
125
+ allowInsecure = false,
126
+ offline = false,
127
+ proxyUrl,
128
+ pacFile,
129
+ caFile,
130
+ } = {},
53
131
  ) {
54
132
  if (!registryUrl || !entry || typeof entry !== "object") return null;
55
133
  const rawSignature =
@@ -98,6 +176,10 @@ async function fetchRegistryRemoteArtifacts(
98
176
  registryUrl,
99
177
  token: resolveRegistryToken(registryUrl, { token, config }),
100
178
  allowInsecure,
179
+ offline,
180
+ proxyUrl,
181
+ pacFile,
182
+ caFile,
101
183
  signature,
102
184
  sbom,
103
185
  });
@@ -114,6 +196,32 @@ function cleanupRegistryRemoteArtifacts(remoteArtifacts) {
114
196
  }
115
197
  }
116
198
 
199
+ function remoteSbomComparisonAuthorityMatches(
200
+ catalogAuthority,
201
+ remoteArtifactEvidence,
202
+ expectedSbom = null,
203
+ ) {
204
+ const sbom = remoteArtifactEvidence?.sbom;
205
+ const semanticFormat = sbom?.format || expectedSbom?.format;
206
+ if (!isMarketplacePayloadSbomFormat(semanticFormat)) return true;
207
+ if (!sbom) {
208
+ const comparisonRequired =
209
+ semanticFormat === PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
210
+ (semanticFormat === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
211
+ Boolean(expectedSbom?.url && expectedSbom?.documentSha256));
212
+ return !comparisonRequired;
213
+ }
214
+ const comparison = catalogAuthority?.remoteSbomPayloadComparison;
215
+ return (
216
+ comparison?.status === "matched" &&
217
+ comparison.format === sbom.format &&
218
+ comparison.remoteArtifactEvidenceDigest ===
219
+ remoteArtifactEvidence.evidenceDigest &&
220
+ comparison.documentSha256 === sbom.documentSha256 &&
221
+ comparison.remotePayload?.digest === comparison.installedPayload?.digest
222
+ );
223
+ }
224
+
117
225
  function registryManifestDigestConflict(registrySha256, optionSha256) {
118
226
  if (!registrySha256 || !optionSha256) return false;
119
227
  return (
@@ -139,7 +247,16 @@ async function buildRegistrySetSelection(
139
247
  registryUrls,
140
248
  name,
141
249
  cwd,
142
- { token, allowInsecure = false, strict = false } = {},
250
+ {
251
+ token,
252
+ allowInsecure = false,
253
+ strict = false,
254
+ offline = false,
255
+ registryDigestPins = new Map(),
256
+ proxyUrl,
257
+ pacFile,
258
+ caFile,
259
+ } = {},
143
260
  ) {
144
261
  const { fetchRegistry, resolveRegistryToken } =
145
262
  await import("../lib/plugin-runtime/remote-source.js");
@@ -165,6 +282,11 @@ async function buildRegistrySetSelection(
165
282
  const resolved = await fetchRegistry(url, {
166
283
  token: resolvedToken,
167
284
  allowInsecure,
285
+ offline,
286
+ expectedSha256: registryDigestPins.get(url),
287
+ proxyUrl,
288
+ pacFile,
289
+ caFile,
168
290
  });
169
291
  return { url, ...resolved };
170
292
  } catch (error) {
@@ -216,6 +338,8 @@ async function buildRegistrySetSelection(
216
338
  sha256: entry.sha256 || null,
217
339
  entry,
218
340
  fromCache: source.fromCache === true,
341
+ documentSha256: source.documentSha256 || null,
342
+ networkAuthority: source.networkAuthority || null,
219
343
  },
220
344
  };
221
345
  }
@@ -229,6 +353,8 @@ async function buildRegistryInstallPreflight(url, resolved, cwd) {
229
353
  registryUrl: url,
230
354
  entry: resolved.entry,
231
355
  fromCache: resolved.fromCache === true,
356
+ registryDocumentSha256: resolved.documentSha256,
357
+ registryNetworkAuthority: resolved.networkAuthority,
232
358
  installed,
233
359
  hostVersion: VERSION,
234
360
  }).preflight;
@@ -245,7 +371,16 @@ function catalogAuthorityFromPreflight(
245
371
  candidateDigest: preflight.candidateDigest,
246
372
  governanceStatus: preflight.governance.status,
247
373
  registryStatus: preflight.registry.status,
374
+ ...(preflight.registry.documentSha256
375
+ ? { registryDocumentSha256: preflight.registry.documentSha256 }
376
+ : {}),
377
+ ...(preflight.registry.networkAuthority
378
+ ? { registryNetworkAuthority: preflight.registry.networkAuthority }
379
+ : {}),
248
380
  versionAuthority: preflight.versionAuthority,
381
+ ...(preflight.publisherIdentity
382
+ ? { publisherDeclaration: preflight.publisherIdentity }
383
+ : {}),
249
384
  artifactExpectations: {
250
385
  manifest: {
251
386
  status: preflight.integrity.digest.status,
@@ -323,9 +458,12 @@ async function installedCatalogAuthorityMatches(
323
458
  if (!preflight) return null;
324
459
  try {
325
460
  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
- );
461
+ const installed = listInstalled({
462
+ cwd,
463
+ scopes: [scope],
464
+ allowRetainedInstall: true,
465
+ }).find((row) => row.name === name && row.scope === scope);
466
+ if (installed?.runtimeBlocked === true) return false;
329
467
  const authority = installed?.source?.catalogAuthority;
330
468
  return (
331
469
  authority?.catalogDigest === preflight.catalogDigest &&
@@ -337,6 +475,11 @@ async function installedCatalogAuthorityMatches(
337
475
  (!remoteArtifactEvidence ||
338
476
  authority?.remoteArtifactEvidence?.evidenceDigest ===
339
477
  remoteArtifactEvidence.evidenceDigest) &&
478
+ remoteSbomComparisonAuthorityMatches(
479
+ authority,
480
+ remoteArtifactEvidence,
481
+ preflight.integrity?.sbom,
482
+ ) &&
340
483
  authority?.preflightStatus === "allowed"
341
484
  );
342
485
  } catch {
@@ -345,12 +488,42 @@ async function installedCatalogAuthorityMatches(
345
488
  }
346
489
 
347
490
  async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
348
- const { listInstalled } = await import("../lib/plugin-runtime/install.js");
491
+ const { listInstalled, readSourceMetadataStrict } =
492
+ await import("../lib/plugin-runtime/install.js");
349
493
  const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
350
494
  const row = listInstalled({ cwd, scopes: [scope] }).find(
351
495
  (candidate) => candidate.name === name && candidate.scope === scope,
352
496
  );
353
497
  if (!row) return null;
498
+ if (row.runtimeBlocked === true) {
499
+ throw new Error(
500
+ `INSTALLED_PLUGIN_RUNTIME_BLOCKED (${row.activePointer?.status || "unknown"})`,
501
+ );
502
+ }
503
+ const installedSource = readSourceMetadataStrict(row.dir, {
504
+ required: true,
505
+ requireRegistryAuthority: true,
506
+ });
507
+ const installedAuthority = installedSource.catalogAuthority;
508
+ const installedExpectedSbom =
509
+ installedAuthority?.artifactExpectations?.sbom || null;
510
+ const installedSemanticComparisonRequired =
511
+ installedExpectedSbom?.format ===
512
+ PLUGIN_MARKETPLACE_CANONICAL_PAYLOAD_SBOM_SCHEMA ||
513
+ (installedExpectedSbom?.format === PLUGIN_MARKETPLACE_PAYLOAD_SBOM_SCHEMA &&
514
+ Boolean(
515
+ installedExpectedSbom.url && installedExpectedSbom.documentSha256,
516
+ ));
517
+ if (
518
+ installedSemanticComparisonRequired &&
519
+ !remoteSbomComparisonAuthorityMatches(
520
+ installedAuthority,
521
+ installedAuthority?.remoteArtifactEvidence,
522
+ installedExpectedSbom,
523
+ )
524
+ ) {
525
+ throw new Error("INSTALLED_SEMANTIC_SBOM_EVIDENCE_INVALID");
526
+ }
354
527
  const discovered = discoverPlugins({
355
528
  cwd,
356
529
  scopes: [scope],
@@ -359,6 +532,7 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
359
532
  }).find((candidate) => candidate.name === name && candidate.scope === scope);
360
533
  let dependencies = {};
361
534
  let payloadSbom = null;
535
+ let semanticPayloadFormat = null;
362
536
  if (discovered?.manifest?.manifestPath) {
363
537
  try {
364
538
  const fs = await import("node:fs");
@@ -381,25 +555,54 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
381
555
  }
382
556
  }
383
557
  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;
558
+ const {
559
+ assertSafeInstalledPluginStructure,
560
+ buildMarketplacePayloadSbom,
561
+ isMarketplacePayloadSbomFormat,
562
+ validateRemoteSbomPayloadComparison,
563
+ } = await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
564
+ const recordedFormat =
565
+ installedSource?.catalogAuthority?.remoteSbomPayloadComparison?.format;
566
+ semanticPayloadFormat = isMarketplacePayloadSbomFormat(recordedFormat)
567
+ ? recordedFormat
568
+ : null;
569
+ if (semanticPayloadFormat) {
570
+ assertSafeInstalledPluginStructure(row.dir);
571
+ payloadSbom = buildMarketplacePayloadSbom(row.dir, {
572
+ schemaVersion: semanticPayloadFormat,
573
+ });
574
+ const comparison = validateRemoteSbomPayloadComparison(
575
+ installedAuthority?.remoteSbomPayloadComparison,
576
+ {
577
+ remoteArtifactEvidence: installedAuthority?.remoteArtifactEvidence,
578
+ expectedPayloadSha256: installedExpectedSbom?.payloadSha256,
579
+ currentPayloadSbom: payloadSbom,
580
+ },
581
+ );
582
+ if (!comparison.valid || !comparison.currentPayloadMatches) {
583
+ throw new Error("INSTALLED_SEMANTIC_SBOM_EVIDENCE_INVALID");
584
+ }
585
+ } else {
586
+ try {
587
+ payloadSbom = buildMarketplacePayloadSbom(row.dir);
588
+ } catch {
589
+ payloadSbom = null;
590
+ }
390
591
  }
391
592
  }
392
593
  return {
393
594
  name: row.name,
394
595
  version: row.version,
395
596
  scope: row.scope,
396
- source: row.source,
597
+ enabled: row.enabled !== false,
598
+ source: installedSource,
397
599
  integrity: {
398
600
  ...row.integrity,
399
601
  sbom: {
400
602
  ...row.integrity?.sbom,
401
603
  payloadSha256: payloadSbom?.digest || null,
402
604
  payloadSchema: payloadSbom?.schemaVersion || null,
605
+ semanticPayloadFormat,
403
606
  },
404
607
  },
405
608
  license: {
@@ -413,8 +616,29 @@ async function buildInstalledMarketplaceSnapshot(name, scope, cwd) {
413
616
  async function buildRegistryUpdateImpact(preflight, name, scope, cwd) {
414
617
  const { buildPluginMarketplaceUpdateImpact } =
415
618
  await import("../lib/plugin-runtime/marketplace-impact.js");
416
- const installed = await buildInstalledMarketplaceSnapshot(name, scope, cwd);
417
- return buildPluginMarketplaceUpdateImpact({ preflight, installed });
619
+ const { listInstalledAllScopes } =
620
+ await import("../lib/plugin-runtime/install.js");
621
+ const inventory = listInstalledAllScopes({ cwd }).filter(
622
+ (entry) => entry.name === name,
623
+ );
624
+ const installedScopes = [];
625
+ for (const entry of inventory) {
626
+ installedScopes.push(
627
+ await buildInstalledMarketplaceSnapshot(name, entry.scope, cwd),
628
+ );
629
+ }
630
+ const effectiveScope = inventory.find(
631
+ (entry) => entry.effectiveAuthority === true,
632
+ )?.scope;
633
+ const installed = effectiveScope
634
+ ? installedScopes.find((entry) => entry?.scope === effectiveScope) || null
635
+ : null;
636
+ return buildPluginMarketplaceUpdateImpact({
637
+ preflight,
638
+ installed,
639
+ installedScopes,
640
+ targetScope: scope,
641
+ });
418
642
  }
419
643
 
420
644
  /**
@@ -437,9 +661,11 @@ async function resolvePluginCapabilityNotice(
437
661
  const { describeCapabilities } =
438
662
  await import("../lib/plugin-runtime/capabilities.js");
439
663
  const consent = await import("../lib/plugin-runtime/capability-consent.js");
440
- const installed = discoverPlugins({ cwd, skipPolicy: true }).find(
441
- (p) => p.name === name && p.scope === scope,
442
- );
664
+ const installed = discoverPlugins({
665
+ cwd,
666
+ skipPolicy: true,
667
+ allowRetainedInstall: true,
668
+ }).find((p) => p.name === name && p.scope === scope);
443
669
  if (!installed) {
444
670
  if (strict) {
445
671
  throw new Error(`${name} is not discoverable at ${scope} scope`);
@@ -532,9 +758,11 @@ async function grantInstalledPluginCapabilities(name, scope, cwd) {
532
758
  try {
533
759
  const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
534
760
  const consent = await import("../lib/plugin-runtime/capability-consent.js");
535
- const installed = discoverPlugins({ cwd, skipPolicy: true }).find(
536
- (p) => p.name === name && p.scope === scope,
537
- );
761
+ const installed = discoverPlugins({
762
+ cwd,
763
+ skipPolicy: true,
764
+ allowRetainedInstall: true,
765
+ }).find((p) => p.name === name && p.scope === scope);
538
766
  if (!installed) return false;
539
767
  const declared = installed.manifest?.capabilities;
540
768
  if (!declared || consent.capabilitiesAreEmpty(declared)) return false;
@@ -810,6 +1038,10 @@ export function registerPluginCommand(program) {
810
1038
  "--scope <scope>",
811
1039
  "Enable the unified-runtime install at this scope (user|project|local)",
812
1040
  )
1041
+ .option(
1042
+ "--allow-source-switch",
1043
+ "Approve changing the effective source authority across scopes",
1044
+ )
813
1045
  .option("--json", "Output unified-runtime result as JSON")
814
1046
  .action(async (name, options) => {
815
1047
  try {
@@ -819,6 +1051,7 @@ export function registerPluginCommand(program) {
819
1051
  const result = setPluginEnabled(name, true, {
820
1052
  scope: options.scope,
821
1053
  cwd: process.cwd(),
1054
+ allowSourceSwitch: options.allowSourceSwitch === true,
822
1055
  });
823
1056
  if (options.json) console.log(JSON.stringify(result, null, 2));
824
1057
  else {
@@ -862,6 +1095,10 @@ export function registerPluginCommand(program) {
862
1095
  "--scope <scope>",
863
1096
  "Disable the unified-runtime install at this scope (user|project|local)",
864
1097
  )
1098
+ .option(
1099
+ "--allow-source-switch",
1100
+ "Approve the lower-scope source authority exposed by disabling",
1101
+ )
865
1102
  .option("--json", "Output unified-runtime result as JSON")
866
1103
  .action(async (name, options) => {
867
1104
  try {
@@ -871,6 +1108,7 @@ export function registerPluginCommand(program) {
871
1108
  const result = setPluginEnabled(name, false, {
872
1109
  scope: options.scope,
873
1110
  cwd: process.cwd(),
1111
+ allowSourceSwitch: options.allowSourceSwitch === true,
874
1112
  });
875
1113
  if (options.json) console.log(JSON.stringify(result, null, 2));
876
1114
  else {
@@ -1273,6 +1511,14 @@ export function registerPluginCommand(program) {
1273
1511
  )
1274
1512
  .option("--scope <scope>", "Install scope (user|project|local)", "user")
1275
1513
  .option("--force", "Reinstall over an existing immutable version")
1514
+ .option(
1515
+ "--allow-source-switch",
1516
+ "Explicitly approve changing effective source authority across scopes",
1517
+ )
1518
+ .option(
1519
+ "--allow-downgrade",
1520
+ "Explicitly approve a version downgrade across scoped installs",
1521
+ )
1276
1522
  .option("--sha256 <hex>", "Expected SHA-256 of the manifest file")
1277
1523
  .option("--signature <path>", "Detached Ed25519 signature of the manifest")
1278
1524
  .option("--public-key <path>", "Public key for signature verification")
@@ -1291,6 +1537,28 @@ export function registerPluginCommand(program) {
1291
1537
  "--allow-insecure-registry",
1292
1538
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1293
1539
  )
1540
+ .option(
1541
+ "--offline",
1542
+ "Use verified registry/artifact/source caches or an existing local plugin source",
1543
+ )
1544
+ .option(
1545
+ "--registry-digest <url=sha256>",
1546
+ "Pin an exact immutable registry document (repeatable)",
1547
+ collectRepeatableOption,
1548
+ [],
1549
+ )
1550
+ .option(
1551
+ "--proxy <url>",
1552
+ "Explicit HTTP(S) registry proxy (or CC_PLUGIN_REGISTRY_PROXY)",
1553
+ )
1554
+ .option(
1555
+ "--pac-file <path>",
1556
+ "Bounded local PAC file (or CC_PLUGIN_REGISTRY_PAC_FILE)",
1557
+ )
1558
+ .option(
1559
+ "--ca-file <path>",
1560
+ "Custom registry CA bundle (or CC_PLUGIN_REGISTRY_CA_FILE)",
1561
+ )
1294
1562
  .option(
1295
1563
  "--expected-selection-digest <sha256>",
1296
1564
  "Require the exact digest from a prior multi-registry `cc plugin select` review",
@@ -1311,13 +1579,34 @@ export function registerPluginCommand(program) {
1311
1579
  let sourceMetadata = null;
1312
1580
  let expectedIdentity = null;
1313
1581
  let marketplacePreflight = null;
1582
+ let marketplaceImpact = null;
1314
1583
  let remoteArtifactRequest = null;
1315
1584
  let remoteArtifacts = null;
1316
1585
  const registryUrls = normalizeRegistryUrls(options.registry);
1317
1586
  const { isRemoteSource, resolveRemoteSource } =
1318
1587
  await import("../lib/plugin-runtime/remote-source.js");
1588
+ if (
1589
+ options.offline === true &&
1590
+ registryUrls.length === 0 &&
1591
+ !isRemoteSource(source)
1592
+ ) {
1593
+ try {
1594
+ assertOfflinePluginSourceLocal(source);
1595
+ } catch (error) {
1596
+ logger.error(`Offline source validation failed: ${error.message}`);
1597
+ process.exitCode = 1;
1598
+ return;
1599
+ }
1600
+ }
1319
1601
  if (registryUrls.length || isRemoteSource(source)) {
1320
1602
  try {
1603
+ const requestedRegistryUrls = registryUrls.length
1604
+ ? registryUrls
1605
+ : [source];
1606
+ const registryDigestPins = normalizeRegistryDigestPins(
1607
+ options.registryDigest,
1608
+ requestedRegistryUrls,
1609
+ );
1321
1610
  let resolved;
1322
1611
  let url;
1323
1612
  let name;
@@ -1330,6 +1619,9 @@ export function registerPluginCommand(program) {
1330
1619
  {
1331
1620
  token: options.token,
1332
1621
  allowInsecure: options.allowInsecureRegistry === true,
1622
+ offline: options.offline === true,
1623
+ registryDigestPins,
1624
+ ...registryNetworkOptions(options),
1333
1625
  },
1334
1626
  );
1335
1627
  resolved = selected.resolved;
@@ -1349,6 +1641,9 @@ export function registerPluginCommand(program) {
1349
1641
  token: options.token,
1350
1642
  config,
1351
1643
  allowInsecure: options.allowInsecureRegistry === true,
1644
+ offline: options.offline === true,
1645
+ expectedSha256: registryDigestPins.get(url),
1646
+ ...registryNetworkOptions(options),
1352
1647
  });
1353
1648
  marketplacePreflight = await buildRegistryInstallPreflight(
1354
1649
  url,
@@ -1372,6 +1667,47 @@ export function registerPluginCommand(program) {
1372
1667
  options.expectedSelectionDigest,
1373
1668
  marketplacePreflight,
1374
1669
  );
1670
+ marketplaceImpact = await buildRegistryUpdateImpact(
1671
+ marketplacePreflight,
1672
+ resolved.entry.name,
1673
+ options.scope,
1674
+ process.cwd(),
1675
+ );
1676
+ if (
1677
+ marketplaceImpact.blockers.some(
1678
+ (blocker) => blocker.code === "SEMANTIC_SBOM_BINDING_DOWNGRADE",
1679
+ )
1680
+ ) {
1681
+ throw new Error(
1682
+ "registry candidate preflight blocked: SEMANTIC_SBOM_BINDING_DOWNGRADE",
1683
+ );
1684
+ }
1685
+ if (
1686
+ marketplaceImpact.blockers.some(
1687
+ (blocker) =>
1688
+ blocker.code === "REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
1689
+ )
1690
+ ) {
1691
+ throw new Error(
1692
+ "registry candidate preflight blocked: REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
1693
+ );
1694
+ }
1695
+ if (
1696
+ marketplaceImpact.changes.source.requiresApproval &&
1697
+ options.allowSourceSwitch !== true
1698
+ ) {
1699
+ throw new Error(
1700
+ `registry candidate preflight blocked: SOURCE_SWITCH_APPROVAL_REQUIRED (${marketplaceImpact.changes.source.kind}); pass --allow-source-switch after reviewing cc plugin impact`,
1701
+ );
1702
+ }
1703
+ if (
1704
+ marketplaceImpact.changes.version.kind === "downgrade" &&
1705
+ options.allowDowngrade !== true
1706
+ ) {
1707
+ throw new Error(
1708
+ "registry candidate preflight blocked: VERSION_DOWNGRADE_APPROVAL_REQUIRED; pass --allow-downgrade after reviewing cc plugin impact",
1709
+ );
1710
+ }
1375
1711
  expectedIdentity = {
1376
1712
  name:
1377
1713
  typeof resolved.entry.name === "string"
@@ -1399,8 +1735,10 @@ export function registerPluginCommand(program) {
1399
1735
  resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
1400
1736
  null,
1401
1737
  offline: resolved.fromCache === true,
1402
- catalogAuthority:
1403
- catalogAuthorityFromPreflight(marketplacePreflight),
1738
+ catalogAuthority: catalogAuthorityFromPreflight(
1739
+ marketplacePreflight,
1740
+ marketplaceImpact,
1741
+ ),
1404
1742
  };
1405
1743
  if (resolved.fromCache) {
1406
1744
  logger.warn(
@@ -1435,6 +1773,8 @@ export function registerPluginCommand(program) {
1435
1773
  {
1436
1774
  token: options.token,
1437
1775
  allowInsecure: options.allowInsecureRegistry === true,
1776
+ offline: options.offline === true,
1777
+ ...registryNetworkOptions(options),
1438
1778
  },
1439
1779
  );
1440
1780
  if (
@@ -1448,7 +1788,7 @@ export function registerPluginCommand(program) {
1448
1788
  if (remoteArtifacts?.authority && sourceMetadata?.catalogAuthority) {
1449
1789
  sourceMetadata.catalogAuthority = catalogAuthorityFromPreflight(
1450
1790
  marketplacePreflight,
1451
- null,
1791
+ marketplaceImpact,
1452
1792
  remoteArtifacts.authority,
1453
1793
  );
1454
1794
  }
@@ -1489,8 +1829,9 @@ export function registerPluginCommand(program) {
1489
1829
  }
1490
1830
  : null;
1491
1831
  let res = null;
1492
- let marketplaceTransactionFinalized = false;
1493
- let marketplaceCleanupPending = false;
1832
+ let lifecycleTransactionFinalized = false;
1833
+ let lifecycleCleanupPending = false;
1834
+ let lifecycleCleanupPath = null;
1494
1835
  try {
1495
1836
  res = installFromSource(installSource, {
1496
1837
  scope: options.scope,
@@ -1501,7 +1842,12 @@ export function registerPluginCommand(program) {
1501
1842
  expectedIdentity,
1502
1843
  managedPolicy: managed,
1503
1844
  policySource: sourceMetadata?.registry || source,
1504
- transactional: Boolean(marketplacePreflight),
1845
+ remoteSbomBytes: remoteArtifacts?.sbomBytes || null,
1846
+ offline: options.offline === true,
1847
+ transactional: true,
1848
+ enforceUpdateApprovals: true,
1849
+ allowSourceSwitch: options.allowSourceSwitch === true,
1850
+ allowDowngrade: options.allowDowngrade === true,
1505
1851
  });
1506
1852
  const marketplaceAuthorityPersisted = marketplacePreflight
1507
1853
  ? res.sourceMetadata?.catalogAuthority?.catalogDigest ===
@@ -1513,9 +1859,17 @@ export function registerPluginCommand(program) {
1513
1859
  (!marketplacePreflight.selectionDigest ||
1514
1860
  res.sourceMetadata?.catalogAuthority?.selectionDigest ===
1515
1861
  marketplacePreflight.selectionDigest) &&
1862
+ res.sourceMetadata?.catalogAuthority?.updateImpactDigest ===
1863
+ marketplaceImpact?.impactDigest &&
1516
1864
  (!remoteArtifacts?.authority ||
1517
1865
  res.sourceMetadata?.catalogAuthority?.remoteArtifactEvidence
1518
- ?.evidenceDigest === remoteArtifacts.authority.evidenceDigest)
1866
+ ?.evidenceDigest ===
1867
+ remoteArtifacts.authority.evidenceDigest) &&
1868
+ remoteSbomComparisonAuthorityMatches(
1869
+ res.sourceMetadata?.catalogAuthority,
1870
+ remoteArtifacts?.authority,
1871
+ marketplacePreflight.integrity?.sbom,
1872
+ )
1519
1873
  : null;
1520
1874
  if (marketplacePreflight && !marketplaceAuthorityPersisted) {
1521
1875
  throw new Error(
@@ -1538,23 +1892,26 @@ export function registerPluginCommand(program) {
1538
1892
  res.scope,
1539
1893
  process.cwd(),
1540
1894
  );
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;
1895
+ const finalization = finalizePluginUpdate(res);
1896
+ if (!finalization.finalized) {
1897
+ throw new Error(
1898
+ "plugin install transaction could not be finalized",
1899
+ );
1550
1900
  }
1901
+ lifecycleCleanupPending = finalization.cleanupPending === true;
1902
+ lifecycleCleanupPath = finalization.cleanupPath || null;
1903
+ lifecycleTransactionFinalized = true;
1551
1904
  console.log(
1552
1905
  JSON.stringify(
1553
1906
  {
1554
1907
  ...res,
1555
1908
  marketplacePreflight,
1909
+ marketplaceImpact,
1556
1910
  marketplaceAuthorityPersisted,
1557
- marketplaceCleanupPending,
1911
+ marketplaceCleanupPending: lifecycleCleanupPending,
1912
+ cleanupPending:
1913
+ res.cleanupPending === true || lifecycleCleanupPending,
1914
+ cleanupPath: res.cleanupPath || lifecycleCleanupPath,
1558
1915
  capabilities: capNotice,
1559
1916
  capabilitiesGranted,
1560
1917
  },
@@ -1573,16 +1930,15 @@ export function registerPluginCommand(program) {
1573
1930
  interactive: Boolean(process.stdin.isTTY),
1574
1931
  },
1575
1932
  );
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;
1933
+ const finalization = finalizePluginUpdate(res);
1934
+ if (!finalization.finalized) {
1935
+ throw new Error(
1936
+ "plugin install transaction could not be finalized",
1937
+ );
1585
1938
  }
1939
+ lifecycleCleanupPending = finalization.cleanupPending === true;
1940
+ lifecycleCleanupPath = finalization.cleanupPath || null;
1941
+ lifecycleTransactionFinalized = true;
1586
1942
  logger.success(
1587
1943
  `Installed ${res.name} v${res.version} (${res.scope} scope)` +
1588
1944
  (res.signatureVerified ? chalk.green(" ✔ signed") : ""),
@@ -1602,18 +1958,26 @@ export function registerPluginCommand(program) {
1602
1958
  );
1603
1959
  }
1604
1960
  }
1961
+ if (res.cleanupPending === true || lifecycleCleanupPending) {
1962
+ logger.warn(
1963
+ `Install committed; inert cleanup remains at ${res.cleanupPath || lifecycleCleanupPath || "an internal cleanup directory"} and will be retried on the next mutation`,
1964
+ );
1965
+ }
1605
1966
  for (const w of res.warnings || [])
1606
1967
  logger.log(chalk.yellow(` ⚠ ${w}`));
1607
1968
  }
1608
1969
  } catch (err) {
1609
1970
  let recoveryNote = "";
1610
- if (res && marketplacePreflight && !marketplaceTransactionFinalized) {
1971
+ if (res && !lifecycleTransactionFinalized) {
1611
1972
  try {
1612
1973
  const recovery = rollbackPluginUpdate(res);
1613
1974
  if (recovery.rolledBack) {
1614
1975
  recoveryNote = recovery.version
1615
1976
  ? `; restored v${recovery.version}`
1616
1977
  : "; removed the rejected install";
1978
+ if (recovery.cleanupPending) {
1979
+ recoveryNote += `; inert cleanup retained at ${recovery.cleanupPath || "an internal cleanup directory"}`;
1980
+ }
1617
1981
  }
1618
1982
  } catch (recoveryError) {
1619
1983
  recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
@@ -1638,6 +2002,16 @@ export function registerPluginCommand(program) {
1638
2002
  "--allow-insecure-registry",
1639
2003
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1640
2004
  )
2005
+ .option("--offline", "Use only the verified immutable registry cache")
2006
+ .option(
2007
+ "--registry-digest <url=sha256>",
2008
+ "Pin the exact immutable registry document",
2009
+ collectRepeatableOption,
2010
+ [],
2011
+ )
2012
+ .option("--proxy <url>", "Explicit HTTP(S) registry proxy")
2013
+ .option("--pac-file <path>", "Bounded local PAC file")
2014
+ .option("--ca-file <path>", "Custom registry CA bundle")
1641
2015
  .option("--json", "Output as JSON")
1642
2016
  .action(async (query, options) => {
1643
2017
  const { fetchRegistry, listRegistryPlugins, resolveRegistryToken } =
@@ -1650,14 +2024,24 @@ export function registerPluginCommand(program) {
1650
2024
  config = null;
1651
2025
  }
1652
2026
  try {
2027
+ const registryDigestPins = normalizeRegistryDigestPins(
2028
+ options.registryDigest,
2029
+ [options.registry],
2030
+ );
1653
2031
  const token = resolveRegistryToken(options.registry, {
1654
2032
  token: options.token,
1655
2033
  config,
1656
2034
  });
1657
- const { registry, fromCache } = await fetchRegistry(options.registry, {
1658
- token,
1659
- allowInsecure: options.allowInsecureRegistry === true,
1660
- });
2035
+ const { registry, fromCache, documentSha256 } = await fetchRegistry(
2036
+ options.registry,
2037
+ {
2038
+ token,
2039
+ allowInsecure: options.allowInsecureRegistry === true,
2040
+ offline: options.offline === true,
2041
+ expectedSha256: registryDigestPins.get(options.registry),
2042
+ ...registryNetworkOptions(options),
2043
+ },
2044
+ );
1661
2045
  let rows = listRegistryPlugins(registry);
1662
2046
  if (query) {
1663
2047
  const q = query.toLowerCase();
@@ -1668,7 +2052,13 @@ export function registerPluginCommand(program) {
1668
2052
  );
1669
2053
  }
1670
2054
  if (options.json) {
1671
- console.log(JSON.stringify({ fromCache, plugins: rows }, null, 2));
2055
+ console.log(
2056
+ JSON.stringify(
2057
+ { fromCache, documentSha256, plugins: rows },
2058
+ null,
2059
+ 2,
2060
+ ),
2061
+ );
1672
2062
  return;
1673
2063
  }
1674
2064
  if (fromCache) {
@@ -1683,6 +2073,7 @@ export function registerPluginCommand(program) {
1683
2073
  return;
1684
2074
  }
1685
2075
  logger.log(chalk.bold(`Plugins (${rows.length}):`));
2076
+ logger.log(chalk.gray(`Registry SHA-256: ${documentSha256}`));
1686
2077
  for (const r of rows) {
1687
2078
  const ver = r.version ? chalk.gray(` v${r.version}`) : "";
1688
2079
  logger.log(
@@ -1719,6 +2110,16 @@ export function registerPluginCommand(program) {
1719
2110
  "--allow-insecure-registry",
1720
2111
  "Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
1721
2112
  )
2113
+ .option("--offline", "Use only verified immutable registry caches")
2114
+ .option(
2115
+ "--registry-digest <url=sha256>",
2116
+ "Pin exact immutable registry documents (repeatable)",
2117
+ collectRepeatableOption,
2118
+ [],
2119
+ )
2120
+ .option("--proxy <url>", "Explicit HTTP(S) registry proxy")
2121
+ .option("--pac-file <path>", "Bounded local PAC file")
2122
+ .option("--ca-file <path>", "Custom registry CA bundle")
1722
2123
  .option(
1723
2124
  "--strict",
1724
2125
  "Block candidates missing digest, signature, SBOM, license, or capabilities",
@@ -1745,6 +2146,17 @@ export function registerPluginCommand(program) {
1745
2146
  process.exitCode = 1;
1746
2147
  return;
1747
2148
  }
2149
+ let registryDigestPins;
2150
+ try {
2151
+ registryDigestPins = normalizeRegistryDigestPins(
2152
+ options.registryDigest,
2153
+ registryUrls,
2154
+ );
2155
+ } catch (error) {
2156
+ logger.error(`Marketplace catalog failed: ${error.message}`);
2157
+ process.exitCode = 1;
2158
+ return;
2159
+ }
1748
2160
  const { discoverPlugins } =
1749
2161
  await import("../lib/plugin-runtime/scopes.js");
1750
2162
  const { VERSION } = await import("../constants.js");
@@ -1766,6 +2178,9 @@ export function registerPluginCommand(program) {
1766
2178
  const resolved = await fetchRegistry(url, {
1767
2179
  token,
1768
2180
  allowInsecure: options.allowInsecureRegistry === true,
2181
+ offline: options.offline === true,
2182
+ expectedSha256: registryDigestPins.get(url),
2183
+ ...registryNetworkOptions(options),
1769
2184
  });
1770
2185
  return { url, ...resolved };
1771
2186
  } catch (error) {
@@ -1904,6 +2319,16 @@ export function registerPluginCommand(program) {
1904
2319
  "--allow-insecure-registry",
1905
2320
  "Allow plain-HTTP registry URLs (MITM risk — trusted networks only)",
1906
2321
  )
2322
+ .option("--offline", "Use only verified immutable registry caches")
2323
+ .option(
2324
+ "--registry-digest <url=sha256>",
2325
+ "Pin exact immutable registry documents (repeatable)",
2326
+ collectRepeatableOption,
2327
+ [],
2328
+ )
2329
+ .option("--proxy <url>", "Explicit HTTP(S) registry proxy")
2330
+ .option("--pac-file <path>", "Bounded local PAC file")
2331
+ .option("--ca-file <path>", "Custom registry CA bundle")
1907
2332
  .option(
1908
2333
  "--strict",
1909
2334
  "Block candidates missing digest, signature, SBOM, license, or capabilities",
@@ -1911,14 +2336,22 @@ export function registerPluginCommand(program) {
1911
2336
  .option("--json", "Output the versioned candidate selection as JSON")
1912
2337
  .action(async (name, options) => {
1913
2338
  try {
2339
+ const registryUrls = normalizeRegistryUrls(options.registry);
2340
+ const registryDigestPins = normalizeRegistryDigestPins(
2341
+ options.registryDigest,
2342
+ registryUrls,
2343
+ );
1914
2344
  const result = await buildRegistrySetSelection(
1915
- normalizeRegistryUrls(options.registry),
2345
+ registryUrls,
1916
2346
  name,
1917
2347
  process.cwd(),
1918
2348
  {
1919
2349
  token: options.token,
1920
2350
  allowInsecure: options.allowInsecureRegistry === true,
1921
2351
  strict: options.strict === true,
2352
+ offline: options.offline === true,
2353
+ registryDigestPins,
2354
+ ...registryNetworkOptions(options),
1922
2355
  },
1923
2356
  );
1924
2357
  const { selection } = result;
@@ -1980,10 +2413,24 @@ export function registerPluginCommand(program) {
1980
2413
  "--allow-insecure-registry",
1981
2414
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1982
2415
  )
2416
+ .option("--offline", "Use only verified immutable registry caches")
2417
+ .option(
2418
+ "--registry-digest <url=sha256>",
2419
+ "Pin exact immutable registry documents (repeatable)",
2420
+ collectRepeatableOption,
2421
+ [],
2422
+ )
2423
+ .option("--proxy <url>", "Explicit HTTP(S) registry proxy")
2424
+ .option("--pac-file <path>", "Bounded local PAC file")
2425
+ .option("--ca-file <path>", "Custom registry CA bundle")
1983
2426
  .option("--json", "Output the versioned update-impact projection as JSON")
1984
2427
  .action(async (name, options) => {
1985
2428
  try {
1986
2429
  const registryUrls = normalizeRegistryUrls(options.registry);
2430
+ const registryDigestPins = normalizeRegistryDigestPins(
2431
+ options.registryDigest,
2432
+ registryUrls,
2433
+ );
1987
2434
  const selected = await buildRegistrySetSelection(
1988
2435
  registryUrls,
1989
2436
  name,
@@ -1991,6 +2438,9 @@ export function registerPluginCommand(program) {
1991
2438
  {
1992
2439
  token: options.token,
1993
2440
  allowInsecure: options.allowInsecureRegistry === true,
2441
+ offline: options.offline === true,
2442
+ registryDigestPins,
2443
+ ...registryNetworkOptions(options),
1994
2444
  },
1995
2445
  );
1996
2446
  if (!selected.preflight) {
@@ -2019,6 +2469,12 @@ export function registerPluginCommand(program) {
2019
2469
  logger.log(
2020
2470
  ` source: ${impact.changes.source.kind}${impact.changes.source.requiresApproval ? " (explicit approval required)" : ""}`,
2021
2471
  );
2472
+ if (impact.changes.scopeAuthority) {
2473
+ const authority = impact.changes.scopeAuthority;
2474
+ logger.log(
2475
+ ` scope authority: ${authority.effectiveFrom || "none"} → ${authority.effectiveTo || "none"}${authority.changed ? " (effective change)" : ""}`,
2476
+ );
2477
+ }
2022
2478
  logger.log(
2023
2479
  ` license: ${impact.changes.license.from || "unknown"} → ${impact.changes.license.to || "unknown"}`,
2024
2480
  );
@@ -2060,11 +2516,17 @@ export function registerPluginCommand(program) {
2060
2516
  plugin
2061
2517
  .command("installed")
2062
2518
  .description("List plugins installed in the unified runtime (scope dirs)")
2519
+ .option(
2520
+ "--all-scopes",
2521
+ "Show every physical scoped install, including shadowed and disabled rows",
2522
+ )
2063
2523
  .option("--json", "Output as JSON")
2064
2524
  .action(async (options) => {
2065
- const { listInstalled } =
2525
+ const { listInstalled, listInstalledAllScopes } =
2066
2526
  await import("../lib/plugin-runtime/install.js");
2067
- const rows = listInstalled({ cwd: process.cwd() });
2527
+ const rows = options.allScopes
2528
+ ? listInstalledAllScopes({ cwd: process.cwd() })
2529
+ : listInstalled({ cwd: process.cwd() });
2068
2530
  if (options.json) {
2069
2531
  console.log(JSON.stringify(rows, null, 2));
2070
2532
  return;
@@ -2077,6 +2539,17 @@ export function registerPluginCommand(program) {
2077
2539
  await import("../lib/plugin-runtime/trust.js");
2078
2540
  logger.log(chalk.bold(`Installed plugins (${rows.length}):`));
2079
2541
  for (const r of rows) {
2542
+ if (r.runtimeBlocked === true) {
2543
+ const status = r.activePointer?.status || "unknown";
2544
+ const inspectionVersion = r.activePointer?.inspectionVersion;
2545
+ const inspection = inspectionVersion
2546
+ ? `, inspect=v${inspectionVersion}`
2547
+ : "";
2548
+ logger.log(
2549
+ ` ${chalk.red("!")} ${chalk.cyan(r.name)} ${chalk.gray(`[${r.scope}]`)} ${chalk.red(`active=blocked (${status})`)}${inspection}`,
2550
+ );
2551
+ continue;
2552
+ }
2080
2553
  const ok = r.ok ? chalk.green("✔") : chalk.red("✖");
2081
2554
  const trust = isPluginTrusted(r)
2082
2555
  ? chalk.green("trusted")
@@ -2100,8 +2573,172 @@ export function registerPluginCommand(program) {
2100
2573
  }
2101
2574
  });
2102
2575
 
2576
+ // plugin transaction/recover — inspect or resolve an abandoned durable
2577
+ // lifecycle owner. Ordinary mutations never reclaim this authority.
2578
+ plugin
2579
+ .command("transaction <name>")
2580
+ .description("Inspect a durable cross-process plugin lifecycle transaction")
2581
+ .option("--scope <scope>", "Installed plugin scope", "user")
2582
+ .option("--json", "Output the redacted transaction authority as JSON")
2583
+ .action(async (name, options) => {
2584
+ try {
2585
+ const { inspectPluginTransaction } =
2586
+ await import("../lib/plugin-runtime/install.js");
2587
+ const transaction = inspectPluginTransaction(name, {
2588
+ scope: options.scope,
2589
+ cwd: process.cwd(),
2590
+ });
2591
+ if (!transaction) {
2592
+ throw new Error(
2593
+ `no retained transaction exists for ${name} at ${options.scope} scope`,
2594
+ );
2595
+ }
2596
+ if (options.json) {
2597
+ console.log(JSON.stringify(transaction, null, 2));
2598
+ return;
2599
+ }
2600
+ logger.log(
2601
+ `${name} [${options.scope}] ${transaction.operation}/${transaction.phase} r${transaction.revision}`,
2602
+ );
2603
+ logger.log(
2604
+ ` owner: pid=${transaction.owner.pid} host=${transaction.owner.hostname} alive=${transaction.owner.alive == null ? "unverifiable" : transaction.owner.alive}`,
2605
+ );
2606
+ logger.log(` journal digest: ${transaction.journalDigest}`);
2607
+ } catch (error) {
2608
+ logger.error(`Transaction inspection failed: ${error.message}`);
2609
+ process.exitCode = 1;
2610
+ }
2611
+ });
2612
+
2613
+ plugin
2614
+ .command("recover <name>")
2615
+ .description("Recover a durable plugin transaction after its owner died")
2616
+ .option("--scope <scope>", "Installed plugin scope", "user")
2617
+ .option(
2618
+ "--action <action>",
2619
+ "Recovery action (rollback|finalize|abort)",
2620
+ "rollback",
2621
+ )
2622
+ .option(
2623
+ "--force-owner",
2624
+ "Explicitly override a live or cross-host owner after external adjudication",
2625
+ )
2626
+ .option("--json", "Output the recovery result as JSON")
2627
+ .action(async (name, options) => {
2628
+ try {
2629
+ const { recoverPluginTransaction } =
2630
+ await import("../lib/plugin-runtime/install.js");
2631
+ const recovery = recoverPluginTransaction(name, {
2632
+ scope: options.scope,
2633
+ cwd: process.cwd(),
2634
+ action: options.action,
2635
+ forceOwner: options.forceOwner === true,
2636
+ });
2637
+ if (options.json) {
2638
+ console.log(JSON.stringify(recovery, null, 2));
2639
+ return;
2640
+ }
2641
+ logger.success(
2642
+ `${name} transaction ${recovery.action}: ${recovery.recovered ? "recovered" : "recovery still required"}`,
2643
+ );
2644
+ if (recovery.cleanupPending) {
2645
+ logger.warn(`Cleanup remains at ${recovery.cleanupPath}`);
2646
+ }
2647
+ } catch (error) {
2648
+ logger.error(`Transaction recovery failed: ${error.message}`);
2649
+ process.exitCode = 1;
2650
+ }
2651
+ });
2652
+
2653
+ plugin
2654
+ .command("provenance-plan <name>")
2655
+ .description(
2656
+ "Build exact payload-bound authority bytes for a signed legacy provenance migration",
2657
+ )
2658
+ .requiredOption("--version <version>", "Installed plugin version")
2659
+ .requiredOption(
2660
+ "--metadata <file>",
2661
+ "JSON source metadata to bind (local, git, or complete registry authority)",
2662
+ )
2663
+ .option("--scope <scope>", "Installed plugin scope", "user")
2664
+ .option(
2665
+ "--issued-at <timestamp>",
2666
+ "Canonical ISO-8601 UTC signing time (defaults to now)",
2667
+ )
2668
+ .action(async (name, options) => {
2669
+ try {
2670
+ const { planPluginProvenanceMigration } =
2671
+ await import("../lib/plugin-runtime/install.js");
2672
+ const plan = planPluginProvenanceMigration(name, {
2673
+ scope: options.scope,
2674
+ cwd: process.cwd(),
2675
+ version: options.version,
2676
+ issuedAt: options.issuedAt,
2677
+ sourceMetadata: readBoundedPluginJson(
2678
+ options.metadata,
2679
+ "plugin source metadata",
2680
+ ),
2681
+ });
2682
+ console.log(JSON.stringify(plan, null, 2));
2683
+ } catch (error) {
2684
+ logger.error(`Provenance migration plan failed: ${error.message}`);
2685
+ process.exitCode = 1;
2686
+ }
2687
+ });
2688
+
2689
+ plugin
2690
+ .command("provenance-migrate <name>")
2691
+ .description(
2692
+ "Apply a signed, payload-bound provenance record to one legacy install",
2693
+ )
2694
+ .requiredOption("--version <version>", "Installed plugin version")
2695
+ .requiredOption(
2696
+ "--attestation <file>",
2697
+ "Signed attestation JSON containing authority, publicKeyPem, and signatureBase64",
2698
+ )
2699
+ .requiredOption(
2700
+ "--expected-signer-sha256 <hex>",
2701
+ "Exact trusted signer SPKI SHA-256 fingerprint",
2702
+ )
2703
+ .option("--scope <scope>", "Installed plugin scope", "user")
2704
+ .option("--yes", "Confirm the provenance mutation")
2705
+ .option("--json", "Output the migration result as JSON")
2706
+ .action(async (name, options) => {
2707
+ try {
2708
+ if (options.yes !== true) {
2709
+ throw new Error(
2710
+ "refusing provenance mutation without --yes after reviewing the signed plan",
2711
+ );
2712
+ }
2713
+ const { migratePluginProvenance } =
2714
+ await import("../lib/plugin-runtime/install.js");
2715
+ const result = migratePluginProvenance(name, {
2716
+ scope: options.scope,
2717
+ cwd: process.cwd(),
2718
+ version: options.version,
2719
+ attestation: readBoundedPluginJson(
2720
+ options.attestation,
2721
+ "plugin provenance attestation",
2722
+ ),
2723
+ expectedSignerSha256: options.expectedSignerSha256,
2724
+ });
2725
+ if (options.json) {
2726
+ console.log(JSON.stringify(result, null, 2));
2727
+ return;
2728
+ }
2729
+ logger.success(
2730
+ `${name}@${result.version} provenance migrated at ${result.scope} scope`,
2731
+ );
2732
+ logger.log(` authority: ${result.authorityDigest}`);
2733
+ logger.log(` signer: ${result.signerPublicKeySha256}`);
2734
+ } catch (error) {
2735
+ logger.error(`Provenance migration failed: ${error.message}`);
2736
+ process.exitCode = 1;
2737
+ }
2738
+ });
2739
+
2103
2740
  // plugin evidence — compare persisted registry artifact expectations with
2104
- // actual bytes in the active immutable install. Remote artifacts are not
2741
+ // actual bytes in the active on-disk install. Remote artifacts are not
2105
2742
  // re-fetched here. The signature record is bound to the locally re-verified
2106
2743
  // lock; the external SBOM digest remains an install-time record because its
2107
2744
  // document bytes are not retained.
@@ -2118,7 +2755,7 @@ export function registerPluginCommand(program) {
2118
2755
  .option("--json", "Output the versioned artifact readback as JSON")
2119
2756
  .action(async (name, options) => {
2120
2757
  try {
2121
- const { listInstalled } =
2758
+ const { listInstalled, readSourceMetadataStrict } =
2122
2759
  await import("../lib/plugin-runtime/install.js");
2123
2760
  const installed = listInstalled({
2124
2761
  cwd: process.cwd(),
@@ -2130,12 +2767,21 @@ export function registerPluginCommand(program) {
2130
2767
  if (!installed) {
2131
2768
  throw new Error(`${name} is not installed at ${options.scope} scope`);
2132
2769
  }
2770
+ if (installed.runtimeBlocked === true) {
2771
+ throw new Error(
2772
+ `${name} runtime is blocked by active pointer status ${installed.activePointer?.status || "unknown"}; repair it with plugin use before reading active evidence`,
2773
+ );
2774
+ }
2775
+ const source = readSourceMetadataStrict(installed.dir, {
2776
+ required: true,
2777
+ requireRegistryAuthority: true,
2778
+ });
2133
2779
  const { buildPluginMarketplaceArtifactReadback } =
2134
2780
  await import("../lib/plugin-runtime/marketplace-artifact-readback.js");
2135
2781
  const evidence = buildPluginMarketplaceArtifactReadback({
2136
2782
  root: installed.dir,
2137
2783
  scope: installed.scope,
2138
- source: installed.source,
2784
+ source,
2139
2785
  });
2140
2786
  if (options.json) {
2141
2787
  console.log(JSON.stringify(evidence, null, 2));
@@ -2174,14 +2820,14 @@ export function registerPluginCommand(program) {
2174
2820
  }`
2175
2821
  : null,
2176
2822
  evidence.claims.remoteSbomFetched
2177
- ? "the SBOM digest is an install-time record and was not re-hashed here"
2823
+ ? "the remote SBOM document digest is an install-time record; its document bytes were not retained or re-hashed, while the installed payload inventory above was freshly computed"
2178
2824
  : null,
2179
2825
  ].filter(Boolean);
2180
2826
  logger.log(
2181
2827
  chalk.gray(
2182
2828
  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.",
2829
+ ? ` Install-time remote ${remoteKinds.join("/")} evidence is present: ${remoteDetail.join("; ")}. Remote documents were not re-fetched; publisher identity is ${evidence.claims.registryPublisherIdentityVerified ? "verified by the current managed organization trust root" : "unverified"}.`
2830
+ : ` No valid install-time remote signature/SBOM evidence is available; publisher identity is ${evidence.claims.registryPublisherIdentityVerified ? "verified by the current managed organization trust root" : "unverified"}.`,
2185
2831
  ),
2186
2832
  );
2187
2833
  }
@@ -2574,13 +3220,29 @@ export function registerPluginCommand(program) {
2574
3220
  .description(`Uninstall a runtime plugin from a scope (${SCOPES})`)
2575
3221
  .option("--scope <scope>", "Scope to remove from", "user")
2576
3222
  .option("--version <version>", "Remove only this version (default: all)")
3223
+ .option(
3224
+ "--allow-source-switch",
3225
+ "Approve a different-source fallback when removing the active version",
3226
+ )
2577
3227
  .action(async (name, options) => {
2578
- const { uninstall } = await import("../lib/plugin-runtime/install.js");
3228
+ const { listInstalled, uninstall } =
3229
+ await import("../lib/plugin-runtime/install.js");
2579
3230
  try {
3231
+ const blockedRow = listInstalled({
3232
+ cwd: process.cwd(),
3233
+ scopes: [options.scope],
3234
+ }).find(
3235
+ (row) =>
3236
+ row.name === name &&
3237
+ row.scope === options.scope &&
3238
+ row.runtimeBlocked === true,
3239
+ );
2580
3240
  const res = uninstall(name, {
2581
3241
  scope: options.scope,
2582
3242
  cwd: process.cwd(),
2583
3243
  version: options.version,
3244
+ allowSourceSwitch: options.allowSourceSwitch === true,
3245
+ allowBlockedIdentity: Boolean(blockedRow),
2584
3246
  });
2585
3247
  logger.success(
2586
3248
  `Uninstalled ${name} (${res.removed.join(", ") || "nothing"}) from ${options.scope} scope`,
@@ -2617,6 +3279,28 @@ export function registerPluginCommand(program) {
2617
3279
  "--allow-insecure-registry",
2618
3280
  "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
2619
3281
  )
3282
+ .option(
3283
+ "--offline",
3284
+ "Use verified registry/artifact/source caches or an existing local plugin source",
3285
+ )
3286
+ .option(
3287
+ "--registry-digest <url=sha256>",
3288
+ "Pin an exact immutable registry document (repeatable)",
3289
+ collectRepeatableOption,
3290
+ [],
3291
+ )
3292
+ .option(
3293
+ "--proxy <url>",
3294
+ "Explicit HTTP(S) registry proxy (or CC_PLUGIN_REGISTRY_PROXY)",
3295
+ )
3296
+ .option(
3297
+ "--pac-file <path>",
3298
+ "Bounded local PAC file (or CC_PLUGIN_REGISTRY_PAC_FILE)",
3299
+ )
3300
+ .option(
3301
+ "--ca-file <path>",
3302
+ "Custom registry CA bundle (or CC_PLUGIN_REGISTRY_CA_FILE)",
3303
+ )
2620
3304
  .option(
2621
3305
  "--allow-source-switch",
2622
3306
  "Explicitly approve changing registry/source authority",
@@ -2649,8 +3333,28 @@ export function registerPluginCommand(program) {
2649
3333
  const registryUrls = normalizeRegistryUrls(options.registry);
2650
3334
  const { isRemoteSource, resolveRemoteSource } =
2651
3335
  await import("../lib/plugin-runtime/remote-source.js");
3336
+ if (
3337
+ options.offline === true &&
3338
+ registryUrls.length === 0 &&
3339
+ !isRemoteSource(source)
3340
+ ) {
3341
+ try {
3342
+ assertOfflinePluginSourceLocal(source);
3343
+ } catch (error) {
3344
+ logger.error(`Offline source validation failed: ${error.message}`);
3345
+ process.exitCode = 1;
3346
+ return;
3347
+ }
3348
+ }
2652
3349
  if (registryUrls.length || isRemoteSource(source)) {
2653
3350
  try {
3351
+ const requestedRegistryUrls = registryUrls.length
3352
+ ? registryUrls
3353
+ : [source];
3354
+ const registryDigestPins = normalizeRegistryDigestPins(
3355
+ options.registryDigest,
3356
+ requestedRegistryUrls,
3357
+ );
2654
3358
  let resolved;
2655
3359
  let url;
2656
3360
  let name;
@@ -2663,6 +3367,9 @@ export function registerPluginCommand(program) {
2663
3367
  {
2664
3368
  token: options.token,
2665
3369
  allowInsecure: options.allowInsecureRegistry === true,
3370
+ offline: options.offline === true,
3371
+ registryDigestPins,
3372
+ ...registryNetworkOptions(options),
2666
3373
  },
2667
3374
  );
2668
3375
  resolved = selected.resolved;
@@ -2682,6 +3389,9 @@ export function registerPluginCommand(program) {
2682
3389
  token: options.token,
2683
3390
  config,
2684
3391
  allowInsecure: options.allowInsecureRegistry === true,
3392
+ offline: options.offline === true,
3393
+ expectedSha256: registryDigestPins.get(url),
3394
+ ...registryNetworkOptions(options),
2685
3395
  });
2686
3396
  marketplacePreflight = await buildRegistryInstallPreflight(
2687
3397
  url,
@@ -2711,6 +3421,25 @@ export function registerPluginCommand(program) {
2711
3421
  options.scope,
2712
3422
  process.cwd(),
2713
3423
  );
3424
+ if (
3425
+ marketplaceImpact.blockers.some(
3426
+ (blocker) => blocker.code === "SEMANTIC_SBOM_BINDING_DOWNGRADE",
3427
+ )
3428
+ ) {
3429
+ throw new Error(
3430
+ "registry candidate preflight blocked: SEMANTIC_SBOM_BINDING_DOWNGRADE",
3431
+ );
3432
+ }
3433
+ if (
3434
+ marketplaceImpact.blockers.some(
3435
+ (blocker) =>
3436
+ blocker.code === "REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
3437
+ )
3438
+ ) {
3439
+ throw new Error(
3440
+ "registry candidate preflight blocked: REGISTRY_VERSION_REQUIRED_FOR_UPDATE",
3441
+ );
3442
+ }
2714
3443
  if (
2715
3444
  marketplaceImpact.changes.source.requiresApproval &&
2716
3445
  options.allowSourceSwitch !== true
@@ -2808,6 +3537,8 @@ export function registerPluginCommand(program) {
2808
3537
  {
2809
3538
  token: options.token,
2810
3539
  allowInsecure: options.allowInsecureRegistry === true,
3540
+ offline: options.offline === true,
3541
+ ...registryNetworkOptions(options),
2811
3542
  },
2812
3543
  );
2813
3544
  if (
@@ -2873,7 +3604,12 @@ export function registerPluginCommand(program) {
2873
3604
  expectedIdentity,
2874
3605
  managedPolicy: managed,
2875
3606
  policySource: sourceMetadata?.registry || source,
3607
+ remoteSbomBytes: remoteArtifacts?.sbomBytes || null,
3608
+ offline: options.offline === true,
2876
3609
  transactional: true,
3610
+ enforceUpdateApprovals: true,
3611
+ allowSourceSwitch: options.allowSourceSwitch === true,
3612
+ allowDowngrade: options.allowDowngrade === true,
2877
3613
  });
2878
3614
  marketplaceAuthorityPersisted = await installedCatalogAuthorityMatches(
2879
3615
  res.name,
@@ -2937,6 +3673,7 @@ export function registerPluginCommand(program) {
2937
3673
  let activationStatus = changed ? "activated" : "unchanged";
2938
3674
  let rollbackVersion = null;
2939
3675
  let cleanupPending = false;
3676
+ let cleanupPath = null;
2940
3677
  if (rollbackReason) {
2941
3678
  const recovery = rollbackPluginUpdate(res);
2942
3679
  if (!recovery.rolledBack) {
@@ -2944,6 +3681,8 @@ export function registerPluginCommand(program) {
2944
3681
  }
2945
3682
  activationStatus = "rolled_back";
2946
3683
  rollbackVersion = recovery.version;
3684
+ cleanupPending = recovery.cleanupPending === true;
3685
+ cleanupPath = recovery.cleanupPath || null;
2947
3686
  marketplaceAuthorityPersisted =
2948
3687
  await installedCatalogAuthorityMatches(
2949
3688
  res.name,
@@ -2954,7 +3693,9 @@ export function registerPluginCommand(program) {
2954
3693
  remoteArtifacts?.authority || null,
2955
3694
  );
2956
3695
  } else {
2957
- cleanupPending = finalizePluginUpdate(res).cleanupPending === true;
3696
+ const finalization = finalizePluginUpdate(res);
3697
+ cleanupPending = finalization.cleanupPending === true;
3698
+ cleanupPath = finalization.cleanupPath || null;
2958
3699
  }
2959
3700
 
2960
3701
  if (options.json) {
@@ -2972,6 +3713,7 @@ export function registerPluginCommand(program) {
2972
3713
  rollbackVersion,
2973
3714
  rollbackReason,
2974
3715
  cleanupPending,
3716
+ cleanupPath,
2975
3717
  },
2976
3718
  null,
2977
3719
  2,
@@ -3018,6 +3760,11 @@ export function registerPluginCommand(program) {
3018
3760
  `${res.name} is already up to date at v${res.version} (use --force to reinstall)`,
3019
3761
  );
3020
3762
  }
3763
+ if (cleanupPending) {
3764
+ logger.warn(
3765
+ `Plugin state committed; inert cleanup remains at ${cleanupPath || "an internal cleanup directory"} and will be retried on the next mutation`,
3766
+ );
3767
+ }
3021
3768
  } catch (err) {
3022
3769
  let recoveryNote = "";
3023
3770
  if (res) {
@@ -3027,6 +3774,9 @@ export function registerPluginCommand(program) {
3027
3774
  recoveryNote = recovery.version
3028
3775
  ? `; restored v${recovery.version}`
3029
3776
  : "; removed the rejected install";
3777
+ if (recovery.cleanupPending) {
3778
+ recoveryNote += `; inert cleanup retained at ${recovery.cleanupPath || "an internal cleanup directory"}`;
3779
+ }
3030
3780
  }
3031
3781
  } catch (recoveryError) {
3032
3782
  recoveryNote = `; automatic recovery failed: ${recoveryError.message}`;
@@ -3044,6 +3794,10 @@ export function registerPluginCommand(program) {
3044
3794
  .command("use <name> <version>")
3045
3795
  .description("Pin a plugin's active version (rollback or switch)")
3046
3796
  .option("--scope <scope>", "Scope", "user")
3797
+ .option(
3798
+ "--allow-source-switch",
3799
+ "Approve activating an installed version from a different source",
3800
+ )
3047
3801
  .action(async (name, version, options) => {
3048
3802
  const { setActiveVersion } =
3049
3803
  await import("../lib/plugin-runtime/install.js");
@@ -3051,6 +3805,7 @@ export function registerPluginCommand(program) {
3051
3805
  setActiveVersion(name, version, {
3052
3806
  scope: options.scope,
3053
3807
  cwd: process.cwd(),
3808
+ allowSourceSwitch: options.allowSourceSwitch === true,
3054
3809
  });
3055
3810
  logger.success(
3056
3811
  `${name} active version → v${version} (${options.scope} scope)`,