chainlesschain 0.162.182 → 0.162.184

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-DzZ5_xoj.js → AIOps-DzAl5fGx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DeLXddIg.js → ActionButton-DAHtMGeC.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-rvDCmet1.js → Analytics-BHQaexv5.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-Yt6QVDnp.js → AppLayout-Phz_pD0n.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-BS112K5L.js → Artifacts-DHt47kCA.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-0wJuQrNu.js → Audit--Y_8FULl.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-B90PMgbn.js → BackgroundAgents-N1zchH7D.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-TkgMglQQ.js → Backup-B_BoyN30.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-CQVS9p1l.js → BaseInput-Gl4uQHcT.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-YTjQHoC7.js → Chat-Cg-eIyAZ.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-5bwaOabq.js → Checkbox-D0MGR2wA.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BFmNQ_Xe.js → Codegen-BdObJEFn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BT_u4Dzp.js → Col--ghxXT3z.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-CJP52u5b.js → Community-BUFeXOqz.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BpzhJE9i.js → Compact-DhQO2_Mr.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Cg7rfslp.js → Compliance-3HoTqKYd.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-JisxL6Yy.js → Cowork-B3zmO4VW.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-Bu7Ug_MC.js → Cron-CJDrZOcc.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CvuqoLdj.js → Crosschain-SMl15obd.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-C2u9G8Pu.js → DID-CgTrTWrl.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-BFt5Yv3d.js → Dashboard-dXFKaKol.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DIaT9Wxq.js → Dropdown-qQHEhCvI.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-D_A601Y4.js → EmailListRenderer-C_ej0MWH.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard--IyQ8I8w.js → FamilyGuardDashboard-D6ajoE7I.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BihWHxuY.js → Federation-Cio2y49h.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BB8dOXc_.js → FormItemContext-ClJz1cYp.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DQYhj1HH.js → GenericCardRenderer-Dr_8Maca.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-B-2QZxGf.js → Git-CevLwp5u.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CusSb-nG.js → Governance-CW9YyQUY.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BAjmvPPL.js → Inference-CQqeAyER.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DoNYWKYq.js → KnowledgeGraph-cuGVxHrw.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B8fKWD-n.js → Logs-DIuHri5d.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-BlJm1J7E.js → MarkdownRenderer-R91Qw5JA.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-CyOscbSL.js → Marketplace-CY1VhFGx.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-Ex6Om5da.js → McpTools-BBAbi1vt.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-CDcD8l8r.js → Memory-BWYGHxJA.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-xfBqoFDg.js → MobileBridge-CPIFkW7A.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BwObMMA2.js → MobileProjects-DQyWxCho.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CIWd36OV.js → Mtc-Bo3GnQbx.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-CPX0ouuA.js → MtcAudit-C8-c4rSW.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BDJsIUuW.js → Multisig-ChqMTtoq.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DEkbuD3N.js → NLProgramming-C48TyhvI.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DJpHRhH1.js → Notes-zogUFWxp.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-C1UZ-Qd5.js → NotificationSettings-v-K9XpSB.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-Bbhj0fac.js → Organization-Boi1dQfR.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-1H04x3kx.js → Overflow-DM5YWwgA.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CDpPe3et.js → P2P-C57LKpKK.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-MUX0Bpvw.js → PdhVaultBrowser-BFkPqu6Q.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-A07-0Ci1.js → Permissions-D6fB9YSp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DhhDWxAZ.js → PersonalDataHub-Bp7EefEo.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-CyfbJfe-.js → Pipeline-BVln3hZf.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-CNFps3ED.js → Privacy-Bf8P3nF6.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit--T-u3Dr5.js → ProjectInit-DM6wA2E2.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DJmrK2fC.js → ProjectSettings-CGbc5Iho.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-D0p9EOxm.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-DLzzbOZd.js → Providers-B2kJcxS6.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-CexOomKS.js → QrScannerModal-Cxu7dXYy.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-QklGi19_.js → QuickAsk-DTlrTPms.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Bb9kwSw8.js → Recommend-Cm3X-cCj.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DZuVNNN5.js → RemoteSession-DNb2kjvt.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-Dr5cLP0N.js → Reputation-CZUFFjc1.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-oCQHYv5t.js → Row-DAPgloxA.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-IFDEykIR.js → RssFeed-dsx9S793.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-CspVIxDc.js → Search-DnnC8kz-.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bm66o_DH.js → Security-VuZknRMK.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-DvanNqXl.js → Services-B3XNX8Vx.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CHjEe9_O.js → Skeleton-Dl1nVtCj.js} +1 -1
  72. package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +1 -0
  73. package/src/assets/web-panel/assets/{Sla-DxZm1491.js → Sla-HzU56n1z.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-DWxEEclC.js → SpeechSettings-CRle7Jl2.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-FIvOFw5p.js → SyncSettings-DWadz7Sl.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-CaNjzV_t.js → Templates-BBAgvrlM.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DjlP6TmH.js → Tenant-BsbbvGN8.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-uscpZI4q.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-DoZ8y7y8.js → TimelineRenderer-mTyjNE6r.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-390cg2TC.js → Tokens-Bz97oBgs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CLrtCj5_.js → Trigger-BTRKC140.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DpESfSPn.js → Trust-B4A_KuyX.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CN6279O0.js → UkeySign-DES9TmLU.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DPtAUlSe.js → VideoEditing-ClXs7Qxp.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-nKOMV_1n.js → Wallet-IzZqFz-7.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-HuXlSOc4.js → WebAuthn-BtOP04g_.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CUfFjARi.js → WorkflowEditor-_0cqnopY.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-6Eq0PcZJ.js → chat-ax7193uU.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BP_Sf6Iy.js → colors-Bu4WGJah.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BufMKIh5.js → compact-item-BX1dlTW0.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BBMXxWSW.js → createContext-CktA_Dj7.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-x877iKKS.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-fUK_tRO2.js → hasIn-D5wd817l.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-t8N1tSeX.js → index-5PYYkr8S.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DLpIs_P_.js → index-A4uBPQCz.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DVsqvI4M.js → index-BAtBNfkr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Bkg4ZePE.js → index-BLLsUNDc.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-cpEli44X.js → index-BQtoRyTO.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-wKTLbKyc.js → index-BRfXoRuh.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B7Etk0Te.js → index-Bad2CYeH.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CwSf5pnf.js → index-BcGdL3sG.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-3FuwBewR.js → index-C3HmzCLt.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BHy6yNiF.js → index-C8DkWyNr.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CgiECHu2.js → index-C8tLR2TG.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C44atv4k.js → index-CP8ELJBc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CW_G-MXw.js → index-CUForzsG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-pum7HYBA.js → index-CYRE0NJA.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BWKoBU8R.js → index-CeVZaJSC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D4_MuGkd.js → index-CfzxR61U.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dyngbd29.js → index-Ci3y1ZAv.js} +19 -19
  112. package/src/assets/web-panel/assets/{index-fzaMtSJ8.js → index-CjEJjl5C.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Cp8vhyZy.js → index-Cv7bVWJ5.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DPdUNk1i.js → index-D-o4Rwjk.js} +1 -1
  115. package/src/assets/web-panel/assets/index-D7pfPouB.js +1 -0
  116. package/src/assets/web-panel/assets/{index-CeASi4fr.js → index-D9op8poE.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DASluSqY.js → index-DF1TysIF.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DocdRcbd.js → index-DYo9S5OT.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-2A5ph8b2.js → index-D_cycOET.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-wV6iGdpY.js → index-Db3CpWTr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BMLHv389.js → index-DkD1K-fF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B9WYSn4m.js → index-DmPJ6T6F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-uCbEN7cY.js → index-DmVUeWUk.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DmkMXwnR.js → index-Dzdr0zDf.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CZ6Yt17L.js → index-PBLzwYnu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CE2DdTP2.js → index-bFCaM7Eq.js} +1 -1
  127. package/src/assets/web-panel/assets/index-e4i-VLBd.js +1 -0
  128. package/src/assets/web-panel/assets/{index-GwdRUZ-x.js → index-h3KJZr95.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-B2vS66eI.js → index-iHcPtQ2e.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-6GmBa4Kg.js → index-jzlzm4VK.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-D9vU25ij.js → index-o14NNnxw.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CGjmILDm.js → index-tYPSfGbd.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-LvCBfTLo.js → index-ytp2H5Zx.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-V5Fo95FM.js → initDefaultProps-D2Iut83B.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BYWU0hUT.js → motion-BuaJVdK9.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Cr1M-J2h.js → move-CaEuM92y.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-BysQKi2N.js → mtc-parser-wuKNBwys.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-D1zHU8GC.js → omit-7a3eklaq.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-CUArxtfi.js → pickAttrs-Dn4z7KvJ.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-ChoYEys0.js → placementArrow-DVn_qT4-.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-BQRs3Mo7.js → responsiveObserve-Dodz1OMQ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-B8zbsCkg.js → slide-CxoGskbp.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-C2U8Gs6h.js → statusUtils-DBh_epQH.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-Dd8MgL_V.js → styleChecker-RXQ4gG5P.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-hknqY5NH.js → useFlexGapSupport-1Wxm0HEG.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CLTAmcfw.js → useFs-CI_ySNxJ.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-ByegHbiB.js → usePersonalDataHub-C6973rZP.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DqlAy5DP.js → vnode-CalynwKr.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DiXiztQn.js → zoom-CM1cd-Q0.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agenda.js +74 -1
  152. package/src/commands/agent.js +9 -0
  153. package/src/commands/background-session.js +60 -1
  154. package/src/commands/batch.js +160 -5
  155. package/src/commands/plugin.js +318 -43
  156. package/src/commands/session.js +44 -1
  157. package/src/commands/team.js +212 -6
  158. package/src/data/changelog.json +26 -0
  159. package/src/harness/jsonl-session-store.js +59 -6
  160. package/src/harness/mcp-client.js +65 -43
  161. package/src/lib/agent-batch.js +4 -1
  162. package/src/lib/agent-team/team-runner.js +19 -1
  163. package/src/lib/agent-team/team-worktree.js +3 -0
  164. package/src/lib/async-hook-supervisor.cjs +82 -10
  165. package/src/lib/async-hook-supervisor.js +50 -4
  166. package/src/lib/background-agent-supervisor.js +49 -0
  167. package/src/lib/collaboration-run-store.js +362 -0
  168. package/src/lib/hook-runner.cjs +98 -17
  169. package/src/lib/hook-runner.js +77 -6
  170. package/src/lib/hook-shell-command.cjs +148 -0
  171. package/src/lib/hooks-v2-runtime.js +188 -77
  172. package/src/lib/jsonl-session-store.js +2 -0
  173. package/src/lib/lsp/__tests__/lsp-client.test.js +18 -0
  174. package/src/lib/lsp/lsp-client.js +12 -1
  175. package/src/lib/plugin-monitor-supervisor.js +53 -16
  176. package/src/lib/plugin-runtime/bin.js +1 -1
  177. package/src/lib/plugin-runtime/install.js +465 -54
  178. package/src/lib/plugin-runtime/mcp.js +21 -2
  179. package/src/lib/plugin-runtime/monitors.js +19 -3
  180. package/src/lib/plugin-runtime/sandbox-policy.js +99 -0
  181. package/src/lib/plugin-runtime/scopes.js +13 -2
  182. package/src/lib/plugin-usage-attribution.js +44 -0
  183. package/src/lib/process-execution-broker/index.js +705 -13
  184. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1347 -0
  185. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1260 -0
  186. package/src/lib/process-execution-broker/platform-sandbox.js +1831 -157
  187. package/src/lib/session-usage.js +278 -1
  188. package/src/lib/stream-retry.js +29 -0
  189. package/src/repl/agent-repl.js +38 -9
  190. package/src/runtime/agent-core.js +32 -4
  191. package/src/runtime/headless-runner.js +46 -1
  192. package/src/runtime/headless-stream.js +54 -3
  193. package/src/runtime/mcp-config.js +15 -5
  194. package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +0 -1
  195. package/src/assets/web-panel/assets/OrderTableRenderer-GlJtqLjT.js +0 -1
  196. package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +0 -1
  197. package/src/assets/web-panel/assets/Skills-Cb8jWjMX.js +0 -1
  198. package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +0 -1
  199. package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +0 -3
  200. package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +0 -1
  201. package/src/assets/web-panel/assets/index-DOZTSoS7.js +0 -1
  202. package/src/assets/web-panel/assets/index-DhpvjpqI.js +0 -1
@@ -26,22 +26,33 @@ import {
26
26
  listInstalledVersions,
27
27
  activeVersion,
28
28
  discoverPlugins,
29
+ DISABLED_FILENAME,
29
30
  } from "./scopes.js";
30
- import { verifyPluginManifest } from "../plugin-security.js";
31
+ import {
32
+ enforcePluginPolicy,
33
+ verifyPluginManifest,
34
+ } from "../plugin-security.js";
31
35
  import {
32
36
  writePluginLock,
33
37
  buildPluginSbom,
34
38
  LOCK_FILENAME,
39
+ readPluginLock,
40
+ verifyInstalledSignature,
35
41
  } from "./signature.js";
42
+ import { loadManagedPluginPolicy, filterByManagedPolicy } from "./policy.js";
43
+ import { managedSettingsPath } from "../settings-loader.cjs";
36
44
  import executionBroker from "../process-execution-broker/index.js";
37
45
 
38
46
  export const MAX_LISTED_PLUGIN_VERSIONS = 64;
47
+ export const SOURCE_METADATA_FILENAME = ".plugin-source.json";
39
48
 
40
49
  export const _deps = {
41
50
  existsSync: fs.existsSync,
42
51
  mkdirSync: fs.mkdirSync,
43
52
  rmSync: fs.rmSync,
53
+ renameSync: fs.renameSync,
44
54
  readdirSync: fs.readdirSync,
55
+ readFileSync: fs.readFileSync,
45
56
  copyFileSync: fs.copyFileSync,
46
57
  lstatSync: fs.lstatSync,
47
58
  writeFileSync: fs.writeFileSync,
@@ -49,6 +60,8 @@ export const _deps = {
49
60
  spawnSync: (...args) => executionBroker.spawnSync(...args),
50
61
  };
51
62
 
63
+ const pendingPluginTransactions = new WeakMap();
64
+
52
65
  /**
53
66
  * Install a plugin from a local directory into a scope's immutable version dir.
54
67
  * @param {string} srcDir directory containing the plugin (with its manifest)
@@ -69,6 +82,17 @@ export function installFromDirectory(srcDir, opts = {}) {
69
82
  );
70
83
  }
71
84
  const { name, version } = manifest.metadata;
85
+ const sourceMetadata = normalizeSourceMetadata(opts.sourceMetadata);
86
+ if (opts.managedPolicy) {
87
+ enforcePluginPolicy(
88
+ {
89
+ name,
90
+ source: opts.policySource || sourceMetadata?.source || null,
91
+ action: "install",
92
+ },
93
+ opts.managedPolicy,
94
+ );
95
+ }
72
96
 
73
97
  // Optional signature/integrity verification of the manifest at install time
74
98
  // (Phase 3.3l). verifyPluginManifest THROWS on any mismatch/failure — a signed
@@ -90,55 +114,116 @@ export function installFromDirectory(srcDir, opts = {}) {
90
114
  }
91
115
 
92
116
  const dest = pluginVersionDir(scope, name, version, { cwd: opts.cwd });
93
- if (_deps.existsSync(dest)) {
94
- if (!opts.force) {
95
- throw new Error(
96
- `${name}@${version} is already installed at ${scope} scope (immutable). ` +
97
- `Use --force to reinstall, or bump the version.`,
98
- );
99
- }
100
- _deps.rmSync(dest, { recursive: true, force: true });
117
+ const destExists = _deps.existsSync(dest);
118
+ if (destExists && !opts.force) {
119
+ throw new Error(
120
+ `${name}@${version} is already installed at ${scope} scope (immutable). ` +
121
+ `Use --force to reinstall, or bump the version.`,
122
+ );
101
123
  }
102
124
 
103
- _deps.mkdirSync(dest, { recursive: true });
104
- copyDirGuarded(src, dest, dest);
125
+ const nameDir = pluginNameDir(scope, name, { cwd: opts.cwd });
126
+ _deps.mkdirSync(nameDir, { recursive: true });
127
+ const transactionRoot = _deps.mkdtempSync(path.join(nameDir, ".install-"));
128
+ const staged = path.join(transactionRoot, "staged");
129
+ const backup = destExists ? path.join(transactionRoot, "previous") : null;
130
+ const previousActive = getActiveVersion(name, { scope, cwd: opts.cwd });
131
+ let installedAtDest = false;
132
+ let transactionRetained = false;
105
133
 
106
- // A lock may ONLY exist if THIS installer wrote it. The source is untrusted
107
- // and may ship its own `.plugin-lock.json` (copied verbatim above) on an
108
- // unsigned install that forged lock would otherwise survive into the
109
- // "immutable" version dir and defeat load-time requireSignedPlugins.
110
- _deps.rmSync(path.join(dest, LOCK_FILENAME), { force: true });
134
+ try {
135
+ _deps.mkdirSync(staged, { recursive: true });
136
+ copyDirGuarded(src, staged, staged);
111
137
 
112
- // Record the verified signature into the installed (immutable) version dir so
113
- // load-time enforcement can re-check it. The manifest was copied verbatim, so
114
- // its bytes/sha in `dest` match what we verified in `src`.
115
- if (verification) {
116
- const destManifest = path.join(
117
- dest,
118
- path.relative(src, manifest.manifestPath),
119
- );
120
- writePluginLock(dest, {
121
- manifestFile: destManifest,
122
- sha256: verification.sha256,
123
- publicKeySha256: verification.publicKeySha256,
124
- signatureVerified: verification.signatureVerified === true,
125
- signatureBase64: verification.signatureBase64,
126
- publicKeyPem: verification.publicKeyPem,
127
- sbom: buildPluginSbom(dest),
128
- });
129
- }
138
+ // A lock may ONLY exist if THIS installer wrote it. The source is untrusted
139
+ // and may ship its own `.plugin-lock.json` or provenance metadata.
140
+ _deps.rmSync(path.join(staged, LOCK_FILENAME), { force: true });
141
+ _deps.rmSync(path.join(staged, SOURCE_METADATA_FILENAME), { force: true });
130
142
 
131
- // Make the freshly-installed version active.
132
- setActiveVersion(name, version, { scope, cwd: opts.cwd });
143
+ if (sourceMetadata) {
144
+ _deps.writeFileSync(
145
+ path.join(staged, SOURCE_METADATA_FILENAME),
146
+ JSON.stringify(sourceMetadata, null, 2),
147
+ "utf8",
148
+ );
149
+ }
133
150
 
134
- return {
135
- name,
136
- version,
137
- scope,
138
- dir: dest,
139
- warnings: manifest.warnings,
140
- signatureVerified: verification?.signatureVerified === true,
141
- };
151
+ // Record the verified signature into the staged immutable version. The
152
+ // manifest and complete component SBOM are re-verified below before any
153
+ // active bytes or pointer can change.
154
+ if (verification) {
155
+ const stagedManifest = path.join(
156
+ staged,
157
+ path.relative(src, manifest.manifestPath),
158
+ );
159
+ writePluginLock(staged, {
160
+ manifestFile: stagedManifest,
161
+ sha256: verification.sha256,
162
+ publicKeySha256: verification.publicKeySha256,
163
+ signatureVerified: verification.signatureVerified === true,
164
+ signatureBase64: verification.signatureBase64,
165
+ publicKeyPem: verification.publicKeyPem,
166
+ sbom: buildPluginSbom(staged),
167
+ });
168
+ }
169
+ validateStagedInstall(staged, { name, version, verification });
170
+
171
+ // Commit the fully validated directory with same-volume renames. A forced
172
+ // same-version reinstall retains the old bytes until the caller finalizes
173
+ // the upgrade transaction.
174
+ if (backup) _deps.renameSync(dest, backup);
175
+ try {
176
+ _deps.renameSync(staged, dest);
177
+ installedAtDest = true;
178
+ } catch (error) {
179
+ if (backup && _deps.existsSync(backup) && !_deps.existsSync(dest)) {
180
+ _deps.renameSync(backup, dest);
181
+ }
182
+ throw error;
183
+ }
184
+
185
+ try {
186
+ setActiveVersion(name, version, { scope, cwd: opts.cwd });
187
+ } catch (error) {
188
+ restoreInstalledBytes({ dest, backup });
189
+ restoreActivePointer(name, previousActive, { scope, cwd: opts.cwd });
190
+ installedAtDest = false;
191
+ throw error;
192
+ }
193
+
194
+ const result = {
195
+ name,
196
+ version,
197
+ scope,
198
+ dir: dest,
199
+ warnings: manifest.warnings,
200
+ signatureVerified: verification?.signatureVerified === true,
201
+ sourceMetadata,
202
+ enabled: isPluginEnabled(name, { scope, cwd: opts.cwd }),
203
+ loadValidated: true,
204
+ };
205
+ if (opts.transactional === true) {
206
+ pendingPluginTransactions.set(result, {
207
+ name,
208
+ version,
209
+ scope,
210
+ cwd: opts.cwd,
211
+ dest,
212
+ backup,
213
+ transactionRoot,
214
+ previousActive,
215
+ });
216
+ transactionRetained = true;
217
+ }
218
+ return result;
219
+ } finally {
220
+ if (!transactionRetained) {
221
+ if (installedAtDest && backup && _deps.existsSync(backup)) {
222
+ _deps.rmSync(backup, { recursive: true, force: true });
223
+ }
224
+ _deps.rmSync(transactionRoot, { recursive: true, force: true });
225
+ }
226
+ }
142
227
  }
143
228
 
144
229
  /**
@@ -150,8 +235,21 @@ export function installFromDirectory(srcDir, opts = {}) {
150
235
  */
151
236
  export function installFromSource(source, opts = {}) {
152
237
  return _withMaterializedSource(source, (dir, info) => {
153
- const res = installFromDirectory(dir, opts);
154
- return info ? { ...res, source: info.url, ref: info.ref || null } : res;
238
+ const sourceMetadata =
239
+ normalizeSourceMetadata(opts.sourceMetadata) ||
240
+ normalizeSourceMetadata(
241
+ info
242
+ ? { type: "git", source: info.url, ref: info.ref || null }
243
+ : { type: "local", source: path.resolve(String(source || "")) },
244
+ );
245
+ const res = installFromDirectory(dir, { ...opts, sourceMetadata });
246
+ return info
247
+ ? {
248
+ ...res,
249
+ source: sourceMetadata?.source || null,
250
+ ref: sourceMetadata?.ref || null,
251
+ }
252
+ : res;
155
253
  });
156
254
  }
157
255
 
@@ -202,6 +300,13 @@ export function updatePlugin(source, opts = {}) {
202
300
  );
203
301
  }
204
302
  const { name, version } = manifest.metadata;
303
+ const sourceMetadata =
304
+ normalizeSourceMetadata(opts.sourceMetadata) ||
305
+ normalizeSourceMetadata(
306
+ info
307
+ ? { type: "git", source: info.url, ref: info.ref || null }
308
+ : { type: "local", source: path.resolve(String(source || "")) },
309
+ );
205
310
  const previousVersion = getActiveVersion(name, { scope, cwd: opts.cwd });
206
311
  const dest = pluginVersionDir(scope, name, version, { cwd: opts.cwd });
207
312
  const sameVersionExists = _deps.existsSync(dest);
@@ -212,28 +317,105 @@ export function updatePlugin(source, opts = {}) {
212
317
  if (previousVersion !== version) {
213
318
  setActiveVersion(name, version, { scope, cwd: opts.cwd });
214
319
  }
215
- return {
320
+ const result = {
216
321
  name,
217
322
  version,
218
323
  previousVersion,
219
324
  updated: previousVersion !== version,
220
325
  reinstalled: false,
221
- source: info ? info.url : null,
326
+ source: sourceMetadata?.source || null,
327
+ ref: sourceMetadata?.ref || null,
222
328
  };
329
+ if (opts.transactional === true && previousVersion !== version) {
330
+ pendingPluginTransactions.set(result, {
331
+ name,
332
+ version,
333
+ scope,
334
+ cwd: opts.cwd,
335
+ dest,
336
+ backup: null,
337
+ transactionRoot: null,
338
+ previousActive: previousVersion,
339
+ pointerOnly: true,
340
+ });
341
+ }
342
+ return result;
223
343
  }
224
344
 
225
- const res = installFromDirectory(dir, { ...opts, scope, force: true });
226
- return {
345
+ const res = installFromDirectory(dir, {
346
+ ...opts,
347
+ scope,
348
+ force: true,
349
+ sourceMetadata,
350
+ transactional: opts.transactional === true,
351
+ });
352
+ const result = {
227
353
  ...res,
228
354
  previousVersion,
229
355
  updated: previousVersion !== version,
230
356
  reinstalled: sameVersionExists,
231
- source: info ? info.url : null,
232
- ref: info ? info.ref || null : null,
357
+ source: sourceMetadata?.source || null,
358
+ ref: sourceMetadata?.ref || null,
233
359
  };
360
+ transferPendingTransaction(res, result);
361
+ return result;
234
362
  });
235
363
  }
