chainlesschain 0.162.180 → 0.162.182

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 (263) hide show
  1. package/package.json +7 -3
  2. package/scripts/build-windows-sandbox-helper.ps1 +163 -0
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D_TIL54O.js → AIOps-DzZ5_xoj.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-eQwy9r-f.js → ActionButton-DeLXddIg.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-D2eNuDgT.js → Analytics-rvDCmet1.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CFhwOFsf.js → AppLayout-Yt6QVDnp.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-CJUMw6PB.js → Artifacts-BS112K5L.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-CDB9V58o.js → Audit-0wJuQrNu.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-TBppYFjN.js → BackgroundAgents-B90PMgbn.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-CYEOUpzJ.js → Backup-TkgMglQQ.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C88fFT2u.js → BaseInput-CQVS9p1l.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-mkr1vC3N.js → Chat-YTjQHoC7.js} +6 -6
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-CVU4xqti.js → Checkbox-5bwaOabq.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-rrNcByz3.js → Codegen-BFmNQ_Xe.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-Cqu-zCvh.js → Col-BT_u4Dzp.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-RGRlSgXV.js → Community-CJP52u5b.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DBm6epOK.js → Compact-BpzhJE9i.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-BgKw5iaW.js → Compliance-Cg7rfslp.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-BqQcNO3Z.js → Cowork-JisxL6Yy.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DO5gTFNb.js → Cron-Bu7Ug_MC.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-B8hbJNuN.js → Crosschain-CvuqoLdj.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-B08bFJ2C.js → DID-C2u9G8Pu.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-xNdy_ifo.js → Dashboard-BFt5Yv3d.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D-KIZgWL.js → Dropdown-DIaT9Wxq.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-DKCvsaGo.js → EmailListRenderer-D_A601Y4.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D_cfqama.js → FamilyGuardDashboard--IyQ8I8w.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-B-UqQD68.js → Federation-BihWHxuY.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-BWWvWuJW.js → FormItemContext-BB8dOXc_.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-CPchx6N7.js → GenericCardRenderer-DQYhj1HH.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-Vx4co9Ff.js → Git-B-2QZxGf.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-D4x0pp_m.js → Governance-CusSb-nG.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-T3KvwPYR.js → Inference-BAjmvPPL.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-Ddv3KUv3.js → KnowledgeGraph-DoNYWKYq.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DfH-Dkae.js → Logs-B8fKWD-n.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DHW3PbCq.js → MarkdownRenderer-BlJm1J7E.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-tjgb0R3D.js → Marketplace-CyOscbSL.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DHAk5nYX.js → McpTools-Ex6Om5da.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-A2Yx7thM.js → Memory-CDcD8l8r.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-BEmTVfN5.js → MobileBridge-xfBqoFDg.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-DuFHUgE4.js → MobileProjects-BwObMMA2.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-BDSll0OZ.js → Mtc-CIWd36OV.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-CuBvluaS.js → MtcAudit-CPX0ouuA.js} +4 -4
  45. package/src/assets/web-panel/assets/{Multisig-BmXAA-Sb.js → Multisig-BDJsIUuW.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-ci2stZAe.js → NLProgramming-DEkbuD3N.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-D-1RpqnS.js → Notes-DJpHRhH1.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-C0GyitIT.js → NotificationSettings-C1UZ-Qd5.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-U2bN-hj5.js → OrderTableRenderer-GlJtqLjT.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-BNZgpPci.js → Organization-Bbhj0fac.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-CDidKmUN.js → Overflow-1H04x3kx.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BnreBaXq.js → P2P-CDpPe3et.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-DwJUxO9j.js → PdhVaultBrowser-MUX0Bpvw.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-BQGgJPH0.js → Permissions-A07-0Ci1.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-k_fSkCwL.js → PersonalDataHub-DhhDWxAZ.js} +5 -5
  56. package/src/assets/web-panel/assets/{Pipeline-WGqKMqau.js → Pipeline-CyfbJfe-.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-DtHj9Ymq.js → Privacy-CNFps3ED.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-B3wH2SFC.js → ProjectInit--T-u3Dr5.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Cd0cgcA1.js → ProjectSettings-DJmrK2fC.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DTpxEXur.js → Providers-DLzzbOZd.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-B2v6gR-C.js → QrScannerModal-CexOomKS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-D3Nr9xXR.js → QuickAsk-QklGi19_.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-C9inoMky.js → Recommend-Bb9kwSw8.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-DZuVNNN5.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-41KFG646.js → Reputation-Dr5cLP0N.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DqAaqqCv.js → Row-oCQHYv5t.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-CMDpCDKc.js → RssFeed-IFDEykIR.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-B6mtegNL.js → Search-CspVIxDc.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-BaePFVZ8.js → Security-Bm66o_DH.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-DeG3Qscl.js → Services-DvanNqXl.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-D_QP7F90.js → Skeleton-CHjEe9_O.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DLGyUgIA.js → Skills-Cb8jWjMX.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-BN2pxW1Q.js → Sla-DxZm1491.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BWsZPd8B.js → SpeechSettings-DWxEEclC.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-VOZRPODW.js → SyncSettings-FIvOFw5p.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-eJSob-q0.js → Templates-CaNjzV_t.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-Bdwk8Rwy.js → Tenant-DjlP6TmH.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-D5qkbPKT.js → TimelineRenderer-DoZ8y7y8.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DKgwn8hI.js → Tokens-390cg2TC.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-HlEHniGM.js → Trigger-CLrtCj5_.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CdPcsBpN.js → Trust-DpESfSPn.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-vAdXd-vG.js → UkeySign-CN6279O0.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-9hHBYFnH.js → VideoEditing-DPtAUlSe.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-Dxh5d1yR.js → Wallet-nKOMV_1n.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BsFmLHkp.js → WebAuthn-HuXlSOc4.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-Cn0OIwLn.js → WorkflowEditor-CUfFjARi.js} +1 -1
  90. package/src/assets/web-panel/assets/chat-6Eq0PcZJ.js +1 -0
  91. package/src/assets/web-panel/assets/{colors-Du9jzLN0.js → colors-BP_Sf6Iy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-DhEscfzw.js → compact-item-BufMKIh5.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-CwzEfF3i.js → createContext-BBMXxWSW.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DT_GSE6J.js → hasIn-fUK_tRO2.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-tND8-TDU.js → index-2A5ph8b2.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B8s5FxJY.js → index-3FuwBewR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-53z1YUxX.js → index-6GmBa4Kg.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BFD0cEna.js → index-B2vS66eI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQWxDOvM.js → index-B7Etk0Te.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-VeHs6qH0.js → index-B9WYSn4m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CGvgnfl9.js → index-BHy6yNiF.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D3llLkYM.js → index-BMLHv389.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dny_ZnAO.js → index-BWKoBU8R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DzjzrOli.js → index-Bkg4ZePE.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DN60VBn5.js → index-C44atv4k.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-v5HLYCVa.js → index-CE2DdTP2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BF-ZCVGE.js → index-CGjmILDm.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-gFjYdstW.js → index-CW_G-MXw.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-NA2oBLSB.js → index-CZ6Yt17L.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Mobw3PHs.js → index-CeASi4fr.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DWL3SYN3.js → index-CgiECHu2.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-1LoutPJd.js → index-Cp8vhyZy.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnHhFnwt.js → index-CwSf5pnf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B6QDWRLk.js → index-D4_MuGkd.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D-QkW1fp.js → index-D9vU25ij.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C4SN3by5.js → index-DASluSqY.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CyEhUJcu.js → index-DLpIs_P_.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DOZTSoS7.js +1 -0
  120. package/src/assets/web-panel/assets/{index-BrkJ7JPm.js → index-DPdUNk1i.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cnok8mhZ.js → index-DVsqvI4M.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DhpvjpqI.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CAVW0cWM.js → index-DmkMXwnR.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Cc9aPkv-.js → index-DocdRcbd.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DnVVYKCE.js → index-Dyngbd29.js} +20 -20
  126. package/src/assets/web-panel/assets/{index-D8gt0j7r.js → index-GwdRUZ-x.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BIYTjbv_.js → index-LvCBfTLo.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DhXay17G.js → index-cpEli44X.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQWENaZf.js → index-fzaMtSJ8.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DfRDyY1q.js → index-pum7HYBA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BgSR2WMG.js → index-t8N1tSeX.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D5IytTND.js → index-uCbEN7cY.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DtRwLhee.js → index-wKTLbKyc.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-D4kZoXNr.js → index-wV6iGdpY.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-Cvt59c10.js → initDefaultProps-V5Fo95FM.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-D15pPEzl.js → motion-BYWU0hUT.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-N55KeXJ7.js → move-Cr1M-J2h.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-CyFO1bYP.js → mtc-parser-BysQKi2N.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-B5IXfxU2.js → omit-D1zHU8GC.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-CpVqsulV.js → pickAttrs-CUArxtfi.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-ME36sMF3.js → placementArrow-ChoYEys0.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-BXAIR1r-.js → responsiveObserve-BQRs3Mo7.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-BkOX3-Dp.js → slide-B8zbsCkg.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-Cy4Aklpt.js → statusUtils-C2U8Gs6h.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-gUd2T206.js → styleChecker-Dd8MgL_V.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-CaVD4o99.js → useFlexGapSupport-hknqY5NH.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs--pFGl0KD.js → useFs-CLTAmcfw.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-D45iP86i.js → usePersonalDataHub-ByegHbiB.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-BayoChH_.js → vnode-DqlAy5DP.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CpP38poi.js → zoom-DiXiztQn.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/agenda.js +115 -3
  153. package/src/commands/agent.js +10 -1
  154. package/src/commands/background-session.js +2 -1
  155. package/src/commands/context.js +69 -2
  156. package/src/data/changelog.json +26 -0
  157. package/src/gateways/terminal/PtyManager.js +45 -4
  158. package/src/gateways/ws/background-agent-protocol.js +38 -0
  159. package/src/gateways/ws/message-dispatcher.js +1 -0
  160. package/src/gateways/ws/remote-session-protocol.js +65 -6
  161. package/src/gateways/ws/ws-server.js +18 -2
  162. package/src/harness/background-task-manager.js +70 -2
  163. package/src/harness/feature-flags.js +15 -8
  164. package/src/harness/mcp-client.js +618 -43
  165. package/src/harness/worktree-isolator.js +14 -3
  166. package/src/lazy-dispatch.js +30 -1
  167. package/src/lib/agent-authority.js +22 -0
  168. package/src/lib/agent-core.js +3 -0
  169. package/src/lib/agent-sandbox.js +161 -0
  170. package/src/lib/agent-schedule-store.js +45 -22
  171. package/src/lib/agent-team/team-runner.js +22 -0
  172. package/src/lib/artifact-store.js +35 -0
  173. package/src/lib/async-hook-supervisor.cjs +13 -2
  174. package/src/lib/audit-mtc.js +3 -2
  175. package/src/lib/background-agent-supervisor.js +104 -3
  176. package/src/lib/background-interaction-journal.js +407 -0
  177. package/src/lib/background-interaction-resolver.js +23 -34
  178. package/src/lib/background-session-transport.js +12 -2
  179. package/src/lib/capability-manifest.js +41 -3
  180. package/src/lib/capability-negotiation.js +1 -1
  181. package/src/lib/channels/channel-manager.js +33 -0
  182. package/src/lib/channels/telegram-channel.js +23 -5
  183. package/src/lib/channels/webhook-channel.js +38 -15
  184. package/src/lib/chrome-connector.js +169 -23
  185. package/src/lib/config-manager.js +23 -9
  186. package/src/lib/context-breakdown.js +31 -0
  187. package/src/lib/cowork-cron.js +274 -32
  188. package/src/lib/cross-chain-mtc.js +3 -2
  189. package/src/lib/durable-security-store.js +124 -0
  190. package/src/lib/event-runtime-host.js +67 -6
  191. package/src/lib/event-runtime-store.js +163 -0
  192. package/src/lib/goal-store.js +32 -12
  193. package/src/lib/headless-manifest.js +2 -1
  194. package/src/lib/hook-environment.cjs +58 -0
  195. package/src/lib/hook-manager.js +12 -5
  196. package/src/lib/hook-runner.cjs +122 -28
  197. package/src/lib/hook-runner.js +7 -2
  198. package/src/lib/hooks-v2-producers.js +36 -0
  199. package/src/lib/hooks-v2-runtime.js +329 -52
  200. package/src/lib/interaction-adapter.js +60 -19
  201. package/src/lib/interaction-binding.js +64 -0
  202. package/src/lib/json-schema-output.js +439 -14
  203. package/src/lib/json-schema-validate.js +514 -883
  204. package/src/lib/lan-pairing-tokens.js +115 -57
  205. package/src/lib/lsp/__tests__/lsp-client.test.js +32 -0
  206. package/src/lib/lsp/__tests__/lsp-manager.test.js +25 -0
  207. package/src/lib/lsp/lsp-client.js +4 -0
  208. package/src/lib/lsp/lsp-manager.js +3 -0
  209. package/src/lib/lsp/lsp-server-registry.js +18 -0
  210. package/src/lib/mcp-client.js +1 -0
  211. package/src/lib/mcp-oauth.js +35 -21
  212. package/src/lib/packer/pkg-config-generator.js +3 -0
  213. package/src/lib/permission-decision.js +99 -0
  214. package/src/lib/plugin-monitor-supervisor.js +3 -0
  215. package/src/lib/plugin-runtime/bin.js +1148 -70
  216. package/src/lib/plugin-runtime/capabilities.js +50 -0
  217. package/src/lib/plugin-runtime/capability-consent.js +42 -35
  218. package/src/lib/plugin-runtime/hooks.js +36 -9
  219. package/src/lib/plugin-runtime/install.js +23 -7
  220. package/src/lib/plugin-runtime/lsp.js +4 -0
  221. package/src/lib/plugin-runtime/manifest.js +112 -8
  222. package/src/lib/plugin-runtime/mcp.js +30 -5
  223. package/src/lib/plugin-runtime/monitors.js +11 -0
  224. package/src/lib/plugin-runtime/plugin-options.js +94 -38
  225. package/src/lib/plugin-runtime/policy.js +29 -2
  226. package/src/lib/plugin-runtime/sandbox-policy.js +84 -0
  227. package/src/lib/plugin-runtime/scopes.js +27 -13
  228. package/src/lib/plugin-runtime/trust.js +36 -31
  229. package/src/lib/process-execution-broker/credential-agent.js +114 -16
  230. package/src/lib/process-execution-broker/credential-transport-worker.js +312 -0
  231. package/src/lib/process-execution-broker/credential-transport.js +378 -0
  232. package/src/lib/process-execution-broker/index.js +765 -49
  233. package/src/lib/process-execution-broker/platform-sandbox.js +4233 -138
  234. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  235. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  236. package/src/lib/process-execution-broker/windows-sandbox.cs +3856 -0
  237. package/src/lib/project-mcp-trust.js +21 -26
  238. package/src/lib/protocol-replay.js +19 -12
  239. package/src/lib/repl-bang-memorize.js +67 -10
  240. package/src/lib/settings-hooks.cjs +13 -0
  241. package/src/lib/skill-loader.js +83 -10
  242. package/src/lib/slash-commands.js +27 -4
  243. package/src/lib/sync-credentials.js +5 -4
  244. package/src/lib/turn-binding.js +8 -6
  245. package/src/repl/agent-repl.js +117 -31
  246. package/src/repl/repl-turn-binding.js +5 -2
  247. package/src/runtime/agent-core.js +699 -70
  248. package/src/runtime/agent-runtime.js +13 -1
  249. package/src/runtime/coding-agent-contract-shared.cjs +3 -3
  250. package/src/runtime/headless-runner.js +105 -7
  251. package/src/runtime/headless-stream.js +209 -23
  252. package/src/runtime/mcp-config.js +29 -17
  253. package/src/runtime/policies/agent-policy.js +2 -0
  254. package/src/workers/background-agent-worker.js +128 -13
  255. package/src/assets/web-panel/assets/ChatBubbleRenderer-C7bofJuA.js +0 -1
  256. package/src/assets/web-panel/assets/Projects-BbgmyQnS.js +0 -1
  257. package/src/assets/web-panel/assets/RemoteSession-Bqk3EYSu.js +0 -4
  258. package/src/assets/web-panel/assets/Tasks-3bDAUz8K.js +0 -1
  259. package/src/assets/web-panel/assets/Terminal-CsMLKIoV.js +0 -3
  260. package/src/assets/web-panel/assets/chat-CJ19O6Dh.js +0 -1
  261. package/src/assets/web-panel/assets/devWarning-BBMFnsja.js +0 -1
  262. package/src/assets/web-panel/assets/index-Do8JMV4N.js +0 -1
  263. package/src/assets/web-panel/assets/index-UYAJ6iNM.js +0 -1
@@ -13,12 +13,16 @@
13
13
  * Store: ~/.chainlesschain/trusted-project-mcp.json { [absPath]: {fingerprint, trustedAt} }
14
14
  */
15
15
 
16
- import fs from "node:fs";
17
16
  import path from "node:path";
18
17
  import { createHash } from "node:crypto";
19
18
  import { getHomeDir } from "./paths.js";
19
+ import { withFileLock } from "./with-file-lock.js";
20
+ import {
21
+ mutateSecurityStore,
22
+ readSecurityStore,
23
+ } from "./durable-security-store.js";
20
24
 
21
- export const _deps = { fs };
25
+ export const _deps = { withFileLock };
22
26
 
23
27
  function storePath(opts = {}) {
24
28
  return (
@@ -33,13 +37,7 @@ export function projectMcpFingerprint(content) {
33
37
  }
34
38
 
35
39
  function readStore(opts) {
36
- try {
37
- const raw = _deps.fs.readFileSync(storePath(opts), "utf-8");
38
- const data = JSON.parse(raw);
39
- return data && typeof data === "object" ? data : {};
40
- } catch {
41
- return {};
42
- }
40
+ return readSecurityStore(storePath(opts), "project MCP trust");
43
41
  }
44
42
 
45
43
  /**
@@ -61,23 +59,20 @@ export function checkProjectMcpTrust(file, content, opts = {}) {
61
59
  /** Record (or re-record) the trusted fingerprint for a file. */
62
60
  export function recordProjectMcpTrust(file, content, opts = {}) {
63
61
  const target = storePath(opts);
64
- const store = readStore(opts);
65
- store[path.resolve(file)] = {
66
- fingerprint: projectMcpFingerprint(content),
67
- trustedAt: new Date(
68
- typeof opts.now === "number" ? opts.now : Date.now(),
69
- ).toISOString(),
70
- };
71
- try {
72
- _deps.fs.mkdirSync(path.dirname(target), { recursive: true });
73
- _deps.fs.writeFileSync(target, JSON.stringify(store, null, 2), {
74
- encoding: "utf-8",
75
- mode: 0o600,
76
- });
77
- return true;
78
- } catch {
79
- return false; // best-effort — a failed record just re-prompts next run
80
- }
62
+ return mutateSecurityStore(
63
+ target,
64
+ "project MCP trust",
65
+ (store) => {
66
+ store[path.resolve(file)] = {
67
+ fingerprint: projectMcpFingerprint(content),
68
+ trustedAt: new Date(
69
+ typeof opts.now === "number" ? opts.now : Date.now(),
70
+ ).toISOString(),
71
+ };
72
+ return true;
73
+ },
74
+ { lock: _deps.withFileLock },
75
+ );
81
76
  }
82
77
 
83
78
  /** Truthy CC_PROJECT_MCP_TRUST → the user explicitly re-trusts this run. */
@@ -15,10 +15,11 @@
15
15
  * client must never receive a vN+1 field; a recording that violates that is
16
16
  * a compat bug this catches deterministically, offline, in CI.
17
17
  *
18
- * The set of gateable wire fields ({seq, trace_id, tool_use_id}) is projected
19
- * from the ONE canonical [[capability-manifest.js]] via `toFieldGate()` — it can
20
- * NOT drift from the negotiated feature list. All non-negotiable frame fields
21
- * (type, content, tool name, …) always pass through untouched.
18
+ * The gateable wire fields are projected from the ONE canonical
19
+ * [[capability-manifest.js]] via `toGateableFieldGate()` — including companion
20
+ * fields controlled by the same logical feature. They cannot drift from the
21
+ * negotiated feature list. All non-negotiable frame fields (type, content,
22
+ * tool name, …) always pass through untouched.
22
23
  *
23
24
  * PURE + deterministic: no fs / process / clock / RNG. The `digest` lets a golden
24
25
  * recording be pinned so any change to negotiation/gating that alters wire output
@@ -30,13 +31,19 @@ import {
30
31
  negotiateProtocol,
31
32
  applyNegotiationToGate,
32
33
  } from "./capability-negotiation.js";
33
- import { toFieldGate } from "./capability-manifest.js";
34
+ import { toGateableFieldGate } from "./capability-manifest.js";
34
35
 
35
- /** Negotiable wire field names ({seq, trace_id, tool_use_id}), single-sourced. */
36
+ /** Negotiable wire field primary gate field, single-sourced. */
37
+ const GATEABLE_FIELD_GATE = Object.freeze(toGateableFieldGate());
38
+
39
+ /** Every negotiable wire field name, including same-feature companions. */
36
40
  export const GATEABLE_FIELDS = Object.freeze(
37
- [...new Set(Object.values(toFieldGate()))].sort(),
41
+ Object.keys(GATEABLE_FIELD_GATE).sort(),
38
42
  );
39
43
 
44
+ const gateOn = (gate, field) =>
45
+ gate[GATEABLE_FIELD_GATE[field] || field] === true;
46
+
40
47
  /**
41
48
  * Run negotiation and fold it into a per-field gate the replay/audit read.
42
49
  * On an incompatible (ok:false) negotiation the gate stays empty → every gateable
@@ -50,8 +57,8 @@ export const GATEABLE_FIELDS = Object.freeze(
50
57
  export function gateFromNegotiation({ server = {}, client = null } = {}) {
51
58
  const negotiation = negotiateProtocol(server, client ?? null);
52
59
  const gate = applyNegotiationToGate(negotiation, {});
53
- const enabledFields = GATEABLE_FIELDS.filter((f) => gate[f] === true);
54
- const gatedFields = GATEABLE_FIELDS.filter((f) => gate[f] !== true);
60
+ const enabledFields = GATEABLE_FIELDS.filter((f) => gateOn(gate, f));
61
+ const gatedFields = GATEABLE_FIELDS.filter((f) => !gateOn(gate, f));
55
62
  return { negotiation, gate, enabledFields, gatedFields };
56
63
  }
57
64
 
@@ -61,14 +68,14 @@ export function gateFromNegotiation({ server = {}, client = null } = {}) {
61
68
  * Returns a shallow copy — the input frame is never mutated.
62
69
  *
63
70
  * @param {object} frame
64
- * @param {object} gate {seq?:boolean, trace_id?:boolean, tool_use_id?:boolean}
71
+ * @param {object} gate negotiated primary-field gate
65
72
  * @returns {object}
66
73
  */
67
74
  export function replayFrame(frame, gate = {}) {
68
75
  if (!frame || typeof frame !== "object") return frame;
69
76
  const out = {};
70
77
  for (const [k, v] of Object.entries(frame)) {
71
- if (GATEABLE_FIELDS.includes(k) && gate[k] !== true) continue;
78
+ if (GATEABLE_FIELDS.includes(k) && !gateOn(gate, k)) continue;
72
79
  out[k] = v;
73
80
  }
74
81
  return out;
@@ -116,7 +123,7 @@ export function auditRecordedSession(frames, ctx = {}) {
116
123
  if (!frame || typeof frame !== "object") return;
117
124
  for (const field of GATEABLE_FIELDS) {
118
125
  if (
119
- gate[field] !== true &&
126
+ !gateOn(gate, field) &&
120
127
  Object.prototype.hasOwnProperty.call(frame, field)
121
128
  ) {
122
129
  violations.push({
@@ -21,6 +21,7 @@ import fsDefault from "fs";
21
21
  import pathDefault from "path";
22
22
  import { findProjectRoot } from "./project-instructions.js";
23
23
  import executionBroker from "./process-execution-broker/index.js";
24
+ import { collectPluginBinSandboxPolicy as collectDefaultPluginBinSandboxPolicy } from "./plugin-runtime/bin.js";
24
25
 
25
26
  export const BANG_TIMEOUT_MS = 120_000;
26
27
  export const BANG_MAX_BUFFER = 10 * 1024 * 1024;
@@ -33,6 +34,8 @@ export const MEMO_NOTE_MAX = 4_000;
33
34
 
34
35
  export const _deps = {
35
36
  spawnSync: (...args) => executionBroker.spawnSync(...args),
37
+ collectPluginBinSandboxPolicy: (opts) =>
38
+ collectDefaultPluginBinSandboxPolicy(opts),
36
39
  fs: fsDefault,
37
40
  path: pathDefault,
38
41
  };
@@ -44,6 +47,46 @@ function cap(s) {
44
47
  : str;
45
48
  }
46
49
 
50
+ function resolveWindowsCommandShell(opts = {}) {
51
+ const path = opts.deps?.path || _deps.path;
52
+ const env = opts.env || process.env;
53
+ const normalizeLocalPath = (candidate) => {
54
+ const value =
55
+ typeof candidate === "string"
56
+ ? candidate.trim()
57
+ : String(candidate || "");
58
+ // `path.win32.isAbsolute("\\foo")` is true even though the result still
59
+ // depends on the process's current drive. Require a drive-qualified local
60
+ // path so the helper receives one deterministic executable identity.
61
+ if (!/^[a-z]:[\\/]/i.test(value)) return null;
62
+ return path.win32.normalize(value);
63
+ };
64
+
65
+ for (const candidate of [opts.comSpec, env.ComSpec, env.COMSPEC]) {
66
+ const value = normalizeLocalPath(candidate);
67
+ if (value && path.win32.basename(value).toLowerCase() === "cmd.exe") {
68
+ return value;
69
+ }
70
+ }
71
+
72
+ for (const candidate of [
73
+ opts.systemRoot,
74
+ env.SystemRoot,
75
+ env.SYSTEMROOT,
76
+ env.WINDIR,
77
+ "C:\\Windows",
78
+ ]) {
79
+ const value = normalizeLocalPath(candidate);
80
+ if (value) {
81
+ return path.win32.join(value, "System32", "cmd.exe");
82
+ }
83
+ }
84
+
85
+ // The literal fallback above is absolute, so this is unreachable with Node's
86
+ // path.win32 implementation. Keep a fail-closed guard for injected path seams.
87
+ throw new Error("No absolute Windows command shell is available");
88
+ }
89
+
47
90
  /** True when the REPL line is a `!` bash passthrough. */
48
91
  export function isBangCommand(trimmed) {
49
92
  return (
@@ -70,24 +113,37 @@ export function isMemorizeLine(trimmed) {
70
113
  */
71
114
  export function runBangCommand(line, opts = {}) {
72
115
  const spawnSync = opts.deps?.spawnSync || _deps.spawnSync;
116
+ const collectPluginBinSandboxPolicy =
117
+ opts.deps?.collectPluginBinSandboxPolicy ||
118
+ _deps.collectPluginBinSandboxPolicy;
73
119
  const cwd = opts.cwd || process.cwd();
74
120
  const isWin =
75
121
  opts.platform != null
76
122
  ? opts.platform === "win32"
77
123
  : process.platform === "win32";
78
124
  const cmd = String(line).replace(/^!/, "").trim();
125
+ // A bang command is another arbitrary workspace process surface. Collect the
126
+ // tighten-only Plugin bin boundary union before the shell starts so it cannot
127
+ // bypass a policy that run_shell already pins for the same workspace.
128
+ const sandboxPolicy = collectPluginBinSandboxPolicy({ cwd });
129
+ const sandboxOptions = sandboxPolicy ? { sandboxPolicy } : {};
79
130
 
80
131
  const res = isWin
81
- ? spawnSync("cmd.exe", ["/d", "/s", "/c", `chcp 65001 >nul && ${cmd}`], {
82
- encoding: "utf-8",
83
- timeout: BANG_TIMEOUT_MS,
84
- maxBuffer: BANG_MAX_BUFFER,
85
- cwd,
86
- origin: "repl:bang-command",
87
- policy: "allow",
88
- scope: "repl",
89
- shell: false,
90
- })
132
+ ? spawnSync(
133
+ resolveWindowsCommandShell(opts),
134
+ ["/d", "/s", "/c", `chcp 65001 >nul && ${cmd}`],
135
+ {
136
+ encoding: "utf-8",
137
+ timeout: BANG_TIMEOUT_MS,
138
+ maxBuffer: BANG_MAX_BUFFER,
139
+ cwd,
140
+ origin: "repl:bang-command",
141
+ policy: "allow",
142
+ scope: "repl",
143
+ shell: false,
144
+ ...sandboxOptions,
145
+ },
146
+ )
91
147
  : spawnSync("/bin/sh", ["-c", cmd], {
92
148
  encoding: "utf-8",
93
149
  timeout: BANG_TIMEOUT_MS,
@@ -97,6 +153,7 @@ export function runBangCommand(line, opts = {}) {
97
153
  policy: "allow",
98
154
  scope: "repl",
99
155
  shell: false,
156
+ ...sandboxOptions,
100
157
  });
101
158
 
102
159
  const exitCode = res.status == null ? (res.error ? -1 : 0) : res.status;
@@ -256,6 +256,19 @@ function collectHooks(hooksBlock, event, toolName) {
256
256
  pluginId: h.pluginId || null,
257
257
  pluginVersion: h.pluginVersion || null,
258
258
  pluginSource: h.pluginSource || null,
259
+ ...(h.shell != null ? { shell: h.shell } : {}),
260
+ ...(h.environmentAllowlist != null
261
+ ? { environmentAllowlist: h.environmentAllowlist }
262
+ : {}),
263
+ ...(h.envAllowlist != null
264
+ ? { envAllowlist: h.envAllowlist }
265
+ : {}),
266
+ ...(h.sandboxPolicy !== undefined
267
+ ? { sandboxPolicy: h.sandboxPolicy }
268
+ : {}),
269
+ ...(h.requiredBoundaries !== undefined
270
+ ? { requiredBoundaries: h.requiredBoundaries }
271
+ : {}),
259
272
  });
260
273
  }
261
274
  }
@@ -332,6 +332,7 @@ export class CLISkillLoader {
332
332
  this._cacheStats = {
333
333
  descriptorScans: 0,
334
334
  descriptorCacheHits: 0,
335
+ descriptorUses: new Map(),
335
336
  bodyCacheHits: 0,
336
337
  bodyCacheMisses: 0,
337
338
  bodyLoads: new Map(),
@@ -579,10 +580,16 @@ export class CLISkillLoader {
579
580
  estimatedTokens: cached.estimatedTokens,
580
581
  loads: 0,
581
582
  cacheHits: 0,
583
+ contextLoads: 0,
584
+ contextCacheHits: 0,
582
585
  loadedBecause: [],
583
586
  };
584
587
  prior.loads += 1;
585
588
  if (cacheRead) prior.cacheHits += 1;
589
+ if (context.bodyIncluded === true) {
590
+ prior.contextLoads += 1;
591
+ if (cacheRead) prior.contextCacheHits += 1;
592
+ }
586
593
  const reason = context.loadedBecause || "explicit";
587
594
  if (!prior.loadedBecause.includes(reason)) prior.loadedBecause.push(reason);
588
595
  this._cacheStats.bodyLoads.set(skill.skillMdPath, prior);
@@ -594,12 +601,17 @@ export class CLISkillLoader {
594
601
  sourceType: "skill",
595
602
  sourceId: skill.id,
596
603
  permissionMode: context.permissionMode || "agent",
597
- tokenCount: cached.estimatedTokens,
604
+ // Only text that actually entered a model prompt consumes context
605
+ // tokens. Ordinary run_skill materialization is still recorded, but
606
+ // as a content-size/cache observation rather than a false token charge.
607
+ tokenCount:
608
+ context.bodyIncluded === true ? cached.estimatedTokens : 0,
598
609
  metadata: {
599
610
  source: skill.source,
600
611
  loadedBecause: reason,
601
612
  cacheRead,
602
613
  bodyIncluded: context.bodyIncluded === true,
614
+ estimatedBodyTokens: cached.estimatedTokens,
603
615
  },
604
616
  });
605
617
  } catch {
@@ -612,6 +624,22 @@ export class CLISkillLoader {
612
624
  recordDescriptorUse(skills, context = {}) {
613
625
  for (const skill of Array.isArray(skills) ? skills : []) {
614
626
  const descriptorText = `${skill.id}\n${skill.description || ""}\n${skill.category || ""}`;
627
+ const estimatedTokens = estimateSkillTokens(descriptorText);
628
+ const prior = this._cacheStats.descriptorUses.get(skill.id) || {
629
+ id: skill.id,
630
+ source: skill.source,
631
+ estimatedTokens,
632
+ contextLoads: 0,
633
+ contextTokens: 0,
634
+ loadedBecause: [],
635
+ };
636
+ prior.contextLoads += 1;
637
+ prior.contextTokens += estimatedTokens;
638
+ const reason = context.loadedBecause || "list_skills";
639
+ if (!prior.loadedBecause.includes(reason)) {
640
+ prior.loadedBecause.push(reason);
641
+ }
642
+ this._cacheStats.descriptorUses.set(skill.id, prior);
615
643
  try {
616
644
  this._contextLedger?.recordRead?.({
617
645
  sessionId: context.sessionId || "unknown",
@@ -619,10 +647,10 @@ export class CLISkillLoader {
619
647
  sourceType: "skill_descriptor",
620
648
  sourceId: skill.id,
621
649
  permissionMode: context.permissionMode || "agent",
622
- tokenCount: estimateSkillTokens(descriptorText),
650
+ tokenCount: estimatedTokens,
623
651
  metadata: {
624
652
  source: skill.source,
625
- loadedBecause: context.loadedBecause || "list_skills",
653
+ loadedBecause: reason,
626
654
  bodyLoaded: skill.bodyLoaded === true,
627
655
  },
628
656
  });
@@ -635,10 +663,28 @@ export class CLISkillLoader {
635
663
  /** Content-free cache/cost snapshot for `cc context --sources`. */
636
664
  getCacheLedger() {
637
665
  const descriptors = Array.isArray(this._cache) ? this._cache : [];
638
- const descriptorChars = descriptors.reduce(
639
- (sum, skill) =>
640
- sum +
641
- `${skill.id}\n${skill.displayName}\n${skill.description}\n${skill.category}`.length,
666
+ const descriptorEntries = descriptors.map((skill) => {
667
+ const descriptorText = `${skill.id}\n${skill.displayName}\n${skill.description}\n${skill.category}`;
668
+ const used = this._cacheStats.descriptorUses.get(skill.id);
669
+ return {
670
+ id: skill.id,
671
+ source: skill.source,
672
+ estimatedTokens: estimateSkillTokens(descriptorText),
673
+ contextLoads: Number(used?.contextLoads) || 0,
674
+ contextTokens: Number(used?.contextTokens) || 0,
675
+ loadedBecause: [...(used?.loadedBecause || [])],
676
+ };
677
+ });
678
+ const descriptorTokens = descriptorEntries.reduce(
679
+ (sum, entry) => sum + entry.estimatedTokens,
680
+ 0,
681
+ );
682
+ const descriptorContextLoads = descriptorEntries.reduce(
683
+ (sum, entry) => sum + entry.contextLoads,
684
+ 0,
685
+ );
686
+ const descriptorContextTokens = descriptorEntries.reduce(
687
+ (sum, entry) => sum + entry.contextTokens,
642
688
  0,
643
689
  );
644
690
  const residentPaths = new Set(this._bodyCache.keys());
@@ -649,20 +695,46 @@ export class CLISkillLoader {
649
695
  0,
650
696
  );
651
697
  const bodyLoads = Array.from(this._cacheStats.bodyLoads.values()).map(
652
- (entry) => ({ ...entry, loadedBecause: [...entry.loadedBecause] }),
698
+ (entry) => ({
699
+ ...entry,
700
+ cacheMisses: Math.max(0, entry.loads - entry.cacheHits),
701
+ contextTokens: entry.estimatedTokens * entry.contextLoads,
702
+ loadedBecause: [...entry.loadedBecause],
703
+ }),
704
+ );
705
+ const bodyTotals = bodyLoads.reduce(
706
+ (totals, entry) => {
707
+ totals.estimatedTokensResident += entry.estimatedTokens;
708
+ totals.estimatedTokensServed += entry.estimatedTokens * entry.loads;
709
+ totals.estimatedTokensAvoidedByCache +=
710
+ entry.estimatedTokens * entry.cacheHits;
711
+ totals.contextLoads += entry.contextLoads;
712
+ totals.contextTokens += entry.contextTokens;
713
+ return totals;
714
+ },
715
+ {
716
+ estimatedTokensResident: 0,
717
+ estimatedTokensServed: 0,
718
+ estimatedTokensAvoidedByCache: 0,
719
+ contextLoads: 0,
720
+ contextTokens: 0,
721
+ },
653
722
  );
654
723
  return {
655
724
  descriptors: {
656
725
  resident: descriptors.length,
657
726
  scans: this._cacheStats.descriptorScans,
658
727
  cacheHits: this._cacheStats.descriptorCacheHits,
659
- rawChars: descriptorChars,
660
- estimatedTokens: estimateSkillTokens(descriptorChars),
728
+ estimatedTokens: descriptorTokens,
729
+ contextLoads: descriptorContextLoads,
730
+ contextTokens: descriptorContextTokens,
731
+ entries: descriptorEntries,
661
732
  },
662
733
  bodies: {
663
734
  resident: this._bodyCache.size,
664
735
  cacheHits: this._cacheStats.bodyCacheHits,
665
736
  cacheMisses: this._cacheStats.bodyCacheMisses,
737
+ ...bodyTotals,
666
738
  entries: bodyLoads,
667
739
  },
668
740
  savings: {
@@ -770,6 +842,7 @@ export class CLISkillLoader {
770
842
  this._cacheStats = {
771
843
  descriptorScans: 0,
772
844
  descriptorCacheHits: 0,
845
+ descriptorUses: new Map(),
773
846
  bodyCacheHits: 0,
774
847
  bodyCacheMisses: 0,
775
848
  bodyLoads: new Map(),
@@ -24,11 +24,14 @@ import { homedir } from "node:os";
24
24
  import { expandFileRefs } from "../runtime/file-ref-expander.js";
25
25
  import { projectRootBase } from "./project-root.cjs";
26
26
  import executionBroker from "./process-execution-broker/index.js";
27
+ import { collectPluginBinSandboxPolicy as collectDefaultPluginBinSandboxPolicy } from "./plugin-runtime/bin.js";
27
28
 
28
29
  const _deps = {
29
30
  fs: fsDefault,
30
31
  path: pathDefault,
31
32
  execSync: (...args) => executionBroker.execSync(...args),
33
+ collectPluginBinSandboxPolicy: (opts) =>
34
+ collectDefaultPluginBinSandboxPolicy(opts),
32
35
  };
33
36
 
34
37
  /**
@@ -186,8 +189,16 @@ export function substituteArgs(text, args = []) {
186
189
  return out;
187
190
  }
188
191
 
192
+ function isSandboxFailClosedError(error) {
193
+ return (
194
+ error?.sandboxFailClosed === true ||
195
+ (typeof error?.code === "string" &&
196
+ error.code.startsWith("ERR_PROCESS_SANDBOX"))
197
+ );
198
+ }
199
+
189
200
  /** Run every `!`cmd`` and replace it with the command's stdout (best-effort). */
190
- function runBangs(text, { cwd, execSync }) {
201
+ function runBangs(text, { cwd, execSync, sandboxPolicy }) {
191
202
  return text.replace(/!`([^`]+)`/g, (_, cmd) => {
192
203
  try {
193
204
  const out = execSync(cmd, {
@@ -199,9 +210,13 @@ function runBangs(text, { cwd, execSync }) {
199
210
  policy: "allow",
200
211
  scope: "slash-command",
201
212
  shell: true,
213
+ ...(sandboxPolicy ? { sandboxPolicy } : {}),
202
214
  });
203
215
  return String(out).trim();
204
216
  } catch (err) {
217
+ // Broker boundary failures are security decisions, not ordinary command
218
+ // failures. Preserve the exact structured error for the caller.
219
+ if (isSandboxFailClosedError(err)) throw err;
205
220
  return `[command failed: ${cmd} — ${err.message?.split("\n")[0] || err}]`;
206
221
  }
207
222
  });
@@ -215,18 +230,26 @@ function runBangs(text, { cwd, execSync }) {
215
230
  *
216
231
  * @param {object} command output of getCommand/parseCommandFile (needs .body)
217
232
  * @param {string[]} args
218
- * @param {object} [opts] { cwd, allowBang, deps:{ execSync } }
233
+ * @param {object} [opts] { cwd, allowBang,
234
+ * deps:{ execSync, collectPluginBinSandboxPolicy } }
219
235
  * @returns {{ prompt:string, warnings:string[] }}
220
236
  */
221
237
  export function expandCommand(command, args = [], opts = {}) {
222
238
  const cwd = opts.cwd || process.cwd();
223
239
  const execSync = opts.deps?.execSync || _deps.execSync;
240
+ const collectPluginBinSandboxPolicy =
241
+ opts.deps?.collectPluginBinSandboxPolicy ||
242
+ _deps.collectPluginBinSandboxPolicy;
224
243
  const warnings = [];
225
244
 
226
245
  let text = substituteArgs(command.body || "", args);
227
246
 
228
- if (opts.allowBang !== false) {
229
- text = runBangs(text, { cwd, execSync });
247
+ if (opts.allowBang !== false && /!`[^`]+`/.test(text)) {
248
+ // Resolve and pin once before the first bang. Every expansion in this macro
249
+ // receives the same immutable policy object, so no earlier command can run
250
+ // before a later discovery failure or observe a weaker boundary set.
251
+ const sandboxPolicy = collectPluginBinSandboxPolicy({ cwd });
252
+ text = runBangs(text, { cwd, execSync, sandboxPolicy });
230
253
  }
231
254
 
232
255
  // @file expansion appends a <referenced-files> block when anything resolves.
@@ -214,14 +214,15 @@ function setCredentials(providerId, creds) {
214
214
  if (!creds || typeof creds !== "object") {
215
215
  throw new Error("sync-credentials: creds must be an object");
216
216
  }
217
- // Serialize the decrypt-modify-encrypt across processes so a concurrent write
218
- // to a different provider doesn't drop this one (best-effort, never hangs).
217
+ // Credential metadata is security-critical: never decrypt-modify-encrypt
218
+ // without exclusion, because a lost update can silently restore/revoke the
219
+ // wrong provider credentials.
219
220
  return withFileLock(_vaultPath(), () => {
220
221
  const all = loadAll();
221
222
  if (!all.sync || typeof all.sync !== "object") all.sync = {};
222
223
  all.sync[providerId] = { ...creds };
223
224
  return saveAll(all);
224
- });
225
+ }, { failIfUnavailable: true });
225
226
  }
226
227
 
227
228
  function clearCredentials(providerId) {
@@ -233,7 +234,7 @@ function clearCredentials(providerId) {
233
234
  return saveAll(all);
234
235
  }
235
236
  return true;
236
- });
237
+ }, { failIfUnavailable: true });
237
238
  }
238
239
 
239
240
  /** Test seam: override the resolved chainlesschain dir without env leak. */
@@ -555,12 +555,14 @@ export function createTurnBindingFeed({
555
555
  // New core events expose the exact decision id. Legacy/custom event
556
556
  // sources retain the deterministic compatibility derivation.
557
557
  const policy = event.result?.policy;
558
- if (policy && (policy.via || policy.decision)) {
559
- log.recordPermissionDecision(
560
- turnId,
561
- event.permission_decision_id ||
562
- `${callId || "call"}:perm:${policy.via || policy.decision}`,
563
- );
558
+ const decisionId =
559
+ event.permission_decision_id ||
560
+ event.permission_decision?.id ||
561
+ (policy && (policy.via || policy.decision)
562
+ ? `${callId || "call"}:perm:${policy.via || policy.decision}`
563
+ : null);
564
+ if (decisionId) {
565
+ log.recordPermissionDecision(turnId, decisionId);
564
566
  dirty = true;
565
567
  }
566
568
  if (event.result?.userEdited === true) {