chainlesschain 0.162.142 → 0.162.147

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 (192) hide show
  1. package/bin/chainlesschain.js +5 -3
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-Dc6fWgoa.js → AIOps-DywJ7xTv.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DzMx25vJ.js → ActionButton-BTlqcY-q.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-eDC3KNht.js → Analytics-FdG1Mvwy.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-uA1TOtNp.js → AppLayout-CVsnbvN1.js} +3 -3
  7. package/src/assets/web-panel/assets/{Audit-Bq3QX9Cj.js → Audit-ITdpJ7vR.js} +1 -1
  8. package/src/assets/web-panel/assets/{Backup-B1V3Jfyv.js → Backup-BfXqnXo1.js} +1 -1
  9. package/src/assets/web-panel/assets/{BaseInput-DMFwddIF.js → BaseInput-C09ip3_E.js} +1 -1
  10. package/src/assets/web-panel/assets/{Chat-CKWQDH5j.js → Chat-qdkhB42l.js} +6 -6
  11. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CStSBCnk.js → ChatBubbleRenderer-D2KfETZD.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-DepIzI7G.js → Checkbox-j6WZCuff.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-ClanWll2.js → Codegen-CGZ-K3uK.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-CFBzsL4W.js → Col-CIlPOAu6.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-CaAyZ_Pt.js → Community-sY-i-hic.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compact-3dCHRSVe.js → Compact-DcoEfyL8.js} +1 -1
  17. package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +1 -0
  18. package/src/assets/web-panel/assets/{Cowork-DkJmdQaU.js → Cowork-DLY_fkLv.js} +3 -3
  19. package/src/assets/web-panel/assets/{Cron-DKfNf6J2.js → Cron-D049pZBC.js} +2 -2
  20. package/src/assets/web-panel/assets/{Crosschain-COu1aQIO.js → Crosschain-DCXdE8M9.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-CkAgFLkI.js → DID-JCvPZtjW.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dashboard-CfaKWZCU.js → Dashboard-Cg2xu3iE.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-Cjw97sdb.js → Dropdown-DjJLjbLh.js} +1 -1
  24. package/src/assets/web-panel/assets/{EmailListRenderer-DROLQgOn.js → EmailListRenderer-DugNcSLR.js} +1 -1
  25. package/src/assets/web-panel/assets/{FamilyGuardDashboard-o_cYzlLq.js → FamilyGuardDashboard-J6Nb-E30.js} +1 -1
  26. package/src/assets/web-panel/assets/{Federation-Boex3ljt.js → Federation-CQ3Ttpbl.js} +1 -1
  27. package/src/assets/web-panel/assets/{FormItemContext-BQQ05T_3.js → FormItemContext-Cz0NKag4.js} +1 -1
  28. package/src/assets/web-panel/assets/{GenericCardRenderer-CdfL8PBd.js → GenericCardRenderer-NYcMP6iz.js} +1 -1
  29. package/src/assets/web-panel/assets/{Git-CfZ07qJy.js → Git-B4hYN18N.js} +2 -2
  30. package/src/assets/web-panel/assets/{Governance-B21ISudq.js → Governance-DOQPCcC0.js} +1 -1
  31. package/src/assets/web-panel/assets/{Inference-CGIK4Im1.js → Inference-HtLF746W.js} +1 -1
  32. package/src/assets/web-panel/assets/{KnowledgeGraph-CI1kmfdT.js → KnowledgeGraph-BXrk8rMm.js} +1 -1
  33. package/src/assets/web-panel/assets/{Logs-Dpc1-__h.js → Logs-DS0JnD3-.js} +2 -2
  34. package/src/assets/web-panel/assets/{Marketplace-BpMtMWyK.js → Marketplace-Cqnjihrz.js} +1 -1
  35. package/src/assets/web-panel/assets/{McpTools-B94WvOOS.js → McpTools-Cx3Psk-U.js} +5 -5
  36. package/src/assets/web-panel/assets/{Memory-DODsAxZf.js → Memory-BjyysPtj.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileBridge-C3Bjshem.js → MobileBridge-CYaITZ7w.js} +1 -1
  38. package/src/assets/web-panel/assets/{MobileProjects-Bi8IHDv_.js → MobileProjects-Xwf-fdOQ.js} +1 -1
  39. package/src/assets/web-panel/assets/{Mtc-CH5BSuWG.js → Mtc-Djkql5m5.js} +2 -2
  40. package/src/assets/web-panel/assets/{MtcAudit-DN3MzNtL.js → MtcAudit-D2BAZlu0.js} +2 -2
  41. package/src/assets/web-panel/assets/{Multisig-CO6GWQOh.js → Multisig-ByqP1ZzH.js} +3 -3
  42. package/src/assets/web-panel/assets/{NLProgramming-DhIr3V_u.js → NLProgramming-C_kn0nE2.js} +1 -1
  43. package/src/assets/web-panel/assets/{Notes-CNgrvZCJ.js → Notes-7l7eXHPq.js} +3 -3
  44. package/src/assets/web-panel/assets/{NotificationSettings-Crr4LPjQ.js → NotificationSettings-BrCDoitw.js} +1 -1
  45. package/src/assets/web-panel/assets/{OrderTableRenderer-C8q-kK2z.js → OrderTableRenderer-CmtqIdwr.js} +1 -1
  46. package/src/assets/web-panel/assets/{Organization-hWhCmeUn.js → Organization-DdoNAxsM.js} +4 -4
  47. package/src/assets/web-panel/assets/{Overflow-t_EBxns-.js → Overflow-Bw7R6dE3.js} +1 -1
  48. package/src/assets/web-panel/assets/{P2P-B8fqfwis.js → P2P-DJNtBugA.js} +2 -2
  49. package/src/assets/web-panel/assets/{PdhVaultBrowser-d-wlJw3R.js → PdhVaultBrowser-Dwhd5_ue.js} +3 -3
  50. package/src/assets/web-panel/assets/{Permissions-Ch13YpBo.js → Permissions-BYoSfhPR.js} +4 -4
  51. package/src/assets/web-panel/assets/{PersonalDataHub-DW7LlLbS.js → PersonalDataHub-aYCYJbnH.js} +2 -2
  52. package/src/assets/web-panel/assets/{Pipeline-BHh3HqOQ.js → Pipeline-DJp6CprF.js} +1 -1
  53. package/src/assets/web-panel/assets/{Privacy-DAhlJDSC.js → Privacy-Cq9HZweR.js} +1 -1
  54. package/src/assets/web-panel/assets/{ProjectInit-BRtddAM_.js → ProjectInit-CmF0HsSt.js} +2 -2
  55. package/src/assets/web-panel/assets/{ProjectSettings-CzR1EC7b.js → ProjectSettings-P8V5Aea2.js} +2 -2
  56. package/src/assets/web-panel/assets/{Projects-DSavX602.js → Projects-DPd5-lNS.js} +1 -1
  57. package/src/assets/web-panel/assets/{Providers-D55_kGZs.js → Providers-D589v0q2.js} +1 -1
  58. package/src/assets/web-panel/assets/{QuickAsk-BR5qDIaT.js → QuickAsk-lvRV2osB.js} +1 -1
  59. package/src/assets/web-panel/assets/{Recommend-CTFAM97K.js → Recommend-sb84MTpY.js} +1 -1
  60. package/src/assets/web-panel/assets/{Reputation-Z7pvEha9.js → Reputation-d9Y3vy-W.js} +1 -1
  61. package/src/assets/web-panel/assets/{Row-MqKcIsjP.js → Row-DMdhLUTp.js} +1 -1
  62. package/src/assets/web-panel/assets/{RssFeed-DdLDjRpk.js → RssFeed-OL-SMDkz.js} +3 -3
  63. package/src/assets/web-panel/assets/{Search-CeOzh2hz.js → Search-BjStLKq1.js} +1 -1
  64. package/src/assets/web-panel/assets/{Security-gHrXaf4o.js → Security-MeI411qr.js} +3 -3
  65. package/src/assets/web-panel/assets/{Services-B8Y7pmY1.js → Services-UajosuhT.js} +2 -2
  66. package/src/assets/web-panel/assets/{Skeleton-CRS0WNut.js → Skeleton-CGpG-QJ1.js} +1 -1
  67. package/src/assets/web-panel/assets/{Skills--a5_Pdxx.js → Skills-C6P2RPY-.js} +1 -1
  68. package/src/assets/web-panel/assets/{Sla-EcTyL6EM.js → Sla-B_fPprgw.js} +1 -1
  69. package/src/assets/web-panel/assets/{SpeechSettings-QpfvOiJr.js → SpeechSettings-CqxLjSlQ.js} +1 -1
  70. package/src/assets/web-panel/assets/{SyncSettings-DaLBBzlE.js → SyncSettings-DixSfFOQ.js} +2 -2
  71. package/src/assets/web-panel/assets/{Tasks-Ca6xvO-I.js → Tasks-PavzPBxc.js} +1 -1
  72. package/src/assets/web-panel/assets/{Templates-CWguiCoE.js → Templates-BYdGRnfX.js} +1 -1
  73. package/src/assets/web-panel/assets/{Tenant-BGLLZulu.js → Tenant-BQLgnarD.js} +1 -1
  74. package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +3 -0
  75. package/src/assets/web-panel/assets/{TimelineRenderer-CLjvjbmr.js → TimelineRenderer-Bv7uZRM7.js} +1 -1
  76. package/src/assets/web-panel/assets/{Tokens-De5mmw_a.js → Tokens-DxhgszRJ.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trigger-Chbq6U0N.js → Trigger-nQYHayuc.js} +1 -1
  78. package/src/assets/web-panel/assets/{Trust-CYxUO2lv.js → Trust-CvqZDuZ4.js} +1 -1
  79. package/src/assets/web-panel/assets/{UkeySign-iyoZXXF6.js → UkeySign-DaR8GV2I.js} +1 -1
  80. package/src/assets/web-panel/assets/{VideoEditing-CHg3ifJs.js → VideoEditing-C6Mpsokw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Wallet-DfjqObsO.js → Wallet-B57VRrWc.js} +4 -4
  82. package/src/assets/web-panel/assets/{WebAuthn-0FI6xfud.js → WebAuthn-BbtEEtpq.js} +4 -4
  83. package/src/assets/web-panel/assets/{WorkflowEditor-CJgGcctS.js → WorkflowEditor-C5LwvKmH.js} +1 -1
  84. package/src/assets/web-panel/assets/{chat-fH-E4vhY.js → chat-MzuPR5jh.js} +1 -1
  85. package/src/assets/web-panel/assets/{colors-CuxsOD6d.js → colors-HyW8mi_y.js} +1 -1
  86. package/src/assets/web-panel/assets/{compact-item-AfVcFrFU.js → compact-item-DvUSzWAp.js} +1 -1
  87. package/src/assets/web-panel/assets/{createContext-DTsD7sPO.js → createContext-4MppZl7X.js} +1 -1
  88. package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +1 -0
  89. package/src/assets/web-panel/assets/{hasIn-DJR763M6.js → hasIn-D9q3CJ-u.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DhM9FZUH.js → index--Bm6OqmU.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-OZwLoscJ.js → index-B5E4DhVc.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CF8OSTin.js → index-B994UQfE.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-9cryBBzh.js → index-BF2JsbiX.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BMVULNUC.js → index-BFGfC5RS.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dj1_lz3M.js → index-BGQtFso0.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DBPAH7rp.js → index-BcunsBIx.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-mLAfJXAf.js → index-Bl-E4W4q.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Cfppli6E.js → index-BmCo2Xdp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-_hP8-Y2F.js → index-Btz8pU68.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DvlzcX3Q.js → index-C0DiL97c.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CBBStCbO.js → index-C9I5MuJ0.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-SuJ2_OzM.js → index-CLAZs1Vd.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B8DLT-F4.js → index-Caqtu6Et.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cm4cHE5_.js → index-CgIfXOD9.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DbQX0Zbf.js → index-Ci6adKh8.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BEGMojUg.js → index-CifKUNtV.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CdV7a6-K.js → index-Cs-RKRUM.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dswd93W4.js → index-CucRo4Vz.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-D2KdGSMa.js → index-CvAqCAo9.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BqflTl_b.js → index-D2jrnaq3.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cl7chKuM.js → index-D7TDMMfu.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CAbZ3dW9.js → index-DDZ6fP5Z.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BD4-kWQY.js → index-DFqvWeGc.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DejUsZOw.js → index-DNYqw0MO.js} +1 -1
  115. package/src/assets/web-panel/assets/index-D_oeGLr6.js +1 -0
  116. package/src/assets/web-panel/assets/{index-DH-dA4pG.js → index-DcMkjb92.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CMcYdHhK.js → index-DmZ6-suL.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-85iIi3qF.js → index-Dv7ffI2g.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DJIFQ-Ay.js → index-Dvg5xYuP.js} +1 -1
  120. package/src/assets/web-panel/assets/index-Qa57YG19.js +1 -0
  121. package/src/assets/web-panel/assets/{index-DxD03TL7.js → index-SDblbKj6.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CmUxSTNy.js → index-UUMCA2Sq.js} +3 -3
  123. package/src/assets/web-panel/assets/{index-BJl6Ob-o.js → index-Zvtru2oE.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DPHYW3IK.js → index-eOQO6KnV.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B-rcowKd.js → index-m0b6Fj9q.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BTtL2Qkb.js → index-ryFLxB1p.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-fj3ZXgiy.js → index-xvUVuFnA.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BbpuFc8C.js → index-zLwYpvX0.js} +1 -1
  129. package/src/assets/web-panel/assets/{initDefaultProps-BGoLJP6T.js → initDefaultProps-CC--PMsC.js} +1 -1
  130. package/src/assets/web-panel/assets/{motion-23xdoVi0.js → motion-DdrBjRF1.js} +1 -1
  131. package/src/assets/web-panel/assets/{move-C3Bnnft-.js → move-D712Gwl2.js} +1 -1
  132. package/src/assets/web-panel/assets/{mtc-parser-Dv0C3a90.js → mtc-parser-39y-keKO.js} +1 -1
  133. package/src/assets/web-panel/assets/{omit-C3r9qzDO.js → omit-6Y51gDB9.js} +1 -1
  134. package/src/assets/web-panel/assets/{pickAttrs-B4bJOcNp.js → pickAttrs-Cy6EHbq9.js} +1 -1
  135. package/src/assets/web-panel/assets/{placementArrow-Dxf48wcO.js → placementArrow-BYxdAm0p.js} +1 -1
  136. package/src/assets/web-panel/assets/{responsiveObserve-Rq9U2hbz.js → responsiveObserve-C9R6q_cr.js} +1 -1
  137. package/src/assets/web-panel/assets/{slide-BeWiqbdF.js → slide-CXUJlilB.js} +1 -1
  138. package/src/assets/web-panel/assets/{statusUtils-yX9N1cEc.js → statusUtils-DOtrOeql.js} +1 -1
  139. package/src/assets/web-panel/assets/{styleChecker-5Ko9NorO.js → styleChecker-D5r39o92.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFlexGapSupport-WKpkTLok.js → useFlexGapSupport-DfAD5U4Z.js} +1 -1
  141. package/src/assets/web-panel/assets/{useFs-BV4X0JOG.js → useFs-BkrT-Zbc.js} +1 -1
  142. package/src/assets/web-panel/assets/{usePersonalDataHub-g41wq_2p.js → usePersonalDataHub-BwnnnZjU.js} +1 -1
  143. package/src/assets/web-panel/assets/{vnode-BAurbOH8.js → vnode-SU-N4pum.js} +1 -1
  144. package/src/assets/web-panel/assets/{zoom-Cl5XniRg.js → zoom-DHScfpTP.js} +1 -1
  145. package/src/assets/web-panel/index.html +1 -1
  146. package/src/auth/npm-auth.js +65 -0
  147. package/src/command-manifest.json +1126 -0
  148. package/src/commands/agent.js +92 -0
  149. package/src/commands/agents.js +68 -0
  150. package/src/commands/note.js +2 -1
  151. package/src/commands/permissions.js +66 -10
  152. package/src/commands/plugin.js +50 -3
  153. package/src/commands/search.js +2 -1
  154. package/src/harness/worktree-isolator.js +14 -7
  155. package/src/index.js +4 -12
  156. package/src/lazy-dispatch.js +107 -0
  157. package/src/lib/agent-sandbox.js +72 -0
  158. package/src/lib/background-agent-supervisor.js +197 -0
  159. package/src/lib/code-review.js +72 -0
  160. package/src/lib/ensure-utf8.js +25 -7
  161. package/src/lib/knowledge-exporter.js +3 -2
  162. package/src/lib/permission-engine.js +1 -1
  163. package/src/lib/plugin-security.js +128 -0
  164. package/src/lib/pr-create.js +108 -0
  165. package/src/lib/publish-workspace.js +107 -0
  166. package/src/lib/resume-session.js +111 -0
  167. package/src/lib/session-manager.js +2 -1
  168. package/src/lib/settings-hooks.cjs +68 -25
  169. package/src/lib/settings-loader.cjs +115 -2
  170. package/src/lib/task-list.js +53 -0
  171. package/src/program-base.js +24 -0
  172. package/src/repl/agent-repl.js +108 -3
  173. package/src/repl/agents-status.js +8 -2
  174. package/src/repl/btw-command.js +56 -0
  175. package/src/repl/chat-repl.js +52 -1
  176. package/src/repl/hooks-status.js +1 -2
  177. package/src/repl/slash-command-registry.js +197 -0
  178. package/src/repl/think-command.js +31 -0
  179. package/src/runtime/agent-core.js +137 -1
  180. package/src/runtime/coding-agent-contract-shared.cjs +37 -0
  181. package/src/runtime/coding-agent-policy.cjs +14 -0
  182. package/src/runtime/file-ref-expander.js +100 -14
  183. package/src/runtime/headless-runner.js +26 -6
  184. package/src/runtime/headless-stream.js +25 -6
  185. package/src/runtime/mcp-config.js +46 -2
  186. package/src/runtime/policies/agent-policy.js +1 -0
  187. package/src/workers/background-agent-worker.js +60 -0
  188. package/src/assets/web-panel/assets/Compliance-BKYHHTdq.js +0 -1
  189. package/src/assets/web-panel/assets/Terminal-Bryzb1Rm.js +0 -3
  190. package/src/assets/web-panel/assets/devWarning-DffbPlEe.js +0 -1
  191. package/src/assets/web-panel/assets/index-BAHgOh3h.js +0 -1
  192. package/src/assets/web-panel/assets/index-zfkDb9FC.js +0 -1
