chainlesschain 0.162.183 → 0.162.185

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 (205) 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-BapS7lz4.js → AIOps-Co4ipAKx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-BpsoDrDq.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-DMUqeBik.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-C6fD_dXr.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DW0YlQlV.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit-DXtHiiFK.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-BgmF62U-.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-D8vhVLQC.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-D7nCtY2X.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-CBZLEV2G.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CI_jT0ru.js → ChatBubbleRenderer-CtDb-2Qu.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-DJLkeD4M.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-Cs6xO8Fd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col-Dtx9wxfT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-DiigJg4n.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-B67oMDDL.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-BBDl-Jos.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-Ba98hSsa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-DBWQZIz7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-OcrKzxAt.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-mW2eSbjM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-BmsDPmKJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-CVzmCyRx.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-vVgqKzhh.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-ZGomhjxr.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-DyaknbLr.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-DQOJIYkG.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CMiY-EEp.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-D6IXuucf.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-N0jD-349.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-WhYuhBxm.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-DTeO28xy.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-MSHzPSE-.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-Cyqs3J3W.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-MWBnezNM.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-DQu35Nu-.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-CQS_M-DP.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-WD16vQvY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-DqsiSwOq.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-D3LoxO-P.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DdcsOqEz.js → OrderTableRenderer-C5jPoTd6.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-48aMBJso.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-j-a2XkhR.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-BYpBNYnh.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-ZRXGXDFb.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-6OWUPHyL.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-FcjlDK9E.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-vD_6Tldp.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-B_CHOLXp.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-rEiqQ2da.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-Bg8obRAM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-Dgtt97B4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-DaGVylZO.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DP2_2x3u.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-D29yj4ki.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-Ue2wnBLp.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CrxIHqeC.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-BgSzJPCn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-AwhYOrP9.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-D2shpEcm.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-BtKzhTbT.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-BY0DsUSm.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-B3Sb0UI9.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DtT6rWOX.js → Skills-Ne5w0bDs.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-C8VkaP4T.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-BQdIEYP9.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-CwFafrUg.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-DYn9qCmx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-DkKReHvn.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-r6bd-owt.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-Mq2juUC3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-BnMw7MRi.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-CTzKp4o9.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-BBrSZm9U.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-D9fDvEMq.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-DUi5ve-0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-C8Fi1P8_.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-s1LppYi2.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-DJL2C4lj.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-D3F2ndOB.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-B7739-e3.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-CcJbFTF4.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CuP_DEFS.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-6YEXfIik.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-4h3lpWJ3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-BIID4XcP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BOHUo3aM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-BYYSLEjn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-Bh4hcNWW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C327pLbw.js → index-BhIKnQAW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-Bru_kpZl.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-BsdSnHQE.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-BxSqVMGh.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-C3ZA_h1j.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-C3pctdCb.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-CRuoRapN.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-CY0zd_jc.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CezFrAmD.js +1 -0
  109. package/src/assets/web-panel/assets/index-Cxgguez6.js +1 -0
  110. package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-CxradkS6.js} +19 -19
  111. package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-D0K_rFC1.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-D0zdPyTD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-D3haorUt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-D6Ij_2uw.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-DDRlBaDt.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-DGsE7MQz.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-DHvxXXNA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DV11jpeO.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-DXdnfDVK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-D_75jYWk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-DjI9MkId.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-DlSvpNMf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dox44j29.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-DpEKmjoa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Dsy7_x4s.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-Gxw09e9R.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-LfRlSmqe.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-Nz-MUHD1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DUU2606o.js → index-b9cuvFEv.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-eR6H6NR7.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-fWgUQZ9d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-mfWQRSLn.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-yOZYSNmG.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-rYJN_vzf.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-DnDK2gTP.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-CKnXy7ES.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-3nRc3hWT.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Ddjj3sLu.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-CbsbphQY.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-D4pvdFj0.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-C2W-vY-S.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-Cbq5150d.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-BA2B7R82.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CLrwg3xg.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-8BDGy-YU.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-B2SzFprl.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/background-session.js +11 -1
  153. package/src/commands/batch.js +160 -5
  154. package/src/commands/plugin.js +90 -27
  155. package/src/commands/session.js +31 -1
  156. package/src/commands/team.js +212 -6
  157. package/src/data/changelog.json +26 -0
  158. package/src/gateways/terminal/PtyManager.js +45 -15
  159. package/src/gateways/ws/ws-agent-handler.js +15 -1
  160. package/src/gateways/ws/ws-session-gateway.js +88 -0
  161. package/src/harness/background-task-command-runner.js +234 -0
  162. package/src/harness/background-task-manager.js +150 -20
  163. package/src/harness/background-task-worker.js +17 -11
  164. package/src/harness/jsonl-session-store.js +57 -6
  165. package/src/harness/mcp-client.js +65 -43
  166. package/src/lib/agent-batch.js +4 -1
  167. package/src/lib/agent-team/team-worktree.js +3 -0
  168. package/src/lib/async-hook-supervisor.cjs +82 -10
  169. package/src/lib/async-hook-supervisor.js +50 -4
  170. package/src/lib/background-agent-supervisor.js +46 -0
  171. package/src/lib/collaboration-run-store.js +362 -0
  172. package/src/lib/event-runtime-host.js +12 -10
  173. package/src/lib/hook-runner.cjs +105 -18
  174. package/src/lib/hook-runner.js +79 -6
  175. package/src/lib/hook-shell-command.cjs +148 -0
  176. package/src/lib/hooks-v2-runtime.js +280 -81
  177. package/src/lib/hooks-v2-workspace-context.js +258 -0
  178. package/src/lib/jsonl-session-store.js +2 -0
  179. package/src/lib/lsp/__tests__/lsp-client.test.js +15 -0
  180. package/src/lib/lsp/lsp-client.js +12 -1
  181. package/src/lib/plugin-monitor-supervisor.js +53 -16
  182. package/src/lib/plugin-runtime/bin.js +21 -5
  183. package/src/lib/plugin-runtime/install.js +249 -57
  184. package/src/lib/plugin-runtime/mcp.js +21 -2
  185. package/src/lib/plugin-runtime/monitors.js +19 -3
  186. package/src/lib/plugin-runtime/sandbox-policy.js +100 -4
  187. package/src/lib/process-execution-broker/index.js +1151 -44
  188. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1557 -0
  189. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1378 -0
  190. package/src/lib/process-execution-broker/platform-sandbox.js +586 -58
  191. package/src/lib/session-usage.js +202 -2
  192. package/src/lib/stream-retry.js +29 -0
  193. package/src/repl/agent-repl.js +40 -3
  194. package/src/runtime/agent-core.js +304 -42
  195. package/src/runtime/headless-runner.js +48 -1
  196. package/src/runtime/headless-stream.js +42 -2
  197. package/src/workers/background-agent-worker.js +5 -11
  198. package/src/assets/web-panel/assets/MarkdownRenderer-C0o4FtpD.js +0 -1
  199. package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
  200. package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
  201. package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +0 -1
  202. package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
  203. package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
  204. package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
  205. package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