236
364
 
365
+ /**
366
+ * Commit a transactional update after capability consent and command-layer
367
+ * validation have succeeded. Backup cleanup is best-effort: an active,
368
+ * validated install must not be reported as failed solely because antivirus or
369
+ * another reader briefly holds the hidden rollback directory open.
370
+ */
371
+ export function finalizePluginUpdate(result) {
372
+ const transaction = pendingPluginTransactions.get(result);
373
+ if (!transaction) return { finalized: false };
374
+ pendingPluginTransactions.delete(result);
375
+ let cleanupPending = false;
376
+ if (transaction.transactionRoot) {
377
+ try {
378
+ _deps.rmSync(transaction.transactionRoot, {
379
+ recursive: true,
380
+ force: true,
381
+ });
382
+ } catch {
383
+ cleanupPending = true;
384
+ }
385
+ }
386
+ return { finalized: true, cleanupPending };
387
+ }
388
+
389
+ /**
390
+ * Restore the active version and, for a forced same-version reinstall, the
391
+ * exact prior bytes. New rejected versions are removed so `.active` fallback
392
+ * cannot silently select them later.
393
+ */
394
+ export function rollbackPluginUpdate(result) {
395
+ const transaction = pendingPluginTransactions.get(result);
396
+ if (!transaction) return { rolledBack: false, version: null };
397
+
398
+ restoreActivePointer(
399
+ transaction.name,
400
+ transaction.previousActive,
401
+ transaction,
402
+ );
403
+ if (!transaction.pointerOnly) {
404
+ restoreInstalledBytes(transaction);
405
+ }
406
+ pendingPluginTransactions.delete(result);
407
+ if (transaction.transactionRoot) {
408
+ _deps.rmSync(transaction.transactionRoot, {
409
+ recursive: true,
410
+ force: true,
411
+ });
412
+ }
413
+ return {
414
+ rolledBack: true,
415
+ version: transaction.previousActive || null,
416
+ };
417
+ }
418
+
237
419
  /**
238
420
  * Classify a source string into a git URL (+ optional ref), or null when it is
239
421
  * not remote-looking. `owner/repo` expands to a GitHub HTTPS URL.
@@ -319,10 +501,26 @@ export function fetchGitRepo(url, ref) {
319
501
  * invisible and impossible to inspect / uninstall / un-deny.
320
502
  */