@@ -44,6 +44,7 @@ import { createToolContext } from "../tools/tool-context.js";
44
44
  import { createToolTelemetryRecord } from "../tools/tool-telemetry.js";
45
45
  import { isAbortError, throwIfAborted } from "../lib/abort-utils.js";
46
46
  import { buildSearchCommand } from "../lib/search-command.js";
47
+ import { discoverCommands } from "../lib/slash-commands.js";
47
48
  import {
48
49
  isRetryableStreamError,
49
50
  STREAM_RETRY_BASE_MS,
@@ -700,6 +701,30 @@ export function buildSystemPrompt(cwd, opts = {}) {
700
701
  extraDirs.map((d) => `- ${d}`).join("\n");
701
702
  }
702
703
 
704
+ // Advertise user-defined slash commands (.claude/commands/*.md etc.) so the
705
+ // model knows which prompt macros it can run via the slash_command tool.
706
+ try {
707
+ const macros = discoverCommands(dir);
708
+ if (macros.length > 0) {
709
+ prompt +=
710
+ `\n\n## Available slash commands\n` +
711
+ `These are reusable prompt macros the user has defined. Run one with ` +
712
+ `the slash_command tool (e.g. {"command":"/${macros[0].name}"}) when it ` +
713
+ `fits the task; the tool returns the command's expanded instructions ` +
714
+ `for you to carry out.\n` +
715
+ macros
716
+ .map(
717
+ (m) =>
718
+ `- /${m.name}` +
719
+ (m.argumentHint ? ` ${m.argumentHint}` : "") +
720
+ (m.description ? ` — ${m.description}` : ""),
721
+ )
722
+ .join("\n");
723
+ }
724
+ } catch {
725
+ // Non-critical — command discovery failure must not break the prompt.
726
+ }
727
+
703
728
  return prompt;
704
729
  }