@@ -50,6 +50,7 @@ export const _deps = {
50
50
  existsSync: fs.existsSync,
51
51
  mkdirSync: fs.mkdirSync,
52
52
  rmSync: fs.rmSync,
53
+ renameSync: fs.renameSync,
53
54
  readdirSync: fs.readdirSync,
54
55
  readFileSync: fs.readFileSync,
55
56
  copyFileSync: fs.copyFileSync,
@@ -59,6 +60,8 @@ export const _deps = {
59
60
  spawnSync: (...args) => executionBroker.spawnSync(...args),
60
61
  };
61
62
 
63
+ const pendingPluginTransactions = new WeakMap();
64
+
62
65
  /**
63
66
  * Install a plugin from a local directory into a scope's immutable version dir.
64
67
  * @param {string} srcDir directory containing the plugin (with its manifest)
@@ -111,66 +114,116 @@ export function installFromDirectory(srcDir, opts = {}) {
111
114
  }
112
115
 
113
116
  const dest = pluginVersionDir(scope, name, version, { cwd: opts.cwd });
114
- if (_deps.existsSync(dest)) {
115
- if (!opts.force) {
116
- throw new Error(
117
- `${name}@${version} is already installed at ${scope} scope (immutable). ` +
118
- `Use --force to reinstall, or bump the version.`,
119
- );
120
- }
121
- _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
+ );
122
123
  }
123
124
 
124
- _deps.mkdirSync(dest, { recursive: true });
125
- 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;
126
133
 
127
- // A lock may ONLY exist if THIS installer wrote it. The source is untrusted
128
- // and may ship its own `.plugin-lock.json` (copied verbatim above) on an
129
- // unsigned install that forged lock would otherwise survive into the
130
- // "immutable" version dir and defeat load-time requireSignedPlugins.
131
- _deps.rmSync(path.join(dest, LOCK_FILENAME), { force: true });
132
- _deps.rmSync(path.join(dest, SOURCE_METADATA_FILENAME), { force: true });
134
+ try {
135
+ _deps.mkdirSync(staged, { recursive: true });
136
+ copyDirGuarded(src, staged, staged);
137
+
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 });
142
+
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
- if (sourceMetadata) {
135
- _deps.writeFileSync(
136
- path.join(dest, SOURCE_METADATA_FILENAME),
137
- JSON.stringify(sourceMetadata, null, 2),
138
- "utf8",
139
- );
140
- }
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 });
141
170
 
142
- // Record the verified signature into the installed (immutable) version dir so
143
- // load-time enforcement can re-check it. The manifest was copied verbatim, so
144
- // its bytes/sha in `dest` match what we verified in `src`.
145
- if (verification) {
146
- const destManifest = path.join(
147
- dest,
148
- path.relative(src, manifest.manifestPath),
149
- );
150
- writePluginLock(dest, {
151
- manifestFile: destManifest,
152
- sha256: verification.sha256,
153
- publicKeySha256: verification.publicKeySha256,
154
- signatureVerified: verification.signatureVerified === true,
155
- signatureBase64: verification.signatureBase64,
156
- publicKeyPem: verification.publicKeyPem,
157
- sbom: buildPluginSbom(dest),
158
- });
159
- }
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
+ }
160
184
 
161
- // Make the freshly-installed version active.
162
- setActiveVersion(name, version, { scope, cwd: opts.cwd });
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
+ }
163
193
 
164
- return {
165
- name,
166
- version,
167
- scope,
168
- dir: dest,
169
- warnings: manifest.warnings,
170
- signatureVerified: verification?.signatureVerified === true,
171
- sourceMetadata,
172
- enabled: isPluginEnabled(name, { scope, cwd: opts.cwd }),
173
- };
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
+ }
174
227
  }
175
228
 
176
229
  /**
@@ -264,7 +317,7 @@ export function updatePlugin(source, opts = {}) {
264
317
  if (previousVersion !== version) {
265
318
  setActiveVersion(name, version, { scope, cwd: opts.cwd });
266
319
  }
267
- return {
320
+ const result = {
268
321
  name,
269
322
  version,
270
323
  previousVersion,
@@ -273,6 +326,20 @@ export function updatePlugin(source, opts = {}) {
273
326
  source: sourceMetadata?.source || null,
274
327
  ref: sourceMetadata?.ref || null,
275
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;
276
343
  }
277
344
 
278
345
  const res = installFromDirectory(dir, {
@@ -280,8 +347,9 @@ export function updatePlugin(source, opts = {}) {
280
347
  scope,
281
348
  force: true,
282
349
  sourceMetadata,
350
+ transactional: opts.transactional === true,
283
351
  });
284
- return {
352
+ const result = {
285
353
  ...res,
286
354
  previousVersion,
287
355
  updated: previousVersion !== version,
@@ -289,9 +357,65 @@ export function updatePlugin(source, opts = {}) {
289
357
  source: sourceMetadata?.source || null,
290
358
  ref: sourceMetadata?.ref || null,
291
359
  };
360
+ transferPendingTransaction(res, result);
361
+ return result;
292
362
  });
293
363
  }
294
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
+
295
419
  /**
296
420
  * Classify a source string into a git URL (+ optional ref), or null when it is
297
421
  * not remote-looking. `owner/repo` expands to a GitHub HTTPS URL.
@@ -530,8 +654,16 @@ export function setActiveVersion(name, version, opts = {}) {
530
654
  if (!_deps.existsSync(versionDir)) {
531
655
  throw new Error(`${name}@${version} is not installed at ${scope} scope`);
532
656
  }
533
- const activeFile = path.join(pluginNameDir(scope, name, { cwd }), ".active");
534
- _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
+ }
535
667
  return { name, version, scope, active: version };
536
668
  }
537
669
 
@@ -656,3 +788,63 @@ function copyDirGuarded(src, dst, root) {
656
788
  }
657
789
  }
658
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
  }
@@ -22,7 +22,10 @@ import fs from "fs";
22
22
  import { discoverPlugins } from "./scopes.js";
23
23
  import { partitionByTrust, warnUntrustedOnce } from "./trust.js";
24
24
  import { componentCapabilityDenial } from "./capabilities.js";
25
- import { mergePluginSandboxPolicies } from "./sandbox-policy.js";
25
+ import {
26
+ issuePluginWorkspaceAuthority,
27
+ mergePluginSandboxPolicies,
28
+ } from "./sandbox-policy.js";
26
29
 
27
30
  export const _deps = { readFileSync: fs.readFileSync };
28
31
 
@@ -55,7 +58,7 @@ export function _resetMonitorWarnings() {
55
58
  }
56
59
 
57
60
  /** Normalize one raw monitor entry into a validated descriptor, or null. */
58
- function normalizeMonitor(plugin, raw) {
61
+ function normalizeMonitor(plugin, raw, pluginWorkspaceAuthority) {
59
62
  if (!raw || typeof raw !== "object") return null;
60
63
  if (typeof raw.name !== "string" || raw.name === "") return null;
61
64
  if (typeof raw.command !== "string" || raw.command === "") return null;
@@ -91,6 +94,7 @@ function normalizeMonitor(plugin, raw) {
91
94
  intervalMs,
92
95
  env,
93
96
  cwd: plugin.root,
97
+ pluginWorkspaceAuthority,
94
98
  origin: "plugin:monitor",
95
99
  pluginId: plugin.name,
96
100
  pluginVersion: plugin.version,
@@ -131,6 +135,18 @@ export function collectPluginMonitors(opts = {}) {
131
135
  denied.push({ name: p.name, reason: denial.reason });
132
136
  continue;
133
137
  }
138
+ let pluginWorkspaceAuthority;
139
+ try {
140
+ pluginWorkspaceAuthority = issuePluginWorkspaceAuthority({
141
+ root: p.root,
142
+ origin: "plugin:monitor",
143
+ pluginId: p.name,
144
+ pluginVersion: p.version,
145
+ pluginSource: p.manifest?.manifestPath || "",
146
+ });
147
+ } catch {
148
+ continue;
149
+ }
134
150
  let parsed;
135
151
  if (m.absPath) {
136
152
  try {
@@ -159,7 +175,7 @@ export function collectPluginMonitors(opts = {}) {
159
175
  ? parsed
160
176
  : [];
161
177
  for (const raw of list) {
162
- const desc = normalizeMonitor(p, raw);
178
+ const desc = normalizeMonitor(p, raw, pluginWorkspaceAuthority);
163
179
  if (desc) out.push(desc);
164
180
  }
165
181
  }
@@ -1,3 +1,6 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
1
4
  /**
2
5
  * Normalize the small, declarative sandbox contract exposed to plugins.
3
6
  *
@@ -14,6 +17,8 @@ export const PLUGIN_SANDBOX_BOUNDARIES = Object.freeze([
14
17
  ]);
15
18
 
16
19
  const SUPPORTED_BOUNDARIES = new Set(PLUGIN_SANDBOX_BOUNDARIES);
20
+ const PLUGIN_WORKSPACE_AUTHORITY_KIND = "trusted-plugin-workspace";
21
+ const pluginWorkspaceAuthorities = new WeakMap();
17
22
 
18
23
  function invalidPolicy(message) {
19
24
  const error = new Error(message);
@@ -21,6 +26,100 @@ function invalidPolicy(message) {
21
26
  return error;
22
27
  }
23
28
 
29
+ function normalizedProvenance(value = {}) {
30
+ return Object.freeze({
31
+ origin: typeof value.origin === "string" ? value.origin : "",
32
+ pluginId: typeof value.pluginId === "string" ? value.pluginId : "",
33
+ pluginVersion:
34
+ typeof value.pluginVersion === "string" ? value.pluginVersion : "",
35
+ pluginSource:
36
+ typeof value.pluginSource === "string" ? value.pluginSource : "",
37
+ });
38
+ }
39
+
40
+ function sameProvenance(left, right) {
41
+ return (
42
+ left.origin === right.origin &&
43
+ left.pluginId === right.pluginId &&
44
+ left.pluginVersion === right.pluginVersion &&
45
+ left.pluginSource === right.pluginSource
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Mint an opaque, reusable authority from a plugin root produced by trusted
51
+ * installed-plugin discovery. Manifest/server/monitor data receives only the
52
+ * token: the canonical writable root stays in this module's WeakMap.
53
+ *
54
+ * A token is reusable because MCP reconnects and interval monitors can launch
55
+ * more than once. The process broker still mints a separate one-shot execution
56
+ * contract for every child.
57
+ */
58
+ export function issuePluginWorkspaceAuthority(value = {}) {
59
+ if (
60
+ typeof value.root !== "string" ||
61
+ value.root.length === 0 ||
62
+ value.root.includes("\0")
63
+ ) {
64
+ throw invalidPolicy("trusted plugin workspace root must be a path");
65
+ }
66
+ const requestedRoot = path.resolve(value.root);
67
+ let canonicalRoot;
68
+ let stat;
69
+ try {
70
+ const implementation = fs.realpathSync.native || fs.realpathSync;
71
+ canonicalRoot = implementation.call(fs.realpathSync, requestedRoot);
72
+ stat = fs.statSync(canonicalRoot);
73
+ } catch {
74
+ throw invalidPolicy("trusted plugin workspace root is unavailable");
75
+ }
76
+ if (!stat.isDirectory()) {
77
+ throw invalidPolicy("trusted plugin workspace root must be a directory");
78
+ }
79
+
80
+ const provenance = normalizedProvenance(value);
81
+ if (
82
+ !provenance.origin.startsWith("plugin:") ||
83
+ !provenance.pluginId ||
84
+ !provenance.pluginVersion
85
+ ) {
86
+ throw invalidPolicy("trusted plugin workspace provenance is incomplete");
87
+ }
88
+
89
+ const authority = Object.freeze({
90
+ contractVersion: 1,
91
+ kind: PLUGIN_WORKSPACE_AUTHORITY_KIND,
92
+ });
93
+ pluginWorkspaceAuthorities.set(
94
+ authority,
95
+ Object.freeze({ root: canonicalRoot, provenance }),
96
+ );
97
+ return authority;
98
+ }
99
+
100
+ /**
101
+ * Resolve an authority only for the exact plugin/component provenance to which
102
+ * the loader bound it. Structurally similar manifest objects are not accepted.
103
+ */
104
+ export function resolvePluginWorkspaceAuthority(authority, provenance = {}) {
105
+ if (
106
+ !authority ||
107
+ typeof authority !== "object" ||
108
+ authority.contractVersion !== 1 ||
109
+ authority.kind !== PLUGIN_WORKSPACE_AUTHORITY_KIND
110
+ ) {
111
+ return null;
112
+ }
113
+ const binding = pluginWorkspaceAuthorities.get(authority);
114
+ if (
115
+ !binding ||
116
+ !sameProvenance(binding.provenance, normalizedProvenance(provenance))
117
+ ) {
118
+ return null;
119
+ }
120
+ return binding.root;
121
+ }
122
+
24
123
  /**
25
124
  * @param {unknown} raw
26
125
  * @param {{ label?: string }} [opts]
@@ -49,10 +148,7 @@ export function normalizePluginSandboxPolicy(raw, opts = {}) {
49
148
 
50
149
  const requiredBoundaries = [];
51
150
  for (const boundary of boundaries) {
52
- if (
53
- typeof boundary !== "string" ||
54
- !SUPPORTED_BOUNDARIES.has(boundary)
55
- ) {
151
+ if (typeof boundary !== "string" || !SUPPORTED_BOUNDARIES.has(boundary)) {
56
152
  throw invalidPolicy(
57
153
  `${label}.requiredBoundaries contains unsupported boundary: ${String(boundary)}`,
58
154
  );