321
503
  export function listInstalled(opts = {}) {
504
+ let managed = null;
505
+ let managedError = null;
506
+ try {
507
+ managed = loadManagedPluginPolicy({
508
+ env: opts.env,
509
+ managedSettingsFile: opts.managedSettingsFile,
510
+ });
511
+ } catch (error) {
512
+ managedError = error;
513
+ }
514
+ const policySource = managedSettingsPath({
515
+ env: opts.env,
516
+ managedSettingsFile: opts.managedSettingsFile,
517
+ });
518
+
322
519
  return discoverPlugins({
323
520
  cwd: opts.cwd,
324
521
  scopes: opts.scopes,
325
522
  skipPolicy: true,
523
+ includeDisabled: true,
326
524
  }).map((p) => {
327
525
  const allVersions = listInstalledVersions(p.scope, p.name, {
328
526
  cwd: opts.cwd,
@@ -335,6 +533,16 @@ export function listInstalled(opts = {}) {
335
533
  ) {
336
534
  versions[versions.length - 1] = p.version;
337
535
  }
536
+ const lock = readPluginLock(p.root);
537
+ const signature = verifyInstalledSignature(p);
538
+ const policyResult =
539
+ managed && !managedError
540
+ ? filterByManagedPolicy([p], managed)
541
+ : { kept: [p], dropped: [] };
542
+ const policyDrop = policyResult.dropped[0] || null;
543
+ const sbomFiles = Array.isArray(lock?.sbom?.files)
544
+ ? lock.sbom.files.slice(0, 100000)
545
+ : [];
338
546
  return {
339
547
  name: p.name,
340
548
  version: p.version,
@@ -342,6 +550,45 @@ export function listInstalled(opts = {}) {
342
550
  scope: p.scope,
343
551
  dir: p.root,
344
552
  ok: p.manifest?.ok === true,
553
+ enabled: p.enabled !== false,
554
+ source: readSourceMetadata(p.root),
555
+ integrity: {
556
+ signature: {
557
+ present: Boolean(lock?.signatureBase64 && lock?.publicKeyPem),
558
+ verified: signature.signed === true,
559
+ reason: signature.signed ? null : signature.reason || "unsigned",
560
+ manifestSha256: lock?.sha256 || null,
561
+ publicKeySha256:
562
+ signature.publicKeySha256 || lock?.publicKeySha256 || null,
563
+ },
564
+ sbom: {
565
+ present: Boolean(lock?.sbom),
566
+ digest: lock?.sbom?.digest || null,
567
+ fileCount: sbomFiles.length,
568
+ totalBytes: Math.min(
569
+ Number.MAX_SAFE_INTEGER,
570
+ sbomFiles.reduce(
571
+ (sum, file) =>
572
+ sum +
573
+ (Number.isFinite(Number(file?.bytes)) && Number(file.bytes) > 0
574
+ ? Number(file.bytes)
575
+ : 0),
576
+ 0,
577
+ ),
578
+ ),
579
+ },
580
+ },
581
+ policy: {
582
+ managed: Boolean(managed) || Boolean(managedError),
583
+ source: policySource,
584
+ allowed: !managedError && !policyDrop,
585
+ reason: managedError
586
+ ? managedError.message
587
+ : policyDrop?.reason || (managed ? "allowed by managed policy" : ""),
588
+ requireSigned:
589
+ managed?.requireSignedPlugins === true ||
590
+ managed?.requireSignedPlugins === "require",
591
+ },
345
592
  };
346
593
  });
347
594
  }
@@ -407,16 +654,120 @@ export function setActiveVersion(name, version, opts = {}) {
407
654
  if (!_deps.existsSync(versionDir)) {
408
655
  throw new Error(`${name}@${version} is not installed at ${scope} scope`);
409
656
  }
410
- const activeFile = path.join(pluginNameDir(scope, name, { cwd }), ".active");
411
- _deps.writeFileSync(activeFile, String(version), "utf8");
657
+ const nameDir = pluginNameDir(scope, name, { cwd });
658
+ const activeFile = path.join(nameDir, ".active");
659
+ const tempDir = _deps.mkdtempSync(path.join(nameDir, ".active-"));
660
+ const tempFile = path.join(tempDir, "next");
661
+ try {
662
+ _deps.writeFileSync(tempFile, String(version), "utf8");
663
+ _deps.renameSync(tempFile, activeFile);
664
+ } finally {
665
+ _deps.rmSync(tempDir, { recursive: true, force: true });
666
+ }
412
667
  return { name, version, scope, active: version };
413
668
  }
414
669
 
670
+ /** Enable or disable one scoped plugin without deleting immutable versions. */
671
+ export function setPluginEnabled(name, enabled, opts = {}) {
672
+ const scope = opts.scope || "user";
673
+ const cwd = opts.cwd;
674
+ const nameDir = pluginNameDir(scope, name, { cwd });
675
+ if (
676
+ !_deps.existsSync(nameDir) ||
677
+ listInstalledVersions(scope, name, { cwd }).length === 0
678
+ ) {
679
+ throw new Error(`${name} is not installed at ${scope} scope`);
680
+ }
681
+ const marker = path.join(nameDir, DISABLED_FILENAME);
682
+ if (enabled) {
683
+ _deps.rmSync(marker, { force: true });
684
+ } else {
685
+ _deps.writeFileSync(
686
+ marker,
687
+ JSON.stringify(
688
+ {
689
+ disabled: true,
690
+ reason: String(opts.reason || "disabled by user").slice(0, 256),
691
+ },
692
+ null,
693
+ 2,
694
+ ),
695
+ "utf8",
696
+ );
697
+ }
698
+ return { name, scope, enabled: Boolean(enabled) };
699
+ }
700
+
701
+ /** Current lifecycle state for one scoped plugin. */
702
+ export function isPluginEnabled(name, opts = {}) {
703
+ const scope = opts.scope || "user";
704
+ return !_deps.existsSync(
705
+ path.join(pluginNameDir(scope, name, { cwd: opts.cwd }), DISABLED_FILENAME),
706
+ );
707
+ }
708
+
415
709
  /** Which version is active for a plugin at a scope (or null). */
416
710
  export function getActiveVersion(name, opts = {}) {
417
711
  return activeVersion(opts.scope || "user", name, { cwd: opts.cwd });
418
712
  }
419
713
 
714
+ function readSourceMetadata(versionDir) {
715
+ const file = path.join(versionDir, SOURCE_METADATA_FILENAME);
716
+ if (!_deps.existsSync(file)) return null;
717
+ try {
718
+ return normalizeSourceMetadata(
719
+ JSON.parse(_deps.readFileSync(file, "utf8")),
720
+ );
721
+ } catch {
722
+ return null;
723
+ }
724
+ }
725
+
726
+ function normalizeSourceMetadata(value) {
727
+ if (!value || typeof value !== "object") return null;
728
+ const type = ["local", "git", "registry"].includes(value.type)
729
+ ? value.type
730
+ : "git";
731
+ const source = sanitizeSource(value.source, type === "local");
732
+ if (!source) return null;
733
+ const metadata = {
734
+ version: 1,
735
+ type,
736
+ source,
737
+ ref: cleanBounded(value.ref, 256),
738
+ };
739
+ const registry = sanitizeSource(value.registry);
740
+ const resolvedSource = sanitizeSource(value.resolvedSource);
741
+ const packageName = cleanBounded(value.package, 256);
742
+ if (registry) metadata.registry = registry;
743
+ if (resolvedSource) metadata.resolvedSource = resolvedSource;
744
+ if (packageName) metadata.package = packageName;
745
+ if (value.offline === true) metadata.offline = true;
746
+ return metadata;
747
+ }
748
+
749
+ function sanitizeSource(value, preservePath = false) {
750
+ const raw = cleanBounded(value, 4096);
751
+ if (!raw) return null;
752
+ if (preservePath) return raw;
753
+ try {
754
+ const parsed = new URL(raw);
755
+ parsed.username = "";
756
+ parsed.password = "";
757
+ parsed.search = "";
758
+ parsed.hash = "";
759
+ return parsed.toString();
760
+ } catch {
761
+ return raw;
762
+ }
763
+ }
764
+
765
+ function cleanBounded(value, max) {
766
+ if (typeof value !== "string") return null;
767
+ const clean = value.replace(/\p{Cc}/gu, "").trim();
768
+ return clean ? clean.slice(0, max) : null;
769
+ }
770
+
420
771
  // ── guarded recursive copy ────────────────────────────────────────────────
421
772
 
422
773
  /**
@@ -437,3 +788,63 @@ function copyDirGuarded(src, dst, root) {
437
788
  }
438
789
  }
439
790
  }
791
+
792
+ function validateStagedInstall(root, { name, version, verification }) {
793
+ const parsed = parsePluginManifest(root);
794
+ if (
795
+ !parsed.ok ||
796
+ parsed.metadata.name !== name ||
797
+ parsed.metadata.version !== version
798
+ ) {
799
+ const details = parsed.errors?.length
800
+ ? `: ${parsed.errors.join("; ")}`
801
+ : "";
802
+ throw new Error(`staged plugin failed load validation${details}`);
803
+ }
804
+ if (verification) {
805
+ const signature = verifyInstalledSignature({ root });
806
+ if (!signature.signed) {
807
+ throw new Error(
808
+ `staged plugin failed signature/SBOM validation: ${signature.reason || "unknown error"}`,
809
+ );
810
+ }
811
+ }
812
+ }
813
+
814
+ function transferPendingTransaction(from, to) {
815
+ const transaction = pendingPluginTransactions.get(from);
816
+ if (!transaction) return;
817
+ pendingPluginTransactions.delete(from);
818
+ pendingPluginTransactions.set(to, transaction);
819
+ }
820
+
821
+ function restoreActivePointer(name, version, opts = {}) {
822
+ if (version) {
823
+ setActiveVersion(name, version, opts);
824
+ return;
825
+ }
826
+ _deps.rmSync(
827
+ path.join(
828
+ pluginNameDir(opts.scope || "user", name, { cwd: opts.cwd }),
829
+ ".active",
830
+ ),
831
+ { force: true },
832
+ );
833
+ }
834
+
835
+ function restoreInstalledBytes({ dest, backup, transactionRoot }) {
836
+ if (!backup) {
837
+ _deps.rmSync(dest, { recursive: true, force: true });
838
+ return;
839
+ }
840
+ const rejected = path.join(transactionRoot, "rejected");
841
+ _deps.renameSync(dest, rejected);
842
+ try {
843
+ _deps.renameSync(backup, dest);
844
+ } catch (error) {
845
+ if (_deps.existsSync(rejected) && !_deps.existsSync(dest)) {
846
+ _deps.renameSync(rejected, dest);
847
+ }
848
+ throw error;
849
+ }
850
+ }
@@ -21,7 +21,10 @@ import {
21
21
  componentCapabilityDenial,
22
22
  networkTargetDenial,
23
23
  } from "./capabilities.js";
24
- import { mergePluginSandboxPolicies } from "./sandbox-policy.js";
24
+ import {
25
+ issuePluginWorkspaceAuthority,
26
+ mergePluginSandboxPolicies,
27
+ } from "./sandbox-policy.js";
25
28
 
26
29
  export const _deps = { readFileSync: fs.readFileSync };
27
30
 
@@ -89,6 +92,21 @@ export function collectPluginMcpServers(opts = {}) {
89
92
  if (!p.manifest || p.manifest.ok !== true) continue;
90
93
  const m = p.manifest.components?.mcp;
91
94
  if (!m) continue;
95
+ const pluginSource = m.absPath || p.manifest.manifestPath;
96
+ let pluginWorkspaceAuthority;
97
+ try {
98
+ pluginWorkspaceAuthority = issuePluginWorkspaceAuthority({
99
+ root: p.root,
100
+ origin: "plugin:mcp",
101
+ pluginId: p.name,
102
+ pluginVersion: p.version,
103
+ pluginSource,
104
+ });
105
+ } catch {
106
+ // The discovery root disappeared or stopped resolving safely. Refuse the
107
+ // component instead of copying an attacker-controlled path downstream.
108
+ continue;
109
+ }
92
110
  // Component-level capability gate: a plugin that declared a permissions
93
111
  // block but under-declared the `mcp` capability its server needs gets its
94
112
  // MCP component refused here (mirrors the hooks/monitors/bin/lsp denial).
@@ -150,7 +168,8 @@ export function collectPluginMcpServers(opts = {}) {
150
168
  policy: "allow",
151
169
  pluginId: p.name,
152
170
  pluginVersion: p.version,
153
- pluginSource: m.absPath || p.manifest.manifestPath,
171
+ pluginSource,
172
+ pluginWorkspaceAuthority,
154
173
  };
155
174
  added++;
156
175
  }