chainlesschain 0.162.181 → 0.162.183

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 (195) hide show
  1. package/package.json +2 -1
  2. package/scripts/build-windows-sandbox-helper.ps1 +163 -0
  3. package/src/assets/web-panel/assets/{AIOps-cq0dwbnf.js → AIOps-BapS7lz4.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CvcgGl3w.js → ActionButton-CXYdXEUI.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-hM2-jFqg.js → Analytics-CKR5Lexn.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-B9_Gsd_g.js → AppLayout-DATVUOXB.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-B7bu52Og.js → Artifacts-vb_0ZlV1.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DThfDgqB.js → Audit-BI1l9GWt.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DjlFiD4v.js → BackgroundAgents-BFXbaUEf.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CK9xJKnG.js → Backup-DEYBJJ6H.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DEiSU8K8.js → BaseInput-COdzeO0M.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CpT96czj.js → Chat-oSN6sfM5.js} +6 -6
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-CI_jT0ru.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-C1GfDnO0.js → Checkbox-BezEa4tm.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-3DAKgiLx.js → Codegen-CNEL0Ife.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-74cOwZU1.js → Col-Bf2Y2Bn1.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-tn9Kkz9j.js → Community-BfUlu4Hc.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BcoXH7m4.js → Compact-BN4xWP9e.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-DIPGUBgi.js → Compliance-Bt6BKmEP.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-doD5_91c.js → Cowork-DKLp8YxO.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-C-IDvubz.js → Cron-o7Hhw6ky.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CdcCoBdj.js → Crosschain-DaNXWJIw.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DdH5aMMA.js → DID-B-Qa2mha.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-DHLA4mm0.js → Dashboard-CMX0Qnxw.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-dEeCt7Md.js → Dropdown-BLuLyRu_.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-BMaecFv9.js → EmailListRenderer-CFBAeMij.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DUaUukTN.js → FamilyGuardDashboard-DWzXQ67V.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CMBE0_rp.js → Federation-DB1QSLgr.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-PG-JDbXq.js → FormItemContext-C-OKCToP.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-Bcb0p88m.js → GenericCardRenderer-DMJFtFZf.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Bgv15myB.js → Git-DsGSujOV.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-DB8YTArI.js → Governance-B0iS9NEW.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BQMwCWaS.js → Inference-CH004H7c.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-B1_ZziiB.js → KnowledgeGraph-Q9MTQ83c.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DovEEnt_.js → Logs-B_fyjZK9.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-D201OL4T.js → MarkdownRenderer-C0o4FtpD.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-BlCXSpyB.js → Marketplace-BIStF9d1.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-CsCR09Wj.js → McpTools-pGfal768.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-CBVRLKno.js → Memory-xbNqpIqB.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-C9OIafgA.js → MobileBridge-B5EQGrIK.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-vE-HdvLj.js → Mtc-a_rmfxJH.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-CPLaekCd.js → MtcAudit-BmKuofUr.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-C_UP9C2o.js → Multisig-BQbJ-CAN.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-2nODJcU1.js → NLProgramming-JtPJKX25.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-BhE-SJGu.js → Notes-ZFKznTSG.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-D21q7BNU.js → NotificationSettings-B6aHRBBR.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-0b62GYOD.js → OrderTableRenderer-DdcsOqEz.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-CEqmw_sb.js → Organization-BFDTAUgS.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-MW-gMDO_.js → Overflow-0FoTfbWH.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CY0hjI_G.js → P2P-CWd90etw.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BbWh7Man.js → PdhVaultBrowser-DtCGC-W-.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-ChqEA_4Q.js → Permissions-CvQ7nr7T.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-B3YrbYfD.js → PersonalDataHub-rdNPcR-Z.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-B8WBhhlW.js → Pipeline-CmrfiNOs.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-Cmb0PXNx.js → Privacy-D7ep1h_t.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-hF6zW3Nz.js → ProjectInit-C0xKINHS.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-BFSVfpW0.js → ProjectSettings-AOmZFp-F.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BWHSkXZP.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-B8yux8Dx.js → Providers-Cb7JCTPL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-N76cQluO.js → QrScannerModal-BDMehcXC.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-ClvZ0KYR.js → QuickAsk-BF6_1nvx.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-B-GfgBpT.js → Recommend-BHfT-A28.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-DJIZzQeG.js → Reputation-CsWHLEcj.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-COzxu_bb.js → Row-D6srnWhm.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CoYsJQuE.js → RssFeed-Xhr-KyVd.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DC77Mq9S.js → Search-vIbIJvwL.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Loy2EdMO.js → Security-Bzi5Aiko.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-GUD07Eu0.js → Services-BHtVX52P.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DWzkptYU.js → Skeleton-C0R8B2jf.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CEDn1r-Z.js → Skills-DtT6rWOX.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-ub_k4pXh.js → Sla-0B37mxVU.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-D7HLX9CA.js → SpeechSettings-0V2jE1Bs.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-DbwOvl6m.js → SyncSettings-CYedRzaj.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-BNVeIhud.js → Templates-BKoHztGT.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-PS9AzhXU.js → Tenant--17nwVNZ.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-Cgb4eLB_.js → Terminal-Dnkos7vW.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CrmXMOkg.js → TimelineRenderer-C5jx4AE3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DD-VDHw1.js → Tokens-DYJKYa9t.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-BsoN_n-n.js → Trigger-DChpWiQ9.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-n8LmN4ZV.js → Trust-DfWG52EA.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-C9_7MImA.js → UkeySign-CMugYcyA.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DyBteNgn.js → VideoEditing-BOSmrHAd.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-BS5ixeXI.js → Wallet-D5g3k3Sa.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-spc4UImU.js → WebAuthn-D-7u4Mkc.js} +3 -3
  88. package/src/assets/web-panel/assets/{WorkflowEditor-D7iVvaBO.js → WorkflowEditor-CFzZMSUJ.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-6CmBkHrL.js → chat-VbR6l6mc.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BGoCLkxI.js → colors-BSZn8EDS.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-fYZBetMO.js → compact-item-y74obMEq.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BeM-1UVj.js → createContext-C326bjex.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-CE7ZuEnV.js → hasIn-D_INJQT_.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-ukg99D1V.js → index-4PIeTM--.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B2U-ObMq.js → index-4eyH-eC2.js} +1 -1
  97. package/src/assets/web-panel/assets/index-5iAFwYx1.js +1 -0
  98. package/src/assets/web-panel/assets/{index-CRLjEP1H.js → index-9PIVuXqo.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BfPL-m46.js → index-B7SrC-On.js} +1 -1
  100. package/src/assets/web-panel/assets/{index--lap6cf7.js → index-BJi3UqBt.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B3CDgvNW.js → index-BY8-nCgs.js} +1 -1
  102. package/src/assets/web-panel/assets/index-BfnB7LPx.js +1 -0
  103. package/src/assets/web-panel/assets/{index-De0quFDw.js → index-Bfw2hf2s.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BunaQCa0.js → index-BjcWKg1S.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BsMfoaeo.js → index-BnSfnSMP.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C0v6YKKO.js → index-Bnua8IX3.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C4SJVjs4.js → index-C-ZCaUJc.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BNZDILcO.js → index-C2NyllnW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-YjcumOZ4.js → index-C327pLbw.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CRnUE8Cb.js → index-CMHqs3Jb.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CIpVrf5P.js → index-CfLJ42kv.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BEVm0lCS.js → index-Cr3uuz0C.js} +3 -3
  113. package/src/assets/web-panel/assets/{index-Dw1PyYPm.js → index-CsovTpx9.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-lJBjB1QX.js → index-CzE53HiB.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CpIGIB85.js → index-DBkzrpiL.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DoXk5KJ8.js → index-DNMxIsDg.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DsvfrESp.js → index-DUU2606o.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cip8emOQ.js → index-DdyjrCAu.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-ATcSr_2O.js → index-DkH1JvEt.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BV551878.js → index-Dlxh9D8D.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BU_2qiDk.js → index-DuIkfnTF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-zb7rVOhV.js → index-DwHSZLXg.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-MtOuq2i6.js → index-E_v1ZCTP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-B8BsdNV_.js → index-FQBsuExX.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-WUrJ6S1j.js → index-GA80ir_u.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-RMZ8a7Yt.js → index-IZAWt0H8.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D1r9tT3J.js → index-Iw0FfqUz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BowH1eLQ.js → index-KF9Zmqln.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Puq6Luf1.js → index-abmn_8Yz.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-QIpnpZgz.js → index-gINyI1rh.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C9Y3k0HI.js → index-jYaogFE8.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-B4wiTEOM.js → index-nSn2-C4N.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DT3zPSVX.js → index-qMA4GYdQ.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CYHMs1ag.js → initDefaultProps-DI0Zaf8-.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DDB2Ffk5.js → motion-BZDKSy3Z.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Bg3cD-dX.js → move-DsIW01Vp.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-B5nedjTS.js → mtc-parser-CohkDhsf.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BHyuUILu.js → omit-DpiN5rzg.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-C9YQnEN9.js → pickAttrs-qbuqni3y.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DBUFYUe7.js → placementArrow-mBky6714.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-nBs2Obnk.js → responsiveObserve-Bas3eQ2K.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DkCEaXNU.js → slide-CBx_SKoK.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-aqE1Wi6U.js → statusUtils-BlYguJSV.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-CN5bhfHB.js → styleChecker-BCYQmRGv.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-B_oX_kvJ.js → useFlexGapSupport-Dp5dybLM.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-rCuXHeme.js → useFs-DHAMBgA2.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-DZR9Abyd.js → usePersonalDataHub-W6jsSw76.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-ConCYQlX.js → vnode-BkI6aayc.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CUdDxG5M.js → zoom-C_IMGueq.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agenda.js +115 -3
  152. package/src/commands/agent.js +15 -0
  153. package/src/commands/background-session.js +49 -0
  154. package/src/commands/plugin.js +235 -23
  155. package/src/commands/session.js +13 -0
  156. package/src/data/changelog.json +26 -0
  157. package/src/gateways/terminal/PtyManager.js +45 -4
  158. package/src/gateways/ws/ws-server.js +10 -1
  159. package/src/harness/background-task-manager.js +70 -2
  160. package/src/harness/jsonl-session-store.js +6 -4
  161. package/src/harness/worktree-isolator.js +14 -3
  162. package/src/lib/agent-core.js +1 -0
  163. package/src/lib/agent-schedule-store.js +45 -22
  164. package/src/lib/agent-team/team-runner.js +19 -1
  165. package/src/lib/background-agent-supervisor.js +49 -0
  166. package/src/lib/hook-runner.js +6 -2
  167. package/src/lib/packer/pkg-config-generator.js +3 -1
  168. package/src/lib/plugin-runtime/bin.js +1148 -70
  169. package/src/lib/plugin-runtime/install.js +226 -7
  170. package/src/lib/plugin-runtime/manifest.js +68 -12
  171. package/src/lib/plugin-runtime/scopes.js +33 -12
  172. package/src/lib/plugin-usage-attribution.js +44 -0
  173. package/src/lib/process-execution-broker/index.js +626 -48
  174. package/src/lib/process-execution-broker/platform-sandbox.js +4971 -266
  175. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox.cs +3856 -0
  178. package/src/lib/repl-bang-memorize.js +67 -10
  179. package/src/lib/session-usage.js +77 -0
  180. package/src/lib/slash-commands.js +27 -4
  181. package/src/repl/agent-repl.js +10 -9
  182. package/src/runtime/agent-core.js +376 -52
  183. package/src/runtime/agent-runtime.js +13 -1
  184. package/src/runtime/headless-runner.js +13 -4
  185. package/src/runtime/headless-stream.js +20 -1
  186. package/src/runtime/mcp-config.js +15 -5
  187. package/src/assets/web-panel/assets/ChatBubbleRenderer-BKPPNmZI.js +0 -1
  188. package/src/assets/web-panel/assets/MobileProjects-D_zoJ8Xs.js +0 -1
  189. package/src/assets/web-panel/assets/Projects-DAWkVFkc.js +0 -1
  190. package/src/assets/web-panel/assets/RemoteSession-mlglCdQi.js +0 -5
  191. package/src/assets/web-panel/assets/Tasks-CBQzoAu5.js +0 -1
  192. package/src/assets/web-panel/assets/devWarning-BzPwHnw8.js +0 -1
  193. package/src/assets/web-panel/assets/index-DacrAdc-.js +0 -1
  194. package/src/assets/web-panel/assets/index-EHzNJozo.js +0 -1
  195. package/src/lib/process-execution-broker/windows-sandbox.ps1 +0 -999
@@ -757,6 +757,12 @@ export function registerAgentCommand(program) {
757
757
  process.stderr.write(
758
758
  "--permission-prompt-tool requires --mcp-config (the tool must come from a loaded MCP server).\n",
759
759
  );
760
+ // Complete worktree cleanup while the async command action is still
761
+ // running. Windows hosted runners do not reliably permit the exit
762
+ // handler to start the synchronous git subprocesses needed to remove
763
+ // the linked worktree, so the exit hook remains only a last-resort
764
+ // fallback for unexpected termination paths.
765
+ await _finishWorktree();
760
766
  process.exit(1);
761
767
  }
762
768
 
@@ -1125,6 +1131,15 @@ export function registerAgentCommand(program) {
1125
1131
  title: prompt.slice(0, 100),
1126
1132
  followUpArgv,
1127
1133
  worktree: _worktree,
1134
+ governance: {
1135
+ permissionMode: options.permissionMode || "default",
1136
+ resourceBudget: {
1137
+ maxTurns: options.maxTurns
1138
+ ? parseInt(options.maxTurns, 10)
1139
+ : null,
1140
+ maxCostUsd,
1141
+ },
1142
+ },
1128
1143
  });