705
730
 
@@ -1272,6 +1297,7 @@ export async function executeTool(name, args, context = {}) {
1272
1297
  approvalGate: context.approvalGate || null,
1273
1298
  shellConfirm: context.shellConfirm || null,
1274
1299
  additionalDirectories: context.additionalDirectories || null,
1300
+ sandbox: context.sandbox || null,
1275
1301
  ruleAllowed,
1276
1302
  subAgentDepth: context.subAgentDepth || 0,
1277
1303
  subAgentBudget: context.subAgentBudget || null,
@@ -1686,6 +1712,7 @@ async function executeToolInner(
1686
1712
  approvalGate,
1687
1713
  shellConfirm,
1688
1714
  additionalDirectories,
1715
+ sandbox,
1689
1716
  ruleAllowed = false,
1690
1717
  subAgentDepth = 0,
1691
1718
  subAgentBudget = null,
@@ -1993,6 +2020,13 @@ async function executeToolInner(
1993
2020
  // polls output + completion via check_shell. No timeout — that's the whole
1994
2021
  // point of backgrounding (builds, test suites, dev servers).
1995
2022
  if (args.run_in_background === true) {
2023
+ if (sandbox) {
2024
+ return attachDescriptor({
2025
+ error:
2026
+ "[Sandbox] Background shell tasks are not supported in the ephemeral sandbox. Run in the foreground or explicitly disable --sandbox.",
2027
+ policy: { decision: "deny", via: "sandbox" },
2028
+ });
2029
+ }
1996
2030
  // Free memory from idle background tasks before adding another, so a
1997
2031
  // long agent run can't accumulate forgotten dev servers (no-op unless
1998
2032
  // the machine is actually under memory pressure).
@@ -2088,6 +2122,50 @@ async function executeToolInner(
2088
2122
  );
2089
2123
  }
2090
2124
 
2125
+ if (sandbox) {
2126
+ const { executeSandboxedShell, sandboxSummary } =
2127
+ await import("../lib/agent-sandbox.js");
2128
+ const result = executeSandboxedShell(args.command, sandbox, {
2129
+ cwd: args.cwd || cwd,
2130
+ timeout: _resolveShellTimeout(args.timeout),
2131
+ maxBuffer: 1024 * 1024,
2132
+ env: {
2133
+ CLAUDECODE: "1",
2134
+ ...(sessionId
2135
+ ? {
2136
+ CC_SESSION_ID: String(sessionId),
2137
+ CLAUDE_CODE_SESSION_ID: String(sessionId),
2138
+ }
2139
+ : {}),
2140
+ },
2141
+ });
2142
+ const common = {
2143
+ sandbox: sandboxSummary(sandbox),
2144
+ shellCommandPolicy: shellPolicy,
2145
+ approval: approvalOutcome,
2146
+ policyTrace: ["shell-policy", "approval", "sandbox"],
2147
+ };
2148
+ if (result.exitCode !== 0) {
2149
+ return attachDescriptor(
2150
+ {
2151
+ error: (
2152
+ result.stderr ||
2153
+ `Sandbox command exited with code ${result.exitCode}`
2154
+ ).substring(0, 2000),
2155
+ stdout: result.stdout.substring(0, 30000),
2156
+ stderr: result.stderr.substring(0, 2000),
2157
+ exitCode: result.exitCode,
2158
+ ...common,
2159
+ },
2160
+ override || runtimeDescriptor,
2161
+ );
2162
+ }
2163
+ return attachDescriptor(
2164
+ { stdout: result.stdout.substring(0, 30000), ...common },
2165
+ override || runtimeDescriptor,
2166
+ );
2167
+ }
2168
+
2091
2169
  try {
2092
2170
  const output = execSync(args.command, {
2093
2171
  cwd: args.cwd || cwd,
@@ -2348,6 +2426,59 @@ async function executeToolInner(
2348
2426
  }
2349
2427
  }
2350
2428
 
2429
+ case "slash_command": {
2430
+ try {
2431
+ const { getCommand, expandCommand, discoverCommands } =
2432
+ await import("../lib/slash-commands.js");
2433
+ const raw = String(args.command || "").trim();
2434
+ if (!raw) {
2435
+ return attachDescriptor({
2436
+ error: "slash_command requires a non-empty 'command'.",
2437
+ });
2438
+ }
2439
+ // Parse "/name arg1 arg2" exactly like a typed slash command (leading
2440
+ // '/' optional). First token is the command name, the rest are args.
2441
+ const [head, ...rest] = raw.replace(/^\//, "").split(/\s+/);
2442
+ const macro = head ? getCommand(head, cwd) : null;
2443
+ if (!macro) {
2444
+ const available = discoverCommands(cwd).map((c) => ({
2445
+ name: c.name,
2446
+ scope: c.scope,
2447
+ description: c.description || undefined,
2448
+ }));
2449
+ return attachDescriptor({
2450
+ error: `Unknown slash command "${head}".`,
2451
+ availableCommands: available,
2452
+ hint:
2453
+ available.length === 0
2454
+ ? "No user-defined slash commands found in .claude/commands/ or .chainlesschain/commands/."
2455
+ : "Call slash_command with one of availableCommands[].name.",
2456
+ });
2457
+ }
2458
+ // allowBang:false — the agent must not get an un-gated shell side
2459
+ // channel via a command file. $ARGUMENTS / @file (read) still expand;
2460
+ // any !`cmd` is left literal for the model to run via run_shell.
2461
+ const { prompt: expanded, warnings } = expandCommand(
2462
+ macro,
2463
+ rest.filter(Boolean),
2464
+ { cwd, allowBang: false },
2465
+ );
2466
+ return attachDescriptor({
2467
+ command: macro.name,
2468
+ scope: macro.scope,
2469
+ expandedPrompt: expanded,
2470
+ warnings: warnings && warnings.length ? warnings : undefined,
2471
+ instructions:
2472
+ "The expandedPrompt is the command's instructions. Carry them out " +
2473
+ "now using your normal tools.",
2474
+ });
2475
+ } catch (err) {
2476
+ return attachDescriptor({
2477
+ error: `slash_command failed: ${err.message}`,
2478
+ });
2479
+ }
2480
+ }
2481
+
2351
2482
  case "ask_user_question": {
2352
2483
  if (!interaction || typeof interaction.askUser !== "function") {
2353
2484
  return attachDescriptor({
@@ -4633,6 +4764,7 @@ export async function* agentLoop(messages, options) {
4633
4764
  // false so run_code keeps its existing per-permission-mode behavior.
4634
4765
  interactiveApproval: options.interactiveApproval || false,
4635
4766
  additionalDirectories: options.additionalDirectories || null,
4767
+ sandbox: options.sandbox || null,
4636
4768
  permissionRules: options.permissionRules || null,
4637
4769
  permissionConfirm: options.permissionConfirm || null,
4638
4770
  settingsHooks: options.settingsHooks || null,
@@ -5091,7 +5223,11 @@ export async function* agentLoop(messages, options) {
5091
5223
  });
5092
5224
  for (const { call, toolArgs, promise } of inflight) {
5093
5225
  throwIfAborted(signal);
5094
- yield { type: "tool-executing", tool: call.function.name, args: toolArgs };
5226
+ yield {
5227
+ type: "tool-executing",
5228
+ tool: call.function.name,
5229
+ args: toolArgs,
5230
+ };
5095
5231
  const { result: toolResult, error: toolError } = await promise;
5096
5232
  throwIfAborted(signal);
5097
5233
  const warningMsg = budget.toWarningMessage();
@@ -653,6 +653,43 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
653
653
  tags: ["tool:todo_write", "contract:coding-agent", "tier:extension"],
654
654
  },
655
655
  },
656
+ {
657
+ name: "slash_command",
658
+ title: "Slash Command",
659
+ kind: "planning",
660
+ tier: "extension",
661
+ description:
662
+ "Run a user-defined slash command (a reusable prompt macro from " +
663
+ ".claude/commands/*.md or .chainlesschain/commands/*.md) and get back its " +
664
+ "expanded prompt text to act on. The 'command' string is the slash " +
665
+ "command exactly as a user would type it, e.g. '/review' or " +
666
+ "'/git:commit fix the typo' — the leading slash is optional and trailing " +
667
+ "words become $ARGUMENTS / $1..$9. @file references are expanded; " +
668
+ "!`shell` snippets are NOT executed when invoked this way (run them via " +
669
+ "run_shell if needed). Available commands are listed in the system " +
670
+ "prompt; calling with an unknown command returns the list.",
671
+ inputSchema: {
672
+ type: "object",
673
+ properties: {
674
+ command: {
675
+ type: "string",
676
+ description:
677
+ "The slash command to run, as typed by a user, e.g. '/review' or " +
678
+ "'/git:commit fix the typo'. Leading '/' optional.",
679
+ },
680
+ },
681
+ required: ["command"],
682
+ },
683
+ ...TOOL_POLICY_METADATA.slash_command,
684
+ permissions: {
685
+ level: "readonly",
686
+ scopes: ["session:read"],
687
+ },
688
+ telemetry: {
689
+ category: "planning",
690
+ tags: ["tool:slash_command", "contract:coding-agent", "tier:extension"],
691
+ },
692
+ },
656
693
  {
657
694
  name: "ask_user_question",
658
695
  title: "Ask User Question",
@@ -149,6 +149,20 @@ const TOOL_POLICY_METADATA = Object.freeze({
149
149
  approvalFlow: "auto",
150
150
  isReadOnly: true,
151
151
  },
152
+ // Expand a user-defined slash command (.claude/commands/*.md) into its prompt
153
+ // text. Read-only by design: the agent path disables `!`cmd`` bang execution,
154
+ // so only $ARGUMENTS / @file (read) substitution runs — no un-gated shell.
155
+ // Plan-mode-safe (returns text the model then acts on via its normal tools).
156
+ slash_command: {
157
+ riskLevel: RISK_LEVELS.LOW,
158
+ category: TOOL_CATEGORIES.SKILL,
159
+ availableInPlanMode: true,
160
+ planModeBehavior: "allow",
161
+ requiresPlanApproval: false,
162
+ requiresConfirmation: false,
163
+ approvalFlow: "auto",
164
+ isReadOnly: true,
165
+ },
152
166
  search_sessions: {
153
167
  riskLevel: RISK_LEVELS.LOW,
154
168
  category: TOOL_CATEGORIES.SEARCH,
@@ -31,6 +31,33 @@ import {
31
31
 
32
32
  export const DEFAULT_MAX_BYTES = 100 * 1024; // 100 KB per referenced file
33
33
  export const DEFAULT_MAX_DIR_ENTRIES = 200;
34
+ // How deep a `@folder/` tree walks (the root's own entries are depth 1).
35
+ export const DEFAULT_MAX_DIR_DEPTH = 3;
36
+
37
+ // Directory names a `@folder/` tree lists but never DESCENDS into — heavy or
38
+ // noise trees that would burn the whole entry budget (a single `@./` would
39
+ // otherwise spend all 200 entries inside node_modules). They still appear as a
40
+ // node, marked "(not expanded)", so the structure stays honest.
41
+ const TREE_IGNORE_DIRS = new Set([
42
+ "node_modules",
43
+ ".git",
44
+ ".hg",
45
+ ".svn",
46
+ "dist",
47
+ "build",
48
+ "out",
49
+ ".next",
50
+ ".nuxt",
51
+ ".cache",
52
+ "coverage",
53
+ "__pycache__",
54
+ ".venv",
55
+ "venv",
56
+ ".idea",
57
+ ".gradle",
58
+ ".turbo",
59
+ "target",
60
+ ]);
34
61
 
35
62
  // `@` preceded by start / whitespace / opening bracket-quote, then a path run.
36
63
  // We capture greedily up to the next whitespace or a closing bracket-quote and
@@ -136,12 +163,58 @@ function readBoundedFile(fs, abs, limit) {
136
163
  }
137
164
  }
138
165
 
166
+ /**
167
+ * Walk a directory into a bounded, indented tree (Claude-Code `@folder/`
168
+ * parity — the model sees STRUCTURE, not just the top-level names). Depth- and
169
+ * entry-capped, and never descends into TREE_IGNORE_DIRS. Symlinks report
170
+ * isDirectory() === false (Dirent reads the link, not its target), so symlink
171
+ * loops can never recurse here. Returns { lines, total, truncated } where
172
+ * `total` is the node count actually emitted (`truncated` means more existed).
173
+ */
174
+ function buildDirTree(absRoot, { fs, path, maxEntries, maxDepth }) {
175
+ const lines = [];
176
+ let count = 0;
177
+ let truncated = false;
178
+
179
+ function walk(abs, depth) {
180
+ if (truncated) return;
181
+ let entries;
182
+ try {
183
+ entries = fs.readdirSync(abs, { withFileTypes: true });
184
+ } catch {
185
+ return; // an unreadable subdir is skipped silently (root errors upstream)
186
+ }
187
+ const sorted = entries
188
+ .map((e) => ({ name: e.name, isDir: e.isDirectory() }))
189
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
190
+ const indent = " ".repeat(depth - 1);
191
+ for (const e of sorted) {
192
+ if (count >= maxEntries) {
193
+ truncated = true;
194
+ return;
195
+ }
196
+ count++;
197
+ const ignored = e.isDir && TREE_IGNORE_DIRS.has(e.name);
198
+ const label = e.isDir ? `${e.name}/` : e.name;
199
+ lines.push(indent + label + (ignored ? " (not expanded)" : ""));
200
+ if (e.isDir && !ignored && depth < maxDepth) {
201
+ walk(path.resolve(abs, e.name), depth + 1);
202
+ }
203
+ }
204
+ }
205
+ walk(absRoot, 1);
206
+ return { lines, total: count, truncated };
207
+ }
208
+
139
209
  /**
140
210
  * Resolve a single raw token to an injectable ref descriptor, or null when it
141
211
  * does not resolve to an existing path. Tries the literal path first, then a
142
212
  * trailing-punctuation-stripped variant (handles "see @config.json.").
143
213
  */
144
- function resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries }) {
214
+ function resolveRef(
215
+ raw,
216
+ { cwd, fs, path, maxBytes, maxDirEntries, maxDirDepth },
217
+ ) {
145
218
  const candidates = [raw];
146
219
  const trimmed = trimTrailingPunct(raw);
147
220
  if (trimmed && trimmed !== raw) candidates.push(trimmed);
@@ -158,10 +231,12 @@ function resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries }) {
158
231
  continue; // not this candidate
159
232
  }
160
233
  if (stat.isDirectory()) {
161
- // A line range on a directory is meaningless — ignore it and list the dir.
162
- let entries;
234
+ // A line range on a directory is meaningless — ignore it and tree the dir.
235
+ // Probe the root once so an unreadable directory surfaces as a warning
236
+ // (buildDirTree silently skips unreadable SUBdirs, but the root the user
237
+ // explicitly pointed at deserves an error).
163
238
  try {
164
- entries = fs.readdirSync(abs, { withFileTypes: true });
239
+ fs.readdirSync(abs, { withFileTypes: true });
165
240
  } catch (err) {
166
241
  return {
167
242
  kind: "error",
@@ -170,18 +245,19 @@ function resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries }) {
170
245
  message: `cannot read directory: ${err.message}`,
171
246
  };
172
247
  }
173
- const names = entries
174
- .slice(0, maxDirEntries)
175
- .map((e) => (e.isDirectory() ? `${e.name}/` : e.name))
176
- .sort();
177
- const truncated = entries.length > maxDirEntries;
248
+ const tree = buildDirTree(abs, {
249
+ fs,
250
+ path,
251
+ maxEntries: maxDirEntries,
252
+ maxDepth: maxDirDepth,
253
+ });
178
254
  return {
179
255
  kind: "dir",
180
256
  raw: cand,
181
257
  rel: fsPath,
182
- entries: names,
183
- total: entries.length,
184
- truncated,
258
+ entries: tree.lines,
259
+ total: tree.total,
260
+ truncated: tree.truncated,
185
261
  };
186
262
  }
187
263
  if (stat.isFile()) {
@@ -320,7 +396,7 @@ function renderBlock(refs) {
320
396
  parts.push(`<dir ${attrs}>`);
321
397
  parts.push(ref.entries.join("\n"));
322
398
  if (ref.truncated) {
323
- parts.push(`… [truncated — ${ref.total} entries total]`);
399
+ parts.push(`… [truncated — listing capped at ${ref.total} entries]`);
324
400
  }
325
401
  parts.push("</dir>");
326
402
  } else if (ref.kind === "pdf") {
@@ -385,13 +461,23 @@ function _resolveAllRefs(text, opts) {
385
461
  const maxDirEntries = Number.isFinite(opts.maxDirEntries)
386
462
  ? opts.maxDirEntries
387
463
  : DEFAULT_MAX_DIR_ENTRIES;
464
+ const maxDirDepth = Number.isFinite(opts.maxDirDepth)
465
+ ? opts.maxDirDepth
466
+ : DEFAULT_MAX_DIR_DEPTH;
388
467
 
389
468
  const tokens = findFileRefTokens(text);
390
469
  const refs = [];
391
470
  const warnings = [];
392
471
 
393
472
  for (const { raw } of tokens) {
394
- const ref = resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries });
473
+ const ref = resolveRef(raw, {
474
+ cwd,
475
+ fs,
476
+ path,
477
+ maxBytes,
478
+ maxDirEntries,
479
+ maxDirDepth,
480
+ });
395
481
  if (!ref) {
396
482
  // Only warn for path-like tokens; bare @mentions are left alone.
397
483
  if (looksPathLike(raw)) {
@@ -280,18 +280,31 @@ export async function runAgentHeadless(options = {}, deps = {}) {
280
280
  // an `ask` falls closed (no human to confirm in headless). No file → null →
281
281
  // every existing risk-tier / shell-policy layer runs unchanged.
282
282
  let permissionRules = options.permissionRules || null;
283
- if (!permissionRules) {
284
- try {
285
- const { loadSettings } = await import("../lib/settings-loader.cjs");
286
- const loaded = loadSettings({ cwd, settingsFile: options.settingsFile });
283
+ let managedSettings = null;
284
+ try {
285
+ const { loadSettings, applyManagedPermissionPolicy } =
286
+ await import("../lib/settings-loader.cjs");
287
+ const loaded = loadSettings({
288
+ cwd,
289
+ settingsFile: options.settingsFile,
290
+ managedSettingsFile: options.managedSettingsFile,
291
+ });
292
+ managedSettings = loaded.managed;
293
+ if (!permissionRules) {
287
294
  const total =
288
295
  loaded.rules.allow.length +
289
296
  loaded.rules.ask.length +
290
297
  loaded.rules.deny.length;
291
298
  permissionRules = total > 0 ? loaded.rules : null;
292
- } catch {
293
- permissionRules = null; // fail-open
299
+ } else if (managedSettings) {
300
+ permissionRules = applyManagedPermissionPolicy(
301
+ permissionRules,
302
+ managedSettings,
303
+ );
294
304
  }
305
+ } catch (error) {
306
+ if (error?.code === "CC_MANAGED_SETTINGS_INVALID") throw error;
307
+ // Preserve caller-provided rules; absent settings keep legacy behavior.
295
308
  }
296
309
 
297
310
  // .claude/settings.json `hooks` block — decision-capable PreToolUse/
@@ -483,6 +496,11 @@ export async function runAgentHeadless(options = {}, deps = {}) {
483
496
  }
484
497
 
485
498
  // ── Permission + tool resolution ──────────────────────────────────────
499
+ if (managedSettings) {
500
+ const { assertManagedPermissionMode } =
501
+ await import("../lib/settings-loader.cjs");
502
+ assertManagedPermissionMode(options.permissionMode, managedSettings);
503
+ }
486
504
  const perm = resolvePermissionMode(options.permissionMode);
487
505
  const enabledToolNames = resolveEnabledTools({
488
506
  // An explicit --allowed-tools wins; otherwise a matched command's
@@ -705,6 +723,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
705
723
  mcp = await doResolve(
706
724
  {
707
725
  mcpConfigPath: options.mcpConfig || null,
726
+ managedSettingsFile: options.managedSettingsFile,
708
727
  db: db?.getDatabase?.() || null,
709
728
  includeRegistered: options.useRegisteredMcp !== false,
710
729
  // --strict-mcp-config: use ONLY the --mcp-config servers, ignoring
@@ -803,6 +822,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
803
822
  apiKey,
804
823
  cwd,
805
824
  additionalDirectories,
825
+ sandbox: options.sandbox || null,
806
826
  sessionId,
807
827
  autoCheckpoint: options.autoCheckpoint || false,
808
828
  checkpointSession: options.checkpointSession || sessionId,
@@ -529,18 +529,31 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
529
529
  // .claude/settings.json permission rules (deny > ask > allow); see
530
530
  // runAgentHeadless for the full semantics. null = no file → unchanged.
531
531
  let permissionRules = options.permissionRules || null;
532
- if (!permissionRules) {
533
- try {
534
- const { loadSettings } = await import("../lib/settings-loader.cjs");
535
- const loaded = loadSettings({ cwd, settingsFile: options.settingsFile });
532
+ let managedSettings = null;
533
+ try {
534
+ const { loadSettings, applyManagedPermissionPolicy } =
535
+ await import("../lib/settings-loader.cjs");
536
+ const loaded = loadSettings({
537
+ cwd,
538
+ settingsFile: options.settingsFile,
539
+ managedSettingsFile: options.managedSettingsFile,
540
+ });
541
+ managedSettings = loaded.managed;
542
+ if (!permissionRules) {
536
543
  const total =
537
544
  loaded.rules.allow.length +
538
545
  loaded.rules.ask.length +
539
546
  loaded.rules.deny.length;
540
547
  permissionRules = total > 0 ? loaded.rules : null;
541
- } catch {
542
- permissionRules = null; // fail-open
548
+ } else if (managedSettings) {
549
+ permissionRules = applyManagedPermissionPolicy(
550
+ permissionRules,
551
+ managedSettings,
552
+ );
543
553
  }
554
+ } catch (error) {
555
+ if (error?.code === "CC_MANAGED_SETTINGS_INVALID") throw error;
556
+ // Preserve caller-provided rules; absent settings keep legacy behavior.
544
557
  }
545
558
 
546
559
  // .claude/settings.json `hooks` block (decision-capable PreToolUse/PostToolUse).
@@ -617,6 +630,11 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
617
630
  return m.getApprovalGate();
618
631
  });
619
632
 
633
+ if (managedSettings) {
634
+ const { assertManagedPermissionMode } =
635
+ await import("../lib/settings-loader.cjs");
636
+ assertManagedPermissionMode(options.permissionMode, managedSettings);
637
+ }
620
638
  const perm = resolvePermissionMode(options.permissionMode);
621
639
  const enabledToolNames = resolveEnabledTools({
622
640
  allowedTools: options.allowedTools,
@@ -910,6 +928,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
910
928
  mcp = await doResolve(
911
929
  {
912
930
  mcpConfigPath: options.mcpConfig || null,
931
+ managedSettingsFile: options.managedSettingsFile,
913
932
  db: db?.getDatabase?.() || null,
914
933
  includeRegistered: options.useRegisteredMcp !== false,
915
934
  // --strict-mcp-config: only the --mcp-config servers (ignore
@@ -60,6 +60,37 @@ export function parseMcpServers(raw) {
60
60
  return out;
61
61
  }
62
62
 
63
+ function managedServerNames(value) {
64
+ if (!Array.isArray(value)) return null;
65
+ return new Set(
66
+ value
67
+ .map((entry) =>
68
+ typeof entry === "string" ? entry : entry?.name || entry?.serverName,
69
+ )
70
+ .filter(Boolean),
71
+ );
72
+ }
73
+
74
+ /** Apply organization MCP allow/deny policy to every server source. */
75
+ export function filterMcpServersByPolicy(servers, policy, writeErr = () => {}) {
76
+ if (!policy) return servers;
77
+ const allowed = managedServerNames(policy.allowedMcpServers);
78
+ const denied = managedServerNames(policy.deniedMcpServers) || new Set();
79
+ const requireAllowlist =
80
+ policy.allowManagedMcpServersOnly === true || allowed !== null;
81
+ const out = {};
82
+ for (const [name, config] of Object.entries(servers || {})) {
83
+ const blocked =
84
+ denied.has(name) || (requireAllowlist && !allowed?.has(name));
85
+ if (blocked) {
86
+ writeErr(` mcp: blocked server "${name}" by managed settings\n`);
87
+ continue;
88
+ }
89
+ out[name] = config;
90
+ }
91
+ return out;
92
+ }
93
+
63
94
  /** Namespaced tool name exposed to the LLM. */
64
95
  export function mcpToolName(server, tool) {
65
96
  return `mcp__${server}__${tool}`;
@@ -119,6 +150,7 @@ export function mcpAuthHint(url, errMessage) {
119
150
  export async function setupMcpFromConfig(servers, deps = {}) {
120
151
  const writeErr = deps.writeErr || (() => {});
121
152
  const createClient = deps.createClient || (() => new MCPClient());
153
+ servers = filterMcpServersByPolicy(servers, deps.mcpPolicy, writeErr);
122
154
 
123
155
  // `deps.into` lets a second batch (e.g. registered servers) accumulate into
124
156
  // the SAME client + channel objects as a first batch (e.g. --mcp-config), so
@@ -638,8 +670,20 @@ export async function resolveAgentMcp(args = {}, deps = {}) {
638
670
  const doJetbrains = deps.loadJetbrainsMcp || loadJetbrainsMcp;
639
671
  // Thread the agent session id down to setupMcpFromConfig so spawned stdio MCP
640
672
  // servers get CC_SESSION_ID / CLAUDE_CODE_SESSION_ID (Claude-Code parity).
641
- const fwd =
642
- args.sessionId != null ? { ...deps, sessionId: args.sessionId } : deps;
673
+ let mcpPolicy = deps.mcpPolicy || null;
674
+ if (!mcpPolicy) {
675
+ const { loadManagedSettings } = await import("../lib/settings-loader.cjs");
676
+ const loaded = loadManagedSettings({
677
+ env: args.env || process.env,
678
+ managedSettingsFile: args.managedSettingsFile,
679
+ });
680
+ mcpPolicy = loaded.settings;
681
+ }
682
+ const fwd = {
683
+ ...deps,
684
+ ...(args.sessionId != null ? { sessionId: args.sessionId } : {}),
685
+ mcpPolicy,
686
+ };
643
687
  let result = null;
644
688
  if (args.mcpConfigPath) {
645
689
  result = await doFile(args.mcpConfigPath, fwd); // fail-fast on bad file
@@ -29,6 +29,7 @@ export function resolveAgentPolicy({
29
29
  additionalDirectories: Array.isArray(overrides.additionalDirectories)
30
30
  ? overrides.additionalDirectories
31
31
  : [],
32
+ sandbox: overrides.sandbox || null,
32
33
  autoCheckpoint: overrides.autoCheckpoint === true,
33
34
  systemPrompt: overrides.systemPrompt || null,
34
35
  appendSystemPrompt: overrides.appendSystemPrompt || null,