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.
- package/bin/chainlesschain.js +5 -3
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-Dc6fWgoa.js → AIOps-DywJ7xTv.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DzMx25vJ.js → ActionButton-BTlqcY-q.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-eDC3KNht.js → Analytics-FdG1Mvwy.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-uA1TOtNp.js → AppLayout-CVsnbvN1.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Bq3QX9Cj.js → Audit-ITdpJ7vR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B1V3Jfyv.js → Backup-BfXqnXo1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DMFwddIF.js → BaseInput-C09ip3_E.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CKWQDH5j.js → Chat-qdkhB42l.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CStSBCnk.js → ChatBubbleRenderer-D2KfETZD.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DepIzI7G.js → Checkbox-j6WZCuff.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-ClanWll2.js → Codegen-CGZ-K3uK.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CFBzsL4W.js → Col-CIlPOAu6.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CaAyZ_Pt.js → Community-sY-i-hic.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-3dCHRSVe.js → Compact-DcoEfyL8.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-DkJmdQaU.js → Cowork-DLY_fkLv.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DKfNf6J2.js → Cron-D049pZBC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-COu1aQIO.js → Crosschain-DCXdE8M9.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CkAgFLkI.js → DID-JCvPZtjW.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfaKWZCU.js → Dashboard-Cg2xu3iE.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cjw97sdb.js → Dropdown-DjJLjbLh.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DROLQgOn.js → EmailListRenderer-DugNcSLR.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-o_cYzlLq.js → FamilyGuardDashboard-J6Nb-E30.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Boex3ljt.js → Federation-CQ3Ttpbl.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BQQ05T_3.js → FormItemContext-Cz0NKag4.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CdfL8PBd.js → GenericCardRenderer-NYcMP6iz.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CfZ07qJy.js → Git-B4hYN18N.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B21ISudq.js → Governance-DOQPCcC0.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CGIK4Im1.js → Inference-HtLF746W.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CI1kmfdT.js → KnowledgeGraph-BXrk8rMm.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Dpc1-__h.js → Logs-DS0JnD3-.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-BpMtMWyK.js → Marketplace-Cqnjihrz.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B94WvOOS.js → McpTools-Cx3Psk-U.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DODsAxZf.js → Memory-BjyysPtj.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C3Bjshem.js → MobileBridge-CYaITZ7w.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Bi8IHDv_.js → MobileProjects-Xwf-fdOQ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CH5BSuWG.js → Mtc-Djkql5m5.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DN3MzNtL.js → MtcAudit-D2BAZlu0.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CO6GWQOh.js → Multisig-ByqP1ZzH.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DhIr3V_u.js → NLProgramming-C_kn0nE2.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CNgrvZCJ.js → Notes-7l7eXHPq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Crr4LPjQ.js → NotificationSettings-BrCDoitw.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C8q-kK2z.js → OrderTableRenderer-CmtqIdwr.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-hWhCmeUn.js → Organization-DdoNAxsM.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-t_EBxns-.js → Overflow-Bw7R6dE3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-B8fqfwis.js → P2P-DJNtBugA.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-d-wlJw3R.js → PdhVaultBrowser-Dwhd5_ue.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Ch13YpBo.js → Permissions-BYoSfhPR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DW7LlLbS.js → PersonalDataHub-aYCYJbnH.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BHh3HqOQ.js → Pipeline-DJp6CprF.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DAhlJDSC.js → Privacy-Cq9HZweR.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRtddAM_.js → ProjectInit-CmF0HsSt.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CzR1EC7b.js → ProjectSettings-P8V5Aea2.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DSavX602.js → Projects-DPd5-lNS.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D55_kGZs.js → Providers-D589v0q2.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BR5qDIaT.js → QuickAsk-lvRV2osB.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CTFAM97K.js → Recommend-sb84MTpY.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-Z7pvEha9.js → Reputation-d9Y3vy-W.js} +1 -1
- package/src/assets/web-panel/assets/{Row-MqKcIsjP.js → Row-DMdhLUTp.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DdLDjRpk.js → RssFeed-OL-SMDkz.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CeOzh2hz.js → Search-BjStLKq1.js} +1 -1
- package/src/assets/web-panel/assets/{Security-gHrXaf4o.js → Security-MeI411qr.js} +3 -3
- package/src/assets/web-panel/assets/{Services-B8Y7pmY1.js → Services-UajosuhT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CRS0WNut.js → Skeleton-CGpG-QJ1.js} +1 -1
- package/src/assets/web-panel/assets/{Skills--a5_Pdxx.js → Skills-C6P2RPY-.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-EcTyL6EM.js → Sla-B_fPprgw.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-QpfvOiJr.js → SpeechSettings-CqxLjSlQ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DaLBBzlE.js → SyncSettings-DixSfFOQ.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Ca6xvO-I.js → Tasks-PavzPBxc.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CWguiCoE.js → Templates-BYdGRnfX.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGLLZulu.js → Tenant-BQLgnarD.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-CLjvjbmr.js → TimelineRenderer-Bv7uZRM7.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-De5mmw_a.js → Tokens-DxhgszRJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Chbq6U0N.js → Trigger-nQYHayuc.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CYxUO2lv.js → Trust-CvqZDuZ4.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-iyoZXXF6.js → UkeySign-DaR8GV2I.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CHg3ifJs.js → VideoEditing-C6Mpsokw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DfjqObsO.js → Wallet-B57VRrWc.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-0FI6xfud.js → WebAuthn-BbtEEtpq.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CJgGcctS.js → WorkflowEditor-C5LwvKmH.js} +1 -1
- package/src/assets/web-panel/assets/{chat-fH-E4vhY.js → chat-MzuPR5jh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuxsOD6d.js → colors-HyW8mi_y.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-AfVcFrFU.js → compact-item-DvUSzWAp.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DTsD7sPO.js → createContext-4MppZl7X.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DJR763M6.js → hasIn-D9q3CJ-u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhM9FZUH.js → index--Bm6OqmU.js} +1 -1
- package/src/assets/web-panel/assets/{index-OZwLoscJ.js → index-B5E4DhVc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CF8OSTin.js → index-B994UQfE.js} +1 -1
- package/src/assets/web-panel/assets/{index-9cryBBzh.js → index-BF2JsbiX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMVULNUC.js → index-BFGfC5RS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dj1_lz3M.js → index-BGQtFso0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBPAH7rp.js → index-BcunsBIx.js} +1 -1
- package/src/assets/web-panel/assets/{index-mLAfJXAf.js → index-Bl-E4W4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cfppli6E.js → index-BmCo2Xdp.js} +1 -1
- package/src/assets/web-panel/assets/{index-_hP8-Y2F.js → index-Btz8pU68.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvlzcX3Q.js → index-C0DiL97c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBBStCbO.js → index-C9I5MuJ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-SuJ2_OzM.js → index-CLAZs1Vd.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8DLT-F4.js → index-Caqtu6Et.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm4cHE5_.js → index-CgIfXOD9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbQX0Zbf.js → index-Ci6adKh8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEGMojUg.js → index-CifKUNtV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdV7a6-K.js → index-Cs-RKRUM.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dswd93W4.js → index-CucRo4Vz.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2KdGSMa.js → index-CvAqCAo9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqflTl_b.js → index-D2jrnaq3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cl7chKuM.js → index-D7TDMMfu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAbZ3dW9.js → index-DDZ6fP5Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BD4-kWQY.js → index-DFqvWeGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DejUsZOw.js → index-DNYqw0MO.js} +1 -1
- package/src/assets/web-panel/assets/index-D_oeGLr6.js +1 -0
- package/src/assets/web-panel/assets/{index-DH-dA4pG.js → index-DcMkjb92.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMcYdHhK.js → index-DmZ6-suL.js} +1 -1
- package/src/assets/web-panel/assets/{index-85iIi3qF.js → index-Dv7ffI2g.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJIFQ-Ay.js → index-Dvg5xYuP.js} +1 -1
- package/src/assets/web-panel/assets/index-Qa57YG19.js +1 -0
- package/src/assets/web-panel/assets/{index-DxD03TL7.js → index-SDblbKj6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmUxSTNy.js → index-UUMCA2Sq.js} +3 -3
- package/src/assets/web-panel/assets/{index-BJl6Ob-o.js → index-Zvtru2oE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPHYW3IK.js → index-eOQO6KnV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-rcowKd.js → index-m0b6Fj9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTtL2Qkb.js → index-ryFLxB1p.js} +1 -1
- package/src/assets/web-panel/assets/{index-fj3ZXgiy.js → index-xvUVuFnA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbpuFc8C.js → index-zLwYpvX0.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BGoLJP6T.js → initDefaultProps-CC--PMsC.js} +1 -1
- package/src/assets/web-panel/assets/{motion-23xdoVi0.js → motion-DdrBjRF1.js} +1 -1
- package/src/assets/web-panel/assets/{move-C3Bnnft-.js → move-D712Gwl2.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Dv0C3a90.js → mtc-parser-39y-keKO.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C3r9qzDO.js → omit-6Y51gDB9.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B4bJOcNp.js → pickAttrs-Cy6EHbq9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Dxf48wcO.js → placementArrow-BYxdAm0p.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Rq9U2hbz.js → responsiveObserve-C9R6q_cr.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BeWiqbdF.js → slide-CXUJlilB.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-yX9N1cEc.js → statusUtils-DOtrOeql.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-5Ko9NorO.js → styleChecker-D5r39o92.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WKpkTLok.js → useFlexGapSupport-DfAD5U4Z.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BV4X0JOG.js → useFs-BkrT-Zbc.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-g41wq_2p.js → usePersonalDataHub-BwnnnZjU.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BAurbOH8.js → vnode-SU-N4pum.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cl5XniRg.js → zoom-DHScfpTP.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/auth/npm-auth.js +65 -0
- package/src/command-manifest.json +1126 -0
- package/src/commands/agent.js +92 -0
- package/src/commands/agents.js +68 -0
- package/src/commands/note.js +2 -1
- package/src/commands/permissions.js +66 -10
- package/src/commands/plugin.js +50 -3
- package/src/commands/search.js +2 -1
- package/src/harness/worktree-isolator.js +14 -7
- package/src/index.js +4 -12
- package/src/lazy-dispatch.js +107 -0
- package/src/lib/agent-sandbox.js +72 -0
- package/src/lib/background-agent-supervisor.js +197 -0
- package/src/lib/code-review.js +72 -0
- package/src/lib/ensure-utf8.js +25 -7
- package/src/lib/knowledge-exporter.js +3 -2
- package/src/lib/permission-engine.js +1 -1
- package/src/lib/plugin-security.js +128 -0
- package/src/lib/pr-create.js +108 -0
- package/src/lib/publish-workspace.js +107 -0
- package/src/lib/resume-session.js +111 -0
- package/src/lib/session-manager.js +2 -1
- package/src/lib/settings-hooks.cjs +68 -25
- package/src/lib/settings-loader.cjs +115 -2
- package/src/lib/task-list.js +53 -0
- package/src/program-base.js +24 -0
- package/src/repl/agent-repl.js +108 -3
- package/src/repl/agents-status.js +8 -2
- package/src/repl/btw-command.js +56 -0
- package/src/repl/chat-repl.js +52 -1
- package/src/repl/hooks-status.js +1 -2
- package/src/repl/slash-command-registry.js +197 -0
- package/src/repl/think-command.js +31 -0
- package/src/runtime/agent-core.js +137 -1
- package/src/runtime/coding-agent-contract-shared.cjs +37 -0
- package/src/runtime/coding-agent-policy.cjs +14 -0
- package/src/runtime/file-ref-expander.js +100 -14
- package/src/runtime/headless-runner.js +26 -6
- package/src/runtime/headless-stream.js +25 -6
- package/src/runtime/mcp-config.js +46 -2
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/workers/background-agent-worker.js +60 -0
- package/src/assets/web-panel/assets/Compliance-BKYHHTdq.js +0 -1
- package/src/assets/web-panel/assets/Terminal-Bryzb1Rm.js +0 -3
- package/src/assets/web-panel/assets/devWarning-DffbPlEe.js +0 -1
- package/src/assets/web-panel/assets/index-BAHgOh3h.js +0 -1
- 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 {
|
|
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(
|
|
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
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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:
|
|
183
|
-
total:
|
|
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
|
|
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, {
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
}
|
|
293
|
-
permissionRules =
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
}
|
|
542
|
-
permissionRules =
|
|
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
|
-
|
|
642
|
-
|
|
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,
|