1129
1144
  if (_worktree) {
1130
1145
  // Ownership moves to the persisted background session only after
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import chalk from "chalk";
3
3
  import { logger } from "../lib/logger.js";
4
4
  import { readPrLinkLedger } from "../lib/pr-link-ledger.js";
5
+ import { loadSideEffectLedger } from "../lib/side-effect-ledger-store.js";
5
6
  import executionBroker from "../lib/process-execution-broker/index.js";
6
7
 
7
8
  function formatAge(ms) {
@@ -74,6 +75,21 @@ export function formatBackgroundAgentDetails(
74
75
  lines.push(` branch: ${session.branch || "-"}`);
75
76
  lines.push(` baseSha: ${session.baseSha || "-"}`);
76
77
  }
78
+ if (session.governance) {
79
+ const governance = session.governance;
80
+ const budget = governance.resourceBudget || {};
81
+ lines.push(` owner: ${governance.owner || "-"}`);
82
+ lines.push(` permissionMode: ${governance.permissionMode || "default"}`);
83
+ lines.push(
84
+ ` budget: turns=${budget.maxTurns || "unlimited"} costUsd=${budget.maxCostUsd || "unlimited"}`,
85
+ );
86
+ }
87
+ if (session.sideEffects) {
88
+ const effects = session.sideEffects;
89
+ lines.push(
90
+ ` sideEffects: total=${effects.total || 0} unsettled=${effects.unsettled || 0} unknown=${effects.unknown || 0}`,
91
+ );
92
+ }
77
93
  if (session.phase) lines.push(` phase: ${session.phase}`);
78
94
  if (session.pr?.number) {
79
95
  lines.push(
@@ -101,6 +117,27 @@ export function formatBackgroundAgentDetails(
101
117
  return lines.join("\n");
102
118
  }
103
119
 
120
+ export function summarizeSideEffects(ledger) {
121
+ const ops =
122
+ ledger && typeof ledger.list === "function"
123
+ ? ledger.list()
124
+ : Array.isArray(ledger?.ops)
125
+ ? ledger.ops
126
+ : [];
127
+ const count = (state) => ops.filter((op) => op?.state === state).length;
128
+ return {
129
+ total: ops.length,
130
+ prepared: count("prepared"),
131
+ started: count("started"),
132
+ committed: count("committed"),
133
+ failed: count("failed"),
134
+ unknown: count("unknown"),
135
+ unsettled: ops.filter(
136
+ (op) => op?.state === "prepared" || op?.state === "started",
137
+ ).length,
138
+ };
139
+ }
140
+
104
141
  function parseLines(value, fallback) {
105
142
  const n = Number(value);
106
143
  return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
@@ -447,6 +484,18 @@ async function runDashboardView(options = {}) {
447
484
  } catch {
448
485
  /* PR decoration is cosmetic */
449
486
  }
487
+ for (const s of sessions) {
488
+ if (!s.sessionId) continue;
489
+ try {
490
+ s.sideEffects = summarizeSideEffects(
491
+ loadSideEffectLedger(s.sessionId, {
492
+ failIfUnavailable: false,
493
+ }),
494
+ );
495
+ } catch {
496
+ /* side-effect decoration is cosmetic */
497
+ }
498
+ }
450
499
  return sessions;
451
500
  };
452
501
 
@@ -20,10 +20,8 @@ import {
20
20
  removePlugin,
21
21
  updatePlugin,
22
22
  getPluginSettings,
23
- setPluginSetting,
24
23
  searchRegistry,
25
24
  listRegistry,
26
- registerInMarketplace,
27
25
  getPluginSummary,
28
26
  installPluginSkills,
29
27
  removePluginSkills,
@@ -404,8 +402,28 @@ export function registerPluginCommand(program) {
404
402
  .command("enable")
405
403
  .description("Enable a plugin")
406
404
  .argument("<name>", "Plugin name")
407
- .action(async (name) => {
405
+ .option(
406
+ "--scope <scope>",
407
+ "Enable the unified-runtime install at this scope (user|project|local)",
408
+ )
409
+ .option("--json", "Output unified-runtime result as JSON")
410
+ .action(async (name, options) => {
408
411
  try {
412
+ if (options.scope) {
413
+ const { setPluginEnabled } =
414
+ await import("../lib/plugin-runtime/install.js");
415
+ const result = setPluginEnabled(name, true, {
416
+ scope: options.scope,
417
+ cwd: process.cwd(),
418
+ });
419
+ if (options.json) console.log(JSON.stringify(result, null, 2));
420
+ else {
421
+ logger.success(
422
+ `Enabled ${name} (${options.scope} scope). Reload active plugin sessions to apply the change.`,
423
+ );
424
+ }
425
+ return;
426
+ }
409
427
  enforcePluginPolicy(
410
428
  { name, action: "enable" },
411
429
  loadPluginManagedPolicy(),
@@ -436,8 +454,28 @@ export function registerPluginCommand(program) {
436
454
  .command("disable")
437
455
  .description("Disable a plugin")
438
456
  .argument("<name>", "Plugin name")
439
- .action(async (name) => {
457
+ .option(
458
+ "--scope <scope>",
459
+ "Disable the unified-runtime install at this scope (user|project|local)",
460
+ )
461
+ .option("--json", "Output unified-runtime result as JSON")
462
+ .action(async (name, options) => {
440
463
  try {
464
+ if (options.scope) {
465
+ const { setPluginEnabled } =
466
+ await import("../lib/plugin-runtime/install.js");
467
+ const result = setPluginEnabled(name, false, {
468
+ scope: options.scope,
469
+ cwd: process.cwd(),
470
+ });
471
+ if (options.json) console.log(JSON.stringify(result, null, 2));
472
+ else {
473
+ logger.success(
474
+ `Disabled ${name} (${options.scope} scope). Reload active plugin sessions to apply the change.`,
475
+ );
476
+ }
477
+ return;
478
+ }
441
479
  const ctx = await bootstrap({ verbose: program.opts().verbose });
442
480
  if (!ctx.db) {
443
481
  logger.error("Database not available");
@@ -860,6 +898,7 @@ export function registerPluginCommand(program) {
860
898
  // plugin name) resolves to a git source the installer already handles.
861
899
  let installSource = source;
862
900
  let integritySha = null;
901
+ let sourceMetadata = null;
863
902
  const { isRemoteSource, resolveRemoteSource } =
864
903
  await import("../lib/plugin-runtime/remote-source.js");
865
904
  if (options.registry || isRemoteSource(source)) {
@@ -883,6 +922,19 @@ export function registerPluginCommand(program) {
883
922
  });
884
923
  installSource = resolved.source;
885
924
  integritySha = resolved.sha256;
925
+ sourceMetadata = {
926
+ type: "registry",
927
+ source: url,
928
+ registry: url,
929
+ package: name || source,
930
+ resolvedSource: resolved.source,
931
+ ref:
932
+ (typeof resolved.source === "string" &&
933
+ resolved.source.includes("#") &&
934
+ resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
935
+ null,
936
+ offline: resolved.fromCache === true,
937
+ };
886
938
  if (resolved.fromCache) {
887
939
  logger.warn(
888
940
  chalk.yellow(
@@ -897,13 +949,31 @@ export function registerPluginCommand(program) {
897
949
  }
898
950
  }
899
951
 
952
+ let managed = null;
953
+ try {
954
+ managed = loadPluginManagedPolicy();
955
+ } catch (err) {
956
+ logger.error(`Managed plugin policy is invalid: ${err.message}`);
957
+ process.exitCode = 1;
958
+ return;
959
+ }
960
+ const requiresManagedSignature =
961
+ managed?.requireSignedPlugins === true ||
962
+ managed?.requireSignedPlugins === "require";
900
963
  const signature =
901
- options.sha256 || options.signature || options.publicKey || integritySha
964
+ options.sha256 ||
965
+ options.signature ||
966
+ options.publicKey ||
967
+ integritySha ||
968
+ requiresManagedSignature
902
969
  ? {
903
970
  sha256: options.sha256 || integritySha,
904
971
  signatureFile: options.signature,
905
972
  publicKeyFile: options.publicKey,
906
- requireSignature: Boolean(options.signature),
973
+ requireSignature:
974
+ Boolean(options.signature) || requiresManagedSignature,
975
+ trustedKeySha256: managed?.trustedPluginKeySha256 || null,
976
+ requireTrustedKey: requiresManagedSignature,
907
977
  }
908
978
  : null;
909
979
  try {
@@ -912,6 +982,9 @@ export function registerPluginCommand(program) {
912
982
  cwd: process.cwd(),
913
983
  force: options.force === true,
914
984
  signature,
985
+ sourceMetadata,
986
+ managedPolicy: managed,
987
+ policySource: options.registry || source,
915
988
  });
916
989
  const capNotice = await resolvePluginCapabilityNotice(
917
990
  res.name,
@@ -1060,8 +1133,21 @@ export function registerPluginCommand(program) {
1060
1133
  const trust = isPluginTrusted(r)
1061
1134
  ? chalk.green("trusted")
1062
1135
  : chalk.yellow("untrusted");
1136
+ const state =
1137
+ r.enabled === false
1138
+ ? chalk.yellow("disabled")
1139
+ : chalk.green("enabled");
1140
+ const signed = r.integrity?.signature?.verified
1141
+ ? chalk.green("signed")
1142
+ : chalk.gray("unsigned");
1143
+ const policy =
1144
+ r.policy?.allowed === false
1145
+ ? chalk.red("policy-blocked")
1146
+ : r.policy?.managed
1147
+ ? chalk.cyan("managed")
1148
+ : "";
1063
1149
  logger.log(
1064
- ` ${ok} ${chalk.cyan(r.name)} v${r.version} ${chalk.gray(`[${r.scope}]`)} ${trust}`,
1150
+ ` ${ok} ${chalk.cyan(r.name)} v${r.version} ${chalk.gray(`[${r.scope}]`)} ${state} ${trust} ${signed}${policy ? ` ${policy}` : ""}`,
1065
1151
  );
1066
1152
  }
1067
1153
  });
@@ -1456,22 +1542,162 @@ export function registerPluginCommand(program) {
1456
1542
  plugin
1457
1543
  .command("upgrade <source>")
1458
1544
  .description(
1459
- "Update a runtime plugin from its source (local dir or git); repoints .active",
1545
+ "Update a runtime plugin from its pinned local, git, or registry source; repoints .active",
1460
1546
  )
1461
1547
  .option("--scope <scope>", `Scope to update in (${SCOPES})`, "user")
1462
1548
  .option("--force", "Reinstall even if the version is unchanged")
1549
+ .option("--sha256 <hex>", "Expected SHA-256 of the manifest file")
1550
+ .option("--signature <path>", "Detached Ed25519 signature of the manifest")
1551
+ .option("--public-key <path>", "Public key for signature verification")
1552
+ .option(
1553
+ "--registry <url>",
1554
+ "Resolve <source> as a plugin NAME in this registry URL",
1555
+ )
1556
+ .option(
1557
+ "--name <plugin>",
1558
+ "Plugin name to select from a multi-plugin registry",
1559
+ )
1560
+ .option("--token <token>", "Bearer token for a private registry")
1561
+ .option(
1562
+ "--allow-insecure-registry",
1563
+ "Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
1564
+ )
1463
1565
  .option(
1464
1566
  "--grant-capabilities",
1465
1567
  "Grant any newly declared capabilities during the upgrade (no separate consent step)",
1466
1568
  )
1569
+ .option("--json", "Output as JSON")
1467
1570
  .action(async (source, options) => {
1468
1571
  const { updatePlugin } = await import("../lib/plugin-runtime/install.js");
1572
+ let installSource = source;
1573
+ let integritySha = null;
1574
+ let sourceMetadata = null;
1575
+ const { isRemoteSource, resolveRemoteSource } =
1576
+ await import("../lib/plugin-runtime/remote-source.js");
1577
+ if (options.registry || isRemoteSource(source)) {
1578
+ const url = options.registry || source;
1579
+ const name = options.registry ? source : options.name;
1580
+ let config = null;
1581
+ try {
1582
+ const cm = await import("../lib/config-manager.js");
1583
+ config = cm.loadConfig();
1584
+ } catch {
1585
+ config = null;
1586
+ }
1587
+ try {
1588
+ const resolved = await resolveRemoteSource(url, {
1589
+ name,
1590
+ token: options.token,
1591
+ config,
1592
+ allowInsecure: options.allowInsecureRegistry === true,
1593
+ });
1594
+ installSource = resolved.source;
1595
+ integritySha = resolved.sha256;
1596
+ sourceMetadata = {
1597
+ type: "registry",
1598
+ source: url,
1599
+ registry: url,
1600
+ package: name || source,
1601
+ resolvedSource: resolved.source,
1602
+ ref:
1603
+ (typeof resolved.source === "string" &&
1604
+ resolved.source.includes("#") &&
1605
+ resolved.source.slice(resolved.source.indexOf("#") + 1)) ||
1606
+ null,
1607
+ offline: resolved.fromCache === true,
1608
+ };
1609
+ if (resolved.fromCache && !options.json) {
1610
+ logger.warn(
1611
+ chalk.yellow(
1612
+ " ⚠ registry unreachable — using cached copy (offline)",
1613
+ ),
1614
+ );
1615
+ }
1616
+ } catch (err) {
1617
+ logger.error(`Registry resolution failed: ${err.message}`);
1618
+ process.exitCode = 1;
1619
+ return;
1620
+ }
1621
+ }
1622
+
1623
+ let managed = null;
1469
1624
  try {
1470
- const res = updatePlugin(source, {
1625
+ managed = loadPluginManagedPolicy();
1626
+ } catch (err) {
1627
+ logger.error(`Managed plugin policy is invalid: ${err.message}`);
1628
+ process.exitCode = 1;
1629
+ return;
1630
+ }
1631
+ const requiresManagedSignature =
1632
+ managed?.requireSignedPlugins === true ||
1633
+ managed?.requireSignedPlugins === "require";
1634
+ const signature =
1635
+ options.sha256 ||
1636
+ options.signature ||
1637
+ options.publicKey ||
1638
+ integritySha ||
1639
+ requiresManagedSignature
1640
+ ? {
1641
+ sha256: options.sha256 || integritySha,
1642
+ signatureFile: options.signature,
1643
+ publicKeyFile: options.publicKey,
1644
+ requireSignature:
1645
+ Boolean(options.signature) || requiresManagedSignature,
1646
+ trustedKeySha256: managed?.trustedPluginKeySha256 || null,
1647
+ requireTrustedKey: requiresManagedSignature,
1648
+ }
1649
+ : null;
1650
+ try {
1651
+ const res = updatePlugin(installSource, {
1471
1652
  scope: options.scope,
1472
1653
  cwd: process.cwd(),
1473
1654
  force: options.force,
1655
+ signature,
1656
+ sourceMetadata,
1657
+ managedPolicy: managed,
1658
+ policySource: options.registry || source,
1474
1659
  });
1660
+ const upgradeNotice = await resolvePluginCapabilityNotice(
1661
+ res.name,
1662
+ options.scope,
1663
+ process.cwd(),
1664
+ );
1665
+ if (res.updated || res.reinstalled)
1666
+ await auditPluginInstall(
1667
+ "upgrade",
1668
+ { ...res, scope: options.scope },
1669
+ installSource,
1670
+ upgradeNotice,
1671
+ );
1672
+
1673
+ if (options.json) {
1674
+ let capabilitiesGranted = false;
1675
+ if (
1676
+ options.grantCapabilities &&
1677
+ upgradeNotice &&
1678
+ !upgradeNotice.consented
1679
+ ) {
1680
+ capabilitiesGranted = await grantInstalledPluginCapabilities(
1681
+ res.name,
1682
+ options.scope,
1683
+ process.cwd(),
1684
+ );
1685
+ }
1686
+ console.log(
1687
+ JSON.stringify(
1688
+ {
1689
+ ...res,
1690
+ scope: options.scope,
1691
+ capabilities: upgradeNotice,
1692
+ capabilitiesGranted,
1693
+ },
1694
+ null,
1695
+ 2,
1696
+ ),
1697
+ );
1698
+ return;
1699
+ }
1700
+
1475
1701
  if (res.updated) {
1476
1702
  logger.success(
1477
1703
  `Updated ${res.name}: ${res.previousVersion ? `v${res.previousVersion} → ` : ""}v${res.version} (${options.scope} scope)`,
@@ -1489,20 +1715,6 @@ export function registerPluginCommand(program) {
1489
1715
  // upgrade that adds a capability shows a `⚠ capability consent required`
1490
1716
  // notice diffed against the prior consent, then either auto-grants
1491
1717
  // (--grant-capabilities) or blocks interactively to re-consent now.
1492
- const upgradeNotice = await resolvePluginCapabilityNotice(
1493
- res.name,
1494
- options.scope,
1495
- process.cwd(),
1496
- );
1497
- // Only a version that actually landed on disk is an install; an
1498
- // already-up-to-date no-op fetched nothing new to run.
1499
- if (res.updated || res.reinstalled)
1500
- await auditPluginInstall(
1501
- "upgrade",
1502
- { ...res, scope: options.scope },
1503
- source,
1504
- upgradeNotice,
1505
- );
1506
1718
  await applyCapabilityConsentGate(
1507
1719
  res.name,
1508
1720
  options.scope,
@@ -1333,6 +1333,7 @@ export function registerSessionCommand(program) {
1333
1333
  "subagent",
1334
1334
  "tool",
1335
1335
  "mcp",
1336
+ "plugin",
1336
1337
  "model",
1337
1338
  ];
1338
1339
 
@@ -1403,6 +1404,18 @@ export function registerSessionCommand(program) {
1403
1404
  );
1404
1405
  return;
1405
1406
  }
1407
+ case "plugin": {
1408
+ const rows = a.tools?.byPlugin || [];
1409
+ if (rows.length === 0) return empty("plugin-attributed tool calls");
1410
+ for (const r of rows)
1411
+ toolRow(`${r.plugin}${r.version ? `@${r.version}` : ""}`, r);
1412
+ logger.log(
1413
+ chalk.gray(
1414
+ " turnTokens = tokens of turns that used the plugin (approximation; do not sum across rows)",
1415
+ ),
1416
+ );
1417
+ return;
1418
+ }
1406
1419
  default:
1407
1420
  break;
1408
1421
  }
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "source": "CHANGELOG.md",
3
3
  "releases": [
4
+ {
5
+ "productVersion": null,
6
+ "cliVersion": "0.162.183",
7
+ "cliVersions": ["0.4.74", "0.37.35", "0.162.182", "0.162.183"],
8
+ "date": "2026-07-27",
9
+ "title": "",
10
+ "sections": [
11
+ {
12
+ "heading": "Added — cc CLI 0.162.183 + IDE quality, plugin, and worktree governance",
13
+ "body": "> `chainlesschain` **0.162.182 → 0.162.183**, VS Code\n> **0.37.34 → 0.37.35**, and JetBrains **0.4.73 → 0.4.74** (2026-07-27).\n> `@chainlesschain/personal-data-hub` remains **0.4.57** and\n> `@chainlesschain/agent-sdk` remains **0.1.7**.\n\n- **IDE-native quality context**: both IDE bridges negotiate bounded\n `cc-ide-quality/v1` test-result, coverage, and debugger snapshots with\n Context v2 freshness metadata; VS Code notebook execution now carries its\n real notebook context.\n- **Installation Doctor depth**: VS Code and JetBrains report Node/Java\n availability plus managed-CLI and plugin-registry offline recovery state,\n without probing executables from an untrusted workspace directory.\n- **Plugin lifecycle and supply-chain UI**: scoped enable/disable, source-aware\n upgrades, live-session reload, signature/SBOM summaries, managed-policy\n provenance, and sanitized registry/Git/local source metadata stay governed\n by the CLI runtime.\n- **Plugin usage attribution**: compact transcripts and usage reports now\n attribute plugin-bin and plugin-provided MCP calls by plugin/version without\n persisting tool arguments.\n- **Supervised worktree tasks**: IDE-created isolated tasks run as background\n worktree agents and display canonical lifecycle, owner/session, permission\n mode, resource budget, and bounded side-effect counts. Team and batch rows\n remain explicitly unmanaged until they expose equivalent durable state.\n- **Release validation**: publication remains gated on the exact release\n commit passing CLI CI, CLI Strict Sandbox, background reconnect E2E, packaged\n VSIX Extension Host gates, JetBrains unit/build/Remote Robot gates, and\n public marketplace readback."
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "productVersion": null,
19
+ "cliVersion": "0.162.182",
20
+ "cliVersions": ["0.162.181", "0.162.182"],
21
+ "date": "2026-07-27",
22
+ "title": "",
23
+ "sections": [
24
+ {
25
+ "heading": "Fixed — cc CLI 0.162.182: attested native plugin sandbox closure",
26
+ "body": "> `chainlesschain` **0.162.181 → 0.162.182** (2026-07-27).\n> CLI-only release; `@chainlesschain/personal-data-hub` remains **0.4.57**\n> and `@chainlesschain/agent-sdk` remains **0.1.7**.\n\n- **One-shot native plugin execution contracts**: policy-bearing native plugin\n bins must preserve the resolver-issued target, literal argv, plugin root, and\n executable identity through the process broker. Replayed, copied, stale, or\n asynchronous contracts fail closed before a target process is started.\n- **Linux strong boundary completion**: strict native launches accept only\n attested static ELF executables for the current architecture, reject dynamic\n loaders and malformed program headers, pin the plugin tree through file\n descriptors, and enter the fixed bubblewrap filesystem/network policy\n without exposing a mutable host path.\n- **Cross-platform identity hardening**: Windows AppContainer launches retain\n attested target handles, while platform adapters revalidate executable and\n plugin identities across probe, spawn, IPC, and detached launch boundaries.\n- **Release validation gate**: the exact release commit must pass `CLI CI` and\n `CLI Strict Sandbox` on their configured Ubuntu, Windows, and macOS runners;\n local tests are supplementary and cannot authorize publication."
27
+ }
28
+ ]
29
+ },
4
30
  {
5
31
  "productVersion": null,
6
32
  "cliVersion": "0.162.181",
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * PtyManager — ESM mirror of `desktop-app-vue/src/main/terminal/PtyManager.js`.
3
3
  *
4
- * Keep both copies in sync. The desktop copy targets Electron's CJS main
5
- * process; this one targets `cc ui` (Node ESM) so `cc ui` users also get
6
- * remote-terminal support via the same WS protocol.
4
+ * Keep their protocol and session behavior in sync. The desktop copy targets
5
+ * Electron's CJS main process; this one targets `cc ui` (Node ESM), whose host
6
+ * injects its own trusted workspace-policy resolver.
7
7
  *
8
8
  * Design notes — see docs/design/Android_Remote_Terminal_Plan_A.md §4:
9
9
  * - node-pty loaded lazily so the module loads without the native
@@ -44,6 +44,24 @@ function resolveShellCmd(shell) {
44
44
  return shell;
45
45
  }
46
46
 
47
+ function validatePinnedSandboxPolicy(policy) {
48
+ if (policy == null) return null;
49
+ if (
50
+ typeof policy !== "object" ||
51
+ Array.isArray(policy) ||
52
+ typeof policy.then === "function" ||
53
+ !Array.isArray(policy.requiredBoundaries) ||
54
+ policy.requiredBoundaries.length === 0 ||
55
+ !Object.isFrozen(policy) ||
56
+ !Object.isFrozen(policy.requiredBoundaries)
57
+ ) {
58
+ const error = new TypeError("invalid_pty_sandbox_policy");
59
+ error.code = "ERR_PTY_SANDBOX_POLICY_INVALID";
60
+ throw error;
61
+ }
62
+ return policy;
63
+ }
64
+
47
65
  class PtySession {
48
66
  constructor({ id, shell, cwd, cols, rows, proc, createdAt, ringBuffer }) {
49
67
  this.id = id;
@@ -65,6 +83,16 @@ export class PtyManager extends EventEmitter {
65
83
  constructor(opts = {}) {
66
84
  super();
67
85
  this.config = { ...DEFAULT_CONFIG, ...(opts.config || {}) };
86
+ const resolveSandboxPolicy =
87
+ opts.resolveSandboxPolicy ?? opts._deps?.resolveSandboxPolicy ?? null;
88
+ if (
89
+ resolveSandboxPolicy !== null &&
90
+ typeof resolveSandboxPolicy !== "function"
91
+ ) {
92
+ throw new TypeError("resolveSandboxPolicy must be a function or null");
93
+ }
94
+ this._policyCwd = opts.policyCwd || process.cwd();
95
+ this._resolveSandboxPolicy = resolveSandboxPolicy;
68
96
  this._deps = {
69
97
  loadNodePty: opts._deps?.loadNodePty || (() => require("node-pty")),
70
98
  spawnPty: opts._deps?.spawnPty || null,
@@ -92,6 +120,19 @@ export class PtyManager extends EventEmitter {
92
120
  throw new Error("shell_not_allowed");
93
121
  }
94
122
 
123
+ const cwd = req.cwd || this.config.defaultCwd || process.cwd();
124
+ // A remote cwd may discover a stricter policy, but it must never replace
125
+ // the fixed workspace root supplied by the host. Resolve and validate the
126
+ // pinned policy before loading node-pty or allocating a native terminal.
127
+ const sandboxPolicy = this._resolveSandboxPolicy
128
+ ? validatePinnedSandboxPolicy(
129
+ this._resolveSandboxPolicy({
130
+ workspaceCwd: this._policyCwd,
131
+ executionCwd: cwd,
132
+ }),
133
+ )
134
+ : null;
135
+
95
136
  let pty;
96
137
  try {
97
138
  pty = this._deps.loadNodePty();
@@ -101,7 +142,6 @@ export class PtyManager extends EventEmitter {
101
142
  throw err;
102
143
  }
103
144
 
104
- const cwd = req.cwd || this.config.defaultCwd || process.cwd();
105
145
  const cols = Number.isFinite(req.cols) ? req.cols : 80;
106
146
  const rows = Number.isFinite(req.rows) ? req.rows : 24;
107
147
  const cmd = resolveShellCmd(shell);
@@ -123,6 +163,7 @@ export class PtyManager extends EventEmitter {
123
163
  origin: "terminal:pty",
124
164
  policy: "allow",
125
165
  scope: "terminal",
166
+ ...(sandboxPolicy ? { sandboxPolicy } : {}),
126
167
  });
127
168
 
128
169
  const sessionId = randomUUID();
@@ -114,6 +114,7 @@ import {
114
114
  handleArtifactShow,
115
115
  } from "./artifact-protocol.js";
116
116
  import { executionBroker } from "../../lib/process-execution-broker/index.js";
117
+ import { collectWorkspacePluginBinSandboxPolicy } from "../../lib/plugin-runtime/bin.js";
117
118
 
118
119
  export const _deps = {
119
120
  spawn: (...args) => executionBroker.spawn(...args),
@@ -1215,7 +1216,15 @@ export class ChainlessChainWSServer extends EventEmitter {
1215
1216
  if (this._taskManager) return this._taskManager;
1216
1217
  const { BackgroundTaskManager } =
1217
1218
  await import("../../harness/background-task-manager.js");
1218
- this._taskManager = new BackgroundTaskManager({ recoverOnStart: true });
1219
+ this._taskManager = new BackgroundTaskManager({
1220
+ recoverOnStart: true,
1221
+ policyCwd: this.projectRoot,
1222
+ resolveSandboxPolicy: ({ workspaceCwd, executionCwd }) =>
1223
+ collectWorkspacePluginBinSandboxPolicy({
1224
+ workspaceCwd,
1225
+ executionCwd,
1226
+ }),
1227
+ });
1219
1228
  this._subscribeTaskNotifications();
1220
1229
  return this._taskManager;
1221
1230
  }