chainlesschain 0.162.159 → 0.162.160
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/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-ByIiF5bT.js → AIOps-CkRHjW8e.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-NlgSFZq5.js → ActionButton-BP3MeNi2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D17cOUcb.js → Analytics-CjbjQsOr.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B4mEZcbl.js → AppLayout-i0adH1M8.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CASsmyQw.js → Artifacts-CCCG2GOU.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DYAia2e2.js → Audit-DrsgvwHC.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C1lvYUSu.js → BackgroundAgents-DkXNkbZM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B6agK6bA.js → Backup-DarTWCex.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DM43R2vy.js → BaseInput-DaDzhcto.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Dtdp3eXp.js → Chat-C4EzGw6V.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BL5c_OQ1.js → ChatBubbleRenderer-lkavIWMS.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-D2xf7uZt.js → Checkbox-gddNgaQi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D3j45jJL.js → Codegen-BKldFWf9.js} +1 -1
- package/src/assets/web-panel/assets/{Col-voPJ9fUZ.js → Col-ChR-brDs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DEsyTESF.js → Community-DlHtZX86.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-OHV_QXLD.js → Compact-BAhfcBpC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-eDTJWC-J.js → Compliance-gIrKkm6m.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BBvy-6d1.js → Cowork-BYnzLBO3.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Dw2eoJxu.js → Cron-ByUDIljI.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DQHms_Bh.js → Crosschain-DOdNbHFp.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cyu7Id1c.js → DID-BJHaNWD2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-sIDzkG92.js → Dashboard-Drm_tClz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DUW3T2ey.js → Dropdown-CA1WLzGN.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CECELXW8.js → EmailListRenderer-Di1SKjNO.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BIZR37ov.js → FamilyGuardDashboard-44Ve7ZFe.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BEZIQX84.js → Federation-CubPwv_N.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BcdUMznL.js → FormItemContext-CsUFe5bU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DsN_fr5H.js → GenericCardRenderer-D7xRjPjZ.js} +1 -1
- package/src/assets/web-panel/assets/{Git-63LhjBx2.js → Git-D-Qx9dhO.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYtQtIcH.js → Governance-_YMYaU4n.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BwR6UUox.js → Inference-J7Yzh-K_.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-D9lVNTUA.js → KnowledgeGraph-D37iI_Mo.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C4q5h7Ut.js → Logs-oMTSYfGY.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DexLlXfN.js → MarkdownRenderer-BqaPmC0W.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Plksliuw.js → Marketplace-DKUqYDaF.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DZUFfeuG.js → McpTools-BEv3Gx61.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-NoDYt29V.js → Memory-CtA8XD2y.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CEXGTkdh.js → MobileBridge-ug7c1mMj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DTAa0cwy.js → Mtc-WJaMs4i2.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CVV7LnNS.js → MtcAudit-DXnK_-ru.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BqqGrjt_.js → Multisig-C4Ev7726.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C4WP8DRp.js → NLProgramming-BMFb9_zk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Zo4DJUVI.js → Notes-Bl93nvBX.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DBcjRaz2.js → NotificationSettings-BN2W5cl6.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dxc9oN4o.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BT9WPBOX.js → Organization-CAs48637.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D6b4bZ3q.js → Overflow-Bhq27Zoc.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cw3OsYx-.js → P2P-D61-2542.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C0nyiK2b.js → PdhVaultBrowser-C1j4iUxz.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-zVBZEB_C.js → Permissions-rJ2-fScM.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BOspsFew.js → PersonalDataHub-DFIlTok6.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CQWfZ4XH.js → Pipeline-BjIgEWoZ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C2oGJ_WL.js → Privacy-BQoH5wcX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BZMR4qY1.js → ProjectInit-CYR_-_Ub.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DAujoP63.js → ProjectSettings-nkatCq-9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +1 -0
- package/src/assets/web-panel/assets/{Providers-D_RGHM-v.js → Providers-BnKM9fsF.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Mf6KfL2J.js → QrScannerModal-Cfe82dbz.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-5D8URoI_.js → QuickAsk-r1RbPufV.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Df4kC8Gy.js → Recommend-CjCbM2WE.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DGi3f3vO.js → RemoteSession-BuMGPH6V.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-jmkGFNWb.js → Reputation-Bmj3NY1F.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DP6Pcu3m.js → Row-BIsNcE_1.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D60hSuPO.js → RssFeed-BObmojD2.js} +3 -3
- package/src/assets/web-panel/assets/{Search-0mf1BQUK.js → Search-BRmbkQNS.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CfZtLFYV.js → Security-_rF3DRpY.js} +4 -4
- package/src/assets/web-panel/assets/{Services-D9zpZLWz.js → Services-D0If9ZV4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D1_44eMD.js → Skeleton-o7zIksUg.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-cVFa8zkz.js → Skills-oUR7zi9u.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cinpzc3L.js → Sla-fqKyGi03.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-bqdiJE_P.js → SpeechSettings-B1wZ_gxx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgl_SLgj.js → SyncSettings-B3qJWYXq.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DawWAEhy.js → Tasks-BH5_dUfr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Ca0nY651.js → Templates-CCIVpjRP.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CgaGEhLm.js → Tenant-2GZtTGnB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D-9EnpRx.js → Terminal-BlMkfs_u.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CSOxDHZ6.js → TimelineRenderer-U6hi5OMc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpwLZpL3.js → Tokens-C-_BerGJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DgowwBK5.js → Trigger-DaKzBgN3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BZutapjE.js → Trust-CLvcsco3.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-QZPt8_Sp.js → UkeySign-BFn_Y1zZ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DlQx5UDJ.js → VideoEditing-B0V18dLT.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ZfdTgtWA.js → Wallet-BbmP7Imu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CAp0y_Cr.js → WebAuthn-Dewj_0kt.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BVITZEmr.js → WorkflowEditor-DOxz3Lwa.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CC2tPaNg.js → chat-B1OIpZnh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-31brc9CA.js → colors-CSGNVdri.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DLMkC9C-.js → compact-item-8S0vcg0u.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BMtveI6F.js → createContext-vZyc27TN.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DbD6WHW2.js → hasIn-DWw3-UhA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm0byjm1.js → index-B4pP2ycr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXa-4zd.js → index-BDHyovnK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_eUKGYP.js → index-BKEmQQvR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOM-VFlv.js → index-BQssf0EQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-bdaJwTXJ.js → index-BZnauCqN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cwbqh0KZ.js → index-BdyokEau.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJvxFyIz.js → index-BeBv896m.js} +1 -1
- package/src/assets/web-panel/assets/{index-DndNnYc0.js → index-BjC-TrOb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWgfipcC.js → index-BkWd6YL7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7cV0tu0.js → index-BoIPHP4R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcVszn2b.js → index-BuB5P0TR.js} +1 -1
- package/src/assets/web-panel/assets/{index-COHHQ9cU.js → index-BwKrTVV0.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1PfeI88.js → index-C6r27A-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cod97Urs.js → index-CB2MOS_8.js} +3 -3
- package/src/assets/web-panel/assets/{index-CEwjBA9q.js → index-CFQWZvak.js} +1 -1
- package/src/assets/web-panel/assets/{index-D67kIagl.js → index-CI3slBKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-jG6h22Qg.js → index-CJgKP2cm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZl-UXB6.js → index-CLPr2vC_.js} +1 -1
- package/src/assets/web-panel/assets/{index-ntVQqSML.js → index-CbZqw7pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-cGlCDW.js → index-CfEvacz7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFRLmCR3.js → index-CwXBblm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-AaIKG60c.js → index-D1i_8oop.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPpS-lGb.js → index-D8qXVfoV.js} +1 -1
- package/src/assets/web-panel/assets/{index-WQUO2vvK.js → index-DKwl1AkQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUmLMFsy.js → index-DLAvUtOH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbc2JeQG.js → index-DLgkAhEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKAB6-6V.js → index-DPgY5GLy.js} +1 -1
- package/src/assets/web-panel/assets/{index-ALvoNTRF.js → index-Dc40pX3F.js} +1 -1
- package/src/assets/web-panel/assets/{index-ep6JchNG.js → index-DcfLXAW7.js} +1 -1
- package/src/assets/web-panel/assets/{index-C21pdBqs.js → index-DeCS12Mh.js} +1 -1
- package/src/assets/web-panel/assets/{index-3oBsXZmF.js → index-Dg0Izipr.js} +1 -1
- package/src/assets/web-panel/assets/index-DmVFfcNR.js +1 -0
- package/src/assets/web-panel/assets/{index-BnFLzejT.js → index-DtHWHJnj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7LHNw_N.js → index-Q6oyLnKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoO4WEJB.js → index-dxocDVD_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiRQUoyE.js → index-t-q3rkVm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFXKbOek.js → index-tr1x96W9.js} +1 -1
- package/src/assets/web-panel/assets/index-x7YyhMUa.js +1 -0
- package/src/assets/web-panel/assets/{index-CJhREOlG.js → index-xx5M5Rbj.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D5UoBRhN.js → initDefaultProps-Di5YFId0.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DltOw0CR.js → motion-BVSN0aeL.js} +1 -1
- package/src/assets/web-panel/assets/{move-_oPkk5zj.js → move-DHNiaP2l.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-EES8ROCS.js → mtc-parser-xJPf3gQD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B-_39x_M.js → omit-C7qzUdkN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-l6D0yJHz.js → pickAttrs-pSpjg3Le.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DIeUK31L.js → placementArrow-cs_jL_UC.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dG4EzUQo.js → responsiveObserve-DkS_Zs4r.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWFzYGdH.js → slide-B5O1Kpxo.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CECdTA5H.js → statusUtils-BX02GnZp.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D6kP-mMK.js → styleChecker-EmBYRftd.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BWfna_rd.js → useFlexGapSupport-ZGzN1cU8.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bb-j6rzo.js → useFs-CfP7M1m7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D5OIsYM4.js → usePersonalDataHub-CbIPHgNh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNpwzFTM.js → vnode-7xh0If_O.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-vNLpFQBW.js → zoom-DmitvTR2.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +65 -6
- package/src/commands/background-session.js +30 -0
- package/src/commands/mcp.js +46 -0
- package/src/commands/session.js +70 -0
- package/src/data/changelog.json +7 -2
- package/src/harness/jsonl-session-store.js +70 -1
- package/src/harness/mcp-client.js +50 -0
- package/src/lib/agent-sandbox.js +68 -0
- package/src/lib/agents.js +12 -4
- package/src/lib/api-key-helper.js +62 -0
- package/src/lib/background-agent-supervisor.js +32 -0
- package/src/lib/dependency-install-policy.js +130 -0
- package/src/lib/exit-codes.cjs +77 -0
- package/src/lib/headless-manifest.js +175 -0
- package/src/lib/hook-runner.cjs +70 -4
- package/src/lib/ide-context-redaction.js +130 -0
- package/src/lib/ide-context.js +89 -13
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/llm-config-defaults.js +24 -8
- package/src/lib/project-mcp-trust.js +87 -0
- package/src/lib/sensitive-file-guard.js +51 -0
- package/src/lib/sub-agent-context.js +7 -3
- package/src/runtime/agent-core.js +102 -6
- package/src/runtime/coding-agent-contract-shared.cjs +19 -2
- package/src/runtime/headless-runner.js +66 -7
- package/src/runtime/headless-stream.js +29 -1
- package/src/runtime/mcp-config.js +32 -1
- package/src/assets/web-panel/assets/MobileProjects-DnbJkbZo.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +0 -1
- package/src/assets/web-panel/assets/Projects-DvH_P2Uq.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +0 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +0 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +0 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE-context redaction — the implicit-context counterpart of the credential
|
|
3
|
+
* READ guard ([[credential-guard.js]]).
|
|
4
|
+
*
|
|
5
|
+
* The IDE bridge shares editor state (selection text, open-editor tabs,
|
|
6
|
+
* terminal output, diagnostics) with every prompt WITHOUT any tool-permission
|
|
7
|
+
* gate: nothing was "read" through read_file, so denyRead rules and the
|
|
8
|
+
* credential guard never fire. An open `.env` tab or an `export API_KEY=…`
|
|
9
|
+
* still visible in the integrated terminal would ride straight into model
|
|
10
|
+
* context. This module closes that side door:
|
|
11
|
+
*
|
|
12
|
+
* 1. `filterIdeFileEntry` — file-path entries (open editors, selection file,
|
|
13
|
+
* diagnostics files) that name a credential file are dropped, reusing the
|
|
14
|
+
* exact same `credentialFileReason` patterns the read guard enforces. The
|
|
15
|
+
* non-resolved variant is deliberate: these paths come from the IDE, may
|
|
16
|
+
* not exist locally, and a pure string check never touches the fs.
|
|
17
|
+
* 2. `redactSecretsInText` — free text (selection, terminal output,
|
|
18
|
+
* diagnostic messages) is scrubbed with a conservative set of secret
|
|
19
|
+
* shapes: AWS access-key ids, Bearer tokens, PEM private-key blocks,
|
|
20
|
+
* well-known token prefixes, and `SECRET_NAME = value` assignments (only
|
|
21
|
+
* the VALUE is swallowed — key names and ordinary code survive).
|
|
22
|
+
*
|
|
23
|
+
* Precision over recall, same posture as the guards: a redactor that mangles
|
|
24
|
+
* everyday code teaches users to turn it off. `CC_IDE_CONTEXT_REDACTION=0`
|
|
25
|
+
* disables (default ON).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
credentialFileReason,
|
|
30
|
+
_internals as credentialGuardInternals,
|
|
31
|
+
} from "./credential-guard.js";
|
|
32
|
+
|
|
33
|
+
const REDACTED = "[REDACTED]";
|
|
34
|
+
const REDACTED_FILE = "[redacted credential file]";
|
|
35
|
+
|
|
36
|
+
// Boundary-delimited secret words (KEY / TOKEN / SECRET / PASSWORD / …) —
|
|
37
|
+
// shared with the credential guard so both surfaces agree on what "looks
|
|
38
|
+
// secret".
|
|
39
|
+
const SECRET_VAR_RE = credentialGuardInternals.SECRET_VAR_RE;
|
|
40
|
+
|
|
41
|
+
// AWS access-key ids are globally unique, fixed-shape strings.
|
|
42
|
+
const AWS_ACCESS_KEY_RE = /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g;
|
|
43
|
+
|
|
44
|
+
// `Bearer <token>` in headers / curl lines / pasted requests.
|
|
45
|
+
const BEARER_RE = /\b(Bearer)\s+[A-Za-z0-9\-._~+/=]{8,}/g;
|
|
46
|
+
|
|
47
|
+
// PEM private-key blocks — the bytes ARE the secret. A block whose END marker
|
|
48
|
+
// was truncated off still redacts to the end of the text (fail toward safety).
|
|
49
|
+
const PEM_PRIVATE_KEY_RE =
|
|
50
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g;
|
|
51
|
+
|
|
52
|
+
// High-precision vendor token prefixes (OpenAI/Anthropic sk-, GitHub PATs,
|
|
53
|
+
// GitLab PATs, Slack xox*) — unambiguous even outside an assignment.
|
|
54
|
+
const TOKEN_PREFIX_RE =
|
|
55
|
+
/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g;
|
|
56
|
+
|
|
57
|
+
// `API_KEY = "value"` / `token: value` / JSON `"secret": "value"` — the key
|
|
58
|
+
// must contain a boundary-delimited secret word, the value must be a bare
|
|
59
|
+
// token-like run (≥8 chars, no spaces). The trailing lookahead rejects values
|
|
60
|
+
// that continue into a call — `token = getToken()` is code, not a secret.
|
|
61
|
+
// Only the value is replaced; the key and separator are kept so the agent
|
|
62
|
+
// still sees WHAT was configured, just not the secret itself.
|
|
63
|
+
const ASSIGNMENT_RE =
|
|
64
|
+
/\b([A-Za-z_][A-Za-z0-9_-]*)(["']?\s*[=:]\s*)(["']?)([A-Za-z0-9+/=_.\-]{8,})\3(?![A-Za-z0-9+/=_.\-(])/g;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Env kill-switch: CC_IDE_CONTEXT_REDACTION=0|false|no|off disables all IDE
|
|
68
|
+
* context redaction. Default ON — only an explicit opt-out turns it off.
|
|
69
|
+
*/
|
|
70
|
+
export function ideRedactionEnabled(env = process.env) {
|
|
71
|
+
const raw = env && env.CC_IDE_CONTEXT_REDACTION;
|
|
72
|
+
return !(raw != null && /^(0|false|no|off)$/i.test(String(raw).trim()));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Gate one IDE-supplied file-path entry (open-editor tab, selection file,
|
|
77
|
+
* diagnostic file). Returns the path unchanged when it is fine to share, or
|
|
78
|
+
* null when it names a credential/secret file and the whole entry must be
|
|
79
|
+
* dropped (or replaced with the `REDACTED_FILE_PLACEHOLDER`).
|
|
80
|
+
*
|
|
81
|
+
* Pure string check by design (non-resolved `credentialFileReason`): IDE paths
|
|
82
|
+
* may be remote/virtual, and a context filter must never do fs I/O. `cwd` is
|
|
83
|
+
* accepted for signature parity with the resolved guards but unused here.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} filePath
|
|
86
|
+
* @param {object} [opts] { cwd?, env? }
|
|
87
|
+
* @returns {string|null}
|
|
88
|
+
*/
|
|
89
|
+
export function filterIdeFileEntry(filePath, opts = {}) {
|
|
90
|
+
const p = String(filePath || "");
|
|
91
|
+
if (!p) return filePath ?? null;
|
|
92
|
+
if (!ideRedactionEnabled(opts.env || process.env)) return filePath;
|
|
93
|
+
return credentialFileReason(p) ? null : filePath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Scrub secret-shaped substrings out of IDE-supplied free text (selection
|
|
98
|
+
* text, terminal output, diagnostic messages). Non-strings pass through
|
|
99
|
+
* untouched; with the kill-switch off the text is returned as-is.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} text
|
|
102
|
+
* @param {object} [opts] { env? }
|
|
103
|
+
* @returns {string}
|
|
104
|
+
*/
|
|
105
|
+
export function redactSecretsInText(text, opts = {}) {
|
|
106
|
+
if (typeof text !== "string" || text.length === 0) return text;
|
|
107
|
+
if (!ideRedactionEnabled(opts.env || process.env)) return text;
|
|
108
|
+
let out = text;
|
|
109
|
+
out = out.replace(PEM_PRIVATE_KEY_RE, REDACTED);
|
|
110
|
+
out = out.replace(BEARER_RE, (_m, word) => `${word} ${REDACTED}`);
|
|
111
|
+
out = out.replace(AWS_ACCESS_KEY_RE, REDACTED);
|
|
112
|
+
out = out.replace(TOKEN_PREFIX_RE, REDACTED);
|
|
113
|
+
out = out.replace(ASSIGNMENT_RE, (m, key, sep, quote, _value) => {
|
|
114
|
+
// Hyphens count as segment boundaries too (api-key ≙ api_key).
|
|
115
|
+
if (!SECRET_VAR_RE.test(key.replace(/-/g, "_"))) return m;
|
|
116
|
+
return `${key}${sep}${quote}${REDACTED}${quote}`;
|
|
117
|
+
});
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const REDACTED_MARKER = REDACTED;
|
|
122
|
+
export const REDACTED_FILE_PLACEHOLDER = REDACTED_FILE;
|
|
123
|
+
|
|
124
|
+
export const _internals = {
|
|
125
|
+
AWS_ACCESS_KEY_RE,
|
|
126
|
+
BEARER_RE,
|
|
127
|
+
PEM_PRIVATE_KEY_RE,
|
|
128
|
+
TOKEN_PREFIX_RE,
|
|
129
|
+
ASSIGNMENT_RE,
|
|
130
|
+
};
|
package/src/lib/ide-context.js
CHANGED
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { simpleDiff } from "./note-versioning.js";
|
|
21
|
+
import {
|
|
22
|
+
filterIdeFileEntry,
|
|
23
|
+
redactSecretsInText,
|
|
24
|
+
} from "./ide-context-redaction.js";
|
|
21
25
|
|
|
22
26
|
/** Hard cap on the selected text we inline into the prompt. */
|
|
23
27
|
const SELECTION_TEXT_CAP = 2000;
|
|
@@ -86,6 +90,61 @@ export function parseToolResultJson(result) {
|
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
// ─── Redaction seams (see [[ide-context-redaction.js]]) ─────────────────────
|
|
94
|
+
// IDE context bypasses the tool-permission layer entirely (nothing goes
|
|
95
|
+
// through read_file), so credential files and secret-shaped text are filtered
|
|
96
|
+
// HERE, at the collection boundary, before anything reaches the prompt.
|
|
97
|
+
// CC_IDE_CONTEXT_REDACTION=0 disables (the filters below become pass-through).
|
|
98
|
+
|
|
99
|
+
/** Drop the selection when it lives in a credential file; scrub its text. */
|
|
100
|
+
function sanitizeIdeSelection(sel, env) {
|
|
101
|
+
if (!sel) return sel;
|
|
102
|
+
if (sel.file && filterIdeFileEntry(sel.file, { env }) === null) return null;
|
|
103
|
+
if (typeof sel.text === "string" && sel.text.length > 0) {
|
|
104
|
+
const scrubbed = redactSecretsInText(sel.text, { env });
|
|
105
|
+
if (scrubbed !== sel.text) return { ...sel, text: scrubbed };
|
|
106
|
+
}
|
|
107
|
+
return sel;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Drop open-editor entries whose file is a credential file. */
|
|
111
|
+
function sanitizeIdeEditors(editors, env) {
|
|
112
|
+
if (!Array.isArray(editors)) return editors;
|
|
113
|
+
return editors.filter(
|
|
114
|
+
(e) => !(e && e.file && filterIdeFileEntry(e.file, { env }) === null),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Scrub secret-shaped text out of terminal commands and their output. */
|
|
119
|
+
function sanitizeIdeTerminals(terms, env) {
|
|
120
|
+
if (!Array.isArray(terms)) return terms;
|
|
121
|
+
return terms.map((t) => {
|
|
122
|
+
if (!t || typeof t !== "object") return t;
|
|
123
|
+
const out = { ...t };
|
|
124
|
+
if (typeof out.command === "string") {
|
|
125
|
+
out.command = redactSecretsInText(out.command, { env });
|
|
126
|
+
}
|
|
127
|
+
if (typeof out.output === "string") {
|
|
128
|
+
out.output = redactSecretsInText(out.output, { env });
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Drop diagnostics for credential files; scrub their message text. */
|
|
135
|
+
function sanitizeIdeDiagnostics(diags, env) {
|
|
136
|
+
if (!Array.isArray(diags)) return diags;
|
|
137
|
+
return diags
|
|
138
|
+
.filter(
|
|
139
|
+
(d) => !(d && d.file && filterIdeFileEntry(d.file, { env }) === null),
|
|
140
|
+
)
|
|
141
|
+
.map((d) =>
|
|
142
|
+
d && typeof d.message === "string"
|
|
143
|
+
? { ...d, message: redactSecretsInText(d.message, { env }) }
|
|
144
|
+
: d,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
89
148
|
/** Resolve to the promise's value, or null after `ms` / on rejection. */
|
|
90
149
|
function withTimeout(promise, ms) {
|
|
91
150
|
return new Promise((resolve) => {
|
|
@@ -137,18 +196,22 @@ export async function collectIdeContext(mcp, opts = {}) {
|
|
|
137
196
|
? call("mcp__ide__getTerminalOutput", { limit: TERMINAL_LIMIT })
|
|
138
197
|
: Promise.resolve(null),
|
|
139
198
|
]);
|
|
140
|
-
const
|
|
141
|
-
const
|
|
199
|
+
const env = opts.env || process.env;
|
|
200
|
+
const sel = sanitizeIdeSelection(selection || null, env);
|
|
201
|
+
let openEditors = Array.isArray(editors?.editors) ? editors.editors : null;
|
|
202
|
+
if (openEditors) openEditors = sanitizeIdeEditors(openEditors, env);
|
|
203
|
+
let terminals = Array.isArray(terminalData?.terminals)
|
|
142
204
|
? terminalData.terminals
|
|
143
205
|
: null;
|
|
206
|
+
if (terminals) terminals = sanitizeIdeTerminals(terminals, env);
|
|
144
207
|
if (
|
|
145
|
-
!
|
|
208
|
+
!sel &&
|
|
146
209
|
!(openEditors && openEditors.length > 0) &&
|
|
147
210
|
!(terminals && terminals.length > 0)
|
|
148
211
|
) {
|
|
149
212
|
return null;
|
|
150
213
|
}
|
|
151
|
-
return { selection:
|
|
214
|
+
return { selection: sel, openEditors, terminals };
|
|
152
215
|
}
|
|
153
216
|
|
|
154
217
|
/**
|
|
@@ -304,8 +367,11 @@ export async function collectIdeDiagnostics(mcp, filePath, opts = {}) {
|
|
|
304
367
|
);
|
|
305
368
|
const all = Array.isArray(data?.diagnostics) ? data.diagnostics : null;
|
|
306
369
|
if (!all) return null;
|
|
307
|
-
const relevant =
|
|
308
|
-
|
|
370
|
+
const relevant = sanitizeIdeDiagnostics(
|
|
371
|
+
all.filter(
|
|
372
|
+
(d) => d && DIAG_SEVERITIES.has(String(d.severity).toLowerCase()),
|
|
373
|
+
),
|
|
374
|
+
env,
|
|
309
375
|
);
|
|
310
376
|
return relevant.length > 0 ? relevant : null;
|
|
311
377
|
}
|
|
@@ -653,6 +719,7 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
|
|
|
653
719
|
const mentions = findIdeMentions(src);
|
|
654
720
|
if (mentions.length === 0) return out;
|
|
655
721
|
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
722
|
+
const env = opts.env || process.env;
|
|
656
723
|
const blocks = [];
|
|
657
724
|
for (const kind of mentions) {
|
|
658
725
|
try {
|
|
@@ -663,11 +730,9 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
|
|
|
663
730
|
);
|
|
664
731
|
continue;
|
|
665
732
|
}
|
|
666
|
-
const sel =
|
|
667
|
-
mcp,
|
|
668
|
-
|
|
669
|
-
{},
|
|
670
|
-
timeoutMs,
|
|
733
|
+
const sel = sanitizeIdeSelection(
|
|
734
|
+
await callIdeToolJson(mcp, "mcp__ide__getSelection", {}, timeoutMs),
|
|
735
|
+
env,
|
|
671
736
|
);
|
|
672
737
|
const b = formatSelectionMention(sel);
|
|
673
738
|
if (b) {
|
|
@@ -689,7 +754,14 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
|
|
|
689
754
|
{},
|
|
690
755
|
timeoutMs,
|
|
691
756
|
);
|
|
692
|
-
const
|
|
757
|
+
const safe =
|
|
758
|
+
data && Array.isArray(data.diagnostics)
|
|
759
|
+
? {
|
|
760
|
+
...data,
|
|
761
|
+
diagnostics: sanitizeIdeDiagnostics(data.diagnostics, env),
|
|
762
|
+
}
|
|
763
|
+
: data;
|
|
764
|
+
const b = formatDiagnosticsMention(safe);
|
|
693
765
|
if (b) {
|
|
694
766
|
blocks.push(b);
|
|
695
767
|
out.expanded.push("diagnostics");
|
|
@@ -709,7 +781,11 @@ export async function expandIdeMentions(text, mcp, opts = {}) {
|
|
|
709
781
|
{ limit: TERMINAL_MENTION_LIMIT },
|
|
710
782
|
timeoutMs,
|
|
711
783
|
);
|
|
712
|
-
const
|
|
784
|
+
const safe =
|
|
785
|
+
data && Array.isArray(data.terminals)
|
|
786
|
+
? { ...data, terminals: sanitizeIdeTerminals(data.terminals, env) }
|
|
787
|
+
: data;
|
|
788
|
+
const b = formatTerminalMention(safe);
|
|
713
789
|
if (b) {
|
|
714
790
|
blocks.push(b);
|
|
715
791
|
out.expanded.push("terminal");
|
|
@@ -38,24 +38,40 @@ export async function reconcileConfigLlmProvider(cfgLlm = {}) {
|
|
|
38
38
|
if (!cfgLlm || !cfgLlm.provider || !cfgLlm.baseUrl) {
|
|
39
39
|
return { llm: cfgLlm, changed: false };
|
|
40
40
|
}
|
|
41
|
-
const { inferProviderFromBaseUrl, modelForeignToProvider } =
|
|
42
|
-
"./runnable-provider.js"
|
|
43
|
-
);
|
|
41
|
+
const { inferProviderFromBaseUrl, modelForeignToProvider } =
|
|
42
|
+
await import("./runnable-provider.js");
|
|
44
43
|
const inferred = inferProviderFromBaseUrl(cfgLlm.baseUrl);
|
|
45
44
|
if (!inferred || inferred === cfgLlm.provider) {
|
|
46
45
|
return { llm: cfgLlm, changed: false };
|
|
47
46
|
}
|
|
48
47
|
const corrected = { ...cfgLlm, provider: inferred };
|
|
49
48
|
if (modelForeignToProvider(inferred, cfgLlm.model)) {
|
|
50
|
-
const { selectModelForTask, TaskType } =
|
|
51
|
-
"./task-model-selector.js"
|
|
52
|
-
);
|
|
49
|
+
const { selectModelForTask, TaskType } =
|
|
50
|
+
await import("./task-model-selector.js");
|
|
53
51
|
const def = selectModelForTask(inferred, TaskType.CHAT);
|
|
54
52
|
if (def) corrected.model = def;
|
|
55
53
|
}
|
|
56
54
|
return { llm: corrected, changed: true };
|
|
57
55
|
}
|
|
58
56
|
|
|
57
|
+
import { resolveApiKeyFromHelper } from "./api-key-helper.js";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Key backfill order: config llm.apiKey (plaintext, legacy) first, then
|
|
61
|
+
* llm.apiKeyHelper (external command → OS credential store). The helper only
|
|
62
|
+
* runs when no key is otherwise present, so configured setups never pay a
|
|
63
|
+
* subprocess spawn.
|
|
64
|
+
*/
|
|
65
|
+
function backfillApiKey(options, cfgLlm) {
|
|
66
|
+
if (options.apiKey) return;
|
|
67
|
+
if (cfgLlm.apiKey) {
|
|
68
|
+
options.apiKey = cfgLlm.apiKey;
|
|
69
|
+
} else if (cfgLlm.apiKeyHelper) {
|
|
70
|
+
const key = resolveApiKeyFromHelper(cfgLlm.apiKeyHelper);
|
|
71
|
+
if (key) options.apiKey = key;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
60
76
|
// The IDE chat panel pins --provider/--model (read from config) but DROPS
|
|
61
77
|
// --base-url/--api-key. With an explicit --provider the block below bails, so
|
|
@@ -71,7 +87,7 @@ export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
|
71
87
|
options.provider === cfgLlm.provider
|
|
72
88
|
) {
|
|
73
89
|
if (!options.baseUrl && cfgLlm.baseUrl) options.baseUrl = cfgLlm.baseUrl;
|
|
74
|
-
|
|
90
|
+
backfillApiKey(options, cfgLlm);
|
|
75
91
|
if (!options.model && !opts.explicitModel && cfgLlm.model) {
|
|
76
92
|
options.model = cfgLlm.model;
|
|
77
93
|
}
|
|
@@ -85,6 +101,6 @@ export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
|
85
101
|
options.model = cfgLlm.model;
|
|
86
102
|
}
|
|
87
103
|
if (!options.baseUrl && cfgLlm.baseUrl) options.baseUrl = cfgLlm.baseUrl;
|
|
88
|
-
|
|
104
|
+
backfillApiKey(options, cfgLlm);
|
|
89
105
|
return options;
|
|
90
106
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project `.mcp.json` fingerprint trust (gap-analysis 2026-07-11 P1 "MCP 生命
|
|
3
|
+
* 周期" — 配置指纹变化后重新信任).
|
|
4
|
+
*
|
|
5
|
+
* `--project-mcp` opts a run into a checked-in `.mcp.json`, which can spawn
|
|
6
|
+
* arbitrary commands. That consent is for the file AS THE USER SAW IT — a
|
|
7
|
+
* later commit (or a compromised dependency's postinstall) editing the file
|
|
8
|
+
* must not ride the standing opt-in. So the first successful load records a
|
|
9
|
+
* sha256 fingerprint per absolute file path; a later load whose content no
|
|
10
|
+
* longer matches is REFUSED (fail-closed) until the user re-trusts via
|
|
11
|
+
* `CC_PROJECT_MCP_TRUST=1` (one-shot) or `cc mcp trust-project`.
|
|
12
|
+
*
|
|
13
|
+
* Store: ~/.chainlesschain/trusted-project-mcp.json { [absPath]: {fingerprint, trustedAt} }
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { getHomeDir } from "./paths.js";
|
|
20
|
+
|
|
21
|
+
export const _deps = { fs };
|
|
22
|
+
|
|
23
|
+
function storePath(opts = {}) {
|
|
24
|
+
return (
|
|
25
|
+
opts.storePath ||
|
|
26
|
+
process.env.CC_PROJECT_MCP_TRUST_STORE ||
|
|
27
|
+
path.join(getHomeDir(), "trusted-project-mcp.json")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function projectMcpFingerprint(content) {
|
|
32
|
+
return createHash("sha256").update(String(content), "utf-8").digest("hex");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
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
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check a project .mcp.json against the trust store.
|
|
47
|
+
* @returns {{status:"first-use"|"trusted"|"changed", fingerprint:string}}
|
|
48
|
+
*/
|
|
49
|
+
export function checkProjectMcpTrust(file, content, opts = {}) {
|
|
50
|
+
const fingerprint = projectMcpFingerprint(content);
|
|
51
|
+
const record = readStore(opts)[path.resolve(file)];
|
|
52
|
+
if (!record || !record.fingerprint) {
|
|
53
|
+
return { status: "first-use", fingerprint };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
status: record.fingerprint === fingerprint ? "trusted" : "changed",
|
|
57
|
+
fingerprint,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Record (or re-record) the trusted fingerprint for a file. */
|
|
62
|
+
export function recordProjectMcpTrust(file, content, opts = {}) {
|
|
63
|
+
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
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Truthy CC_PROJECT_MCP_TRUST → the user explicitly re-trusts this run. */
|
|
84
|
+
export function projectMcpRetrustRequested(env = process.env) {
|
|
85
|
+
const raw = String(env.CC_PROJECT_MCP_TRUST || "").toLowerCase();
|
|
86
|
+
return raw === "1" || raw === "true";
|
|
87
|
+
}
|
|
@@ -66,3 +66,54 @@ export function sensitiveFileReason(targetPath) {
|
|
|
66
66
|
}
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Auto-exec CONFIG write guard — the CLI-side twin of the IDE extension's
|
|
72
|
+
* `classifyAutoExecTarget` (vscode-extension/src/auto-exec-guard.js, kept as
|
|
73
|
+
* an independent pure function: no cross-package import). These files are not
|
|
74
|
+
* shells or hooks, but writing them still plants code that RUNS without an
|
|
75
|
+
* explicit user action: VS Code tasks auto-run on folder open, launch configs
|
|
76
|
+
* fire on debug, `.mcp.json` spawns arbitrary server processes the agent then
|
|
77
|
+
* auto-loads, JetBrains run configurations execute on the next Run, and
|
|
78
|
+
* devcontainer postCreate/postStart commands run on container open. Same
|
|
79
|
+
* conservative name-based posture as `sensitiveFileReason`; an unknown path is
|
|
80
|
+
* simply not risky (null), never a false positive.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} targetPath path as the tool received it (rel or abs)
|
|
83
|
+
* @returns {string|null} human reason when auto-exec-risky, null otherwise
|
|
84
|
+
*/
|
|
85
|
+
export function autoExecConfigReason(targetPath) {
|
|
86
|
+
const p = String(targetPath || "");
|
|
87
|
+
if (!p) return null;
|
|
88
|
+
const norm = p.replace(/\\/g, "/").toLowerCase();
|
|
89
|
+
const base = norm.split("/").pop() || "";
|
|
90
|
+
if (!base) return null;
|
|
91
|
+
if (base === ".mcp.json" || base === "mcp.json") {
|
|
92
|
+
return `auto-exec config: MCP server config (${base}) — can spawn arbitrary server processes the agent auto-loads`;
|
|
93
|
+
}
|
|
94
|
+
if (/(^|\/)\.vscode\/tasks\.json$/.test(norm)) {
|
|
95
|
+
return "auto-exec config: VS Code tasks (.vscode/tasks.json) — tasks can auto-run on folder open";
|
|
96
|
+
}
|
|
97
|
+
if (/(^|\/)\.vscode\/launch\.json$/.test(norm)) {
|
|
98
|
+
return "auto-exec config: VS Code launch/debug config (.vscode/launch.json) — runs commands on the next debug session";
|
|
99
|
+
}
|
|
100
|
+
if (/(^|\/)\.vscode\/settings\.json$/.test(norm)) {
|
|
101
|
+
return "auto-exec config: VS Code workspace settings (.vscode/settings.json) — can rebind shells/tools the user then runs";
|
|
102
|
+
}
|
|
103
|
+
if (/(^|\/)\.idea\/runconfigurations\//.test(norm)) {
|
|
104
|
+
return `auto-exec config: JetBrains run configuration (${base}) — executes on the user's next Run action`;
|
|
105
|
+
}
|
|
106
|
+
if (/(^|\/)\.idea\/workspace\.xml$/.test(norm)) {
|
|
107
|
+
return "auto-exec config: JetBrains workspace.xml — can embed run/startup tasks";
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
base === "devcontainer.json" ||
|
|
111
|
+
/(^|\/)\.devcontainer\/devcontainer\.json$/.test(norm)
|
|
112
|
+
) {
|
|
113
|
+
return "auto-exec config: devcontainer.json — postCreate/postStart commands run on container open";
|
|
114
|
+
}
|
|
115
|
+
if (base.endsWith(".code-workspace")) {
|
|
116
|
+
return `auto-exec config: VS Code workspace file (${base}) — can carry auto-run tasks and settings`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
@@ -253,12 +253,16 @@ export class SubAgentContext {
|
|
|
253
253
|
const artifacts = [];
|
|
254
254
|
let lastContent = "";
|
|
255
255
|
|
|
256
|
-
// Build filtered tool list
|
|
257
|
-
const tools = this._getFilteredTools();
|
|
258
|
-
|
|
259
256
|
// Merge LLM options — pass shared iteration budget if available
|
|
260
257
|
const options = {
|
|
261
258
|
...this._llmOptions,
|
|
259
|
+
// Tool allowlist (fixed 2026-07-11): the filtered list used to be
|
|
260
|
+
// computed via _getFilteredTools() but never handed to agentLoop, so a
|
|
261
|
+
// sub-agent's `tools`/frontmatter allowlist was silently ignored at the
|
|
262
|
+
// LLM surface. agentLoop consumes NAMES via enabledToolNames.
|
|
263
|
+
...(Array.isArray(this.allowedTools) && this.allowedTools.length > 0
|
|
264
|
+
? { enabledToolNames: this.allowedTools }
|
|
265
|
+
: {}),
|
|
262
266
|
contextEngine: this.contextEngine,
|
|
263
267
|
cwd: this.cwd,
|
|
264
268
|
// Nesting level: lets a nested spawn_sub_agent see — and cap — its depth.
|