chainlesschain 0.162.125 → 0.162.126
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DcqNhZhA.js → AIOps-BrTOcqIJ.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BigtmPoq.js → ActionButton-na9cocmf.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-1J_X_HF4.js → Analytics-BtDygkpt.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-DlcDG_a_.js → AppLayout-D_t5CS18.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-Do-y8_3w.js → Audit-BYMZccCy.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-S2Df-50Y.js → Backup-D2qDfAdL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D5kgWJfk.js → BaseInput-kj456Ju9.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-aUcp3kN1.js → Chat-BY0A0ZLE.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DTg1U7Xs.js → Checkbox-DiBhtXsh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-YO9ZZ4Ky.js → Codegen-B1ER0L0j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BHonE9fU.js → Col-CjLmza7D.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DA2AlEFh.js → Community-BNId05U7.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DULxLRNg.js → Compact-BuuMj7K1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BZqfuuZL.js → Compliance-DUuDycaJ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CJe3vyMS.js → Cowork-BKHiF6uL.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CeV8AtRu.js → Cron-C7p-lHnC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CAg66zS5.js → Crosschain-BvSOx8a6.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Dtup5JZm.js → DID-D1k9jgHv.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DAR_8PNy.js → Dashboard-B9mCCnqY.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-K5bTaCYN.js → Dropdown-ge9UHSXF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C890uErx.js → EmailListRenderer-TZO7ppXi.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B-Tj_8yM.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C6WZ98ni.js → Federation-JLuA_8zp.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-D-LTfGWd.js → FormItemContext-C4w-rzNg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C80DK4W7.js → GenericCardRenderer-Btns4sV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Cjvvv3zW.js → Git-BheXMjzC.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C0BND77H.js → Governance-D9rLlatH.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BL9kTtSu.js → Inference-DiklIMcd.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CnVTBmJf.js → KnowledgeGraph-C70EWL03.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CbCbg7K6.js → Logs-Zt_MLI10.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CG5On-fH.js → Marketplace-suQxES99.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-AYYDZZK7.js → McpTools-CclpCDpY.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tMWbMDQq.js → Memory-B2lHOUwF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BAOsf4wB.js → MobileBridge-BMXKoDAD.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BT-2komQ.js → MobileProjects-a6mWVdBV.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BFwfC63n.js → Mtc-BFpM4Fkj.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DYG3CWdH.js → MtcAudit-DDQkxkbS.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-KJwd0yWT.js → Multisig-Crkd_zKQ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BmL5dNWm.js → NLProgramming-Dpk5An7B.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DmkaVaMc.js → Notes-BrFnOMNz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-tmVQszDZ.js → NotificationSettings-CM0iApFM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BtrR7anf.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-pppktQyW.js → Organization-CHFW_38T.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D3lBoQDA.js → Overflow-C-vl3EjV.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BgE2qGlZ.js → P2P-BT7Slavq.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DRUaULap.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BR8no2Xe.js → Permissions-DkhOAvMz.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DabbyQEF.js → PersonalDataHub-Dl7dSOvV.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-n4sNpEz8.js → Pipeline-B3nVI4p6.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CGNp4n8M.js → Privacy-Dv1SXx1Q.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CAVA5VsX.js → ProjectInit-DrAhVi5p.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-GCgkfM7A.js → ProjectSettings-h-ggCYN_.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BYK17p52.js → Projects-CM91hYdr.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DpUT5W-d.js → Providers-rfkZel3N.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ZJxTb7vi.js → QuickAsk-XepZB7h_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIzFRDk1.js → Recommend-DzQWsh1a.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-lQ_WDNZn.js → Reputation-CDeaal0j.js} +1 -1
- package/src/assets/web-panel/assets/{Row-1MEtrgtF.js → Row-BN4aKhFH.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-SSgcPPl4.js → RssFeed-INP6VYAA.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CcTMOGNY.js → Search-ChPbwatu.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BA1KMaDx.js → Security-C97ZCY8N.js} +3 -3
- package/src/assets/web-panel/assets/{Services-_aj7czZn.js → Services-Cbtl2Ajs.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BWgg_0Zr.js → Skeleton-B13sFxBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C2ZIziYM.js → Skills-vI9zSIKX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CYxp4axY.js → Sla-Cr3oBH39.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C7Csp1jV.js → SpeechSettings-BwemqPPV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-svGeswiw.js → SyncSettings-CLA78P-Z.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-ClISj6oK.js → Tasks-DBjQ_DOM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CmO-Fp7r.js → Templates-BkSLDkBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-a3Ac3lxz.js → Tenant-Dn0nSlib.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DyJIRh81.js → Terminal-C8f4boru.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-acXS5N5h.js → TimelineRenderer-DW_rMtJR.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DJW0KqV4.js → Tokens-BdffrKYV.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-0HeTi4r6.js → Trigger-CX4CUg-Q.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-aFym34eo.js → Trust-Cq9Bl7Od.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CYhszHJv.js → UkeySign-BQy18_VY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BbJxPF9X.js → VideoEditing-h5hzK7Vw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DDthEwiu.js → Wallet-BLotT3gw.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-DQ6McYPg.js → WebAuthn-8cCANYLE.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CnF8zRsi.js → WorkflowEditor-Db6NwtAv.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Dzkx2gTP.js → chat-BPk1LbpL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-35T51Oo5.js → colors-BnNal71p.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-TtzNrlu1.js → compact-item-W2VC0tcy.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Y1LkWrYb.js → createContext-DceVsgnZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BOYw1BgS.js → hasIn-BEq6EDcp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtlJWaSP.js → index-5gIBUFBn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLqC2sRT.js → index-APCHxOkM.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Wt5feN.js → index-B2dMhwZi.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqhwG1ox.js → index-B8cPjrEH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxWUEFKy.js → index-BDC5HsxZ.js} +1 -1
- package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
- package/src/assets/web-panel/assets/{index-NM9Oke2k.js → index-BUkDcIwJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWgNn9As.js → index-BWcCyH6-.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVrUs6rf.js → index-BgZikQoh.js} +1 -1
- package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
- package/src/assets/web-panel/assets/{index-D6tG4B25.js → index-BzSzRWsw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjeUZxE5.js → index-C1K9CsGr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmkG479D.js → index-CBZVISK6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw1iOGhM.js → index-CEJN-R2L.js} +1 -1
- package/src/assets/web-panel/assets/{index-G4ClSmJc.js → index-CG5dGC9E.js} +1 -1
- package/src/assets/web-panel/assets/{index-D47robkX.js → index-CMGLpstm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKRlWHUp.js → index-CWrGCndd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhMSOd_2.js → index-CbcvfpCV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dx0hIfC-.js → index-Ceqv4lI2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdyFg4FI.js → index-CsT6frT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7aKAZzS.js → index-D8PQKsDT.js} +1 -1
- package/src/assets/web-panel/assets/{index-8jffdb6b.js → index-DGncdA-j.js} +1 -1
- package/src/assets/web-panel/assets/{index-XIuZV9by.js → index-DHanQHO0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dhfw-BXs.js → index-DaUBk28E.js} +1 -1
- package/src/assets/web-panel/assets/{index-hyqEKkBT.js → index-Di2J4b4V.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAizH273.js → index-DtuLvWZN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPGNc2tF.js → index-DvS1J8xc.js} +1 -1
- package/src/assets/web-panel/assets/{index-KgXrlfNF.js → index-DxePJdwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFhlelzN.js → index-Et4XvL49.js} +1 -1
- package/src/assets/web-panel/assets/{index-zF77jnI2.js → index-GlFxgcj4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs82BHAj.js → index-HV119Oui.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dof7lWA_.js → index-InGW87pj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bs69SI96.js → index-P1-s8JR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4BgCBa3.js → index-SdABCNoi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSpFe6j5.js → index-WTAZbN-c.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7dCBw_M.js → index-_2I-KzOj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGPEaeB1.js → index-m1sVaWVU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ16GlOs.js → index-s37wwyeN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2od7Bgx.js → index-tyWABqp9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DD5y5msp.js → initDefaultProps-DzrCDb3j.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BuoutMia.js → motion-BQERVnE3.js} +1 -1
- package/src/assets/web-panel/assets/{move-DkpjqOXg.js → move-I9ACA5XJ.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BmGJD-v-.js → mtc-parser-Dd2Pw-tr.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DftUE0Sz.js → omit-DwzYRzWV.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDRb9FIu.js → pickAttrs-BSGULyIL.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DPgtxOOD.js → placementArrow-tSYYjAts.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BQFRzFeZ.js → responsiveObserve-Dy2lqikT.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DQ4lmUSz.js → slide-RaB4Uq0c.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C1TR4ZiQ.js → statusUtils-CBpC_wZg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B-Suj978.js → styleChecker-BEKwPWt5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Q7bDZ3EI.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DHjRLyQH.js → useFs-n24jutw5.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-lQOvCvCr.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Ie0g4-p3.js → vnode-BSp2KYcj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CHP0YEoH.js → zoom-7UfQtTPh.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +41 -2
- package/src/commands/compliance.js +3 -1
- package/src/commands/review.js +47 -17
- package/src/commands/session.js +11 -2
- package/src/gateways/ws/message-dispatcher.js +145 -165
- package/src/gateways/ws/ws-session-gateway.js +39 -5
- package/src/harness/jsonl-session-store.js +39 -12
- package/src/harness/worktree-isolator.js +5 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/audit-logger.js +3 -1
- package/src/lib/chat-core.js +5 -2
- package/src/lib/chat-intent-service.js +68 -21
- package/src/lib/config-manager.js +25 -1
- package/src/lib/credential-guard.js +43 -1
- package/src/lib/git-integration.js +5 -0
- package/src/lib/interaction-adapter.js +32 -11
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/llm-config-defaults.js +37 -0
- package/src/lib/mcp-oauth.js +76 -10
- package/src/lib/permission-engine.js +4 -1
- package/src/lib/pipeline-orchestrator.js +20 -2
- package/src/lib/project-instructions.js +13 -0
- package/src/lib/repl-denials.js +137 -0
- package/src/lib/safe-json.js +22 -0
- package/src/lib/sandbox-v2.js +7 -6
- package/src/lib/search-command.js +65 -0
- package/src/lib/settings-hooks.cjs +133 -0
- package/src/lib/settings-loader.cjs +16 -7
- package/src/lib/social-graph.js +3 -1
- package/src/lib/stream-retry.js +27 -0
- package/src/lib/turn-context.js +15 -5
- package/src/repl/agent-repl.js +81 -8
- package/src/runtime/__tests__/message-roles.test.js +36 -3
- package/src/runtime/agent-core.js +175 -50
- package/src/runtime/coding-agent-contract-shared.cjs +9 -2
- package/src/runtime/coding-agent-shell-policy.cjs +23 -2
- package/src/runtime/file-ref-expander.js +51 -7
- package/src/runtime/headless-runner.js +83 -2
- package/src/runtime/headless-stream.js +69 -3
- package/src/runtime/mcp-config.js +42 -1
- package/src/runtime/message-roles.js +14 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D26Bz5gS.js +0 -7
- package/src/assets/web-panel/assets/devWarning-D_lwLZ6O.js +0 -1
- package/src/assets/web-panel/assets/index-CEwuCM44.js +0 -1
- package/src/assets/web-panel/assets/index-DzzgU4IU.js +0 -1
|
@@ -43,10 +43,11 @@ import {
|
|
|
43
43
|
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
|
+
import { buildSearchCommand } from "../lib/search-command.js";
|
|
46
47
|
import {
|
|
47
48
|
isRetryableStreamError,
|
|
48
|
-
STREAM_RETRY_MAX,
|
|
49
49
|
STREAM_RETRY_BASE_MS,
|
|
50
|
+
resolveStreamRetryMax,
|
|
50
51
|
} from "../lib/stream-retry.js";
|
|
51
52
|
import {
|
|
52
53
|
annotateLines,
|
|
@@ -755,6 +756,27 @@ export function tokenizeShellWords(input) {
|
|
|
755
756
|
return args;
|
|
756
757
|
}
|
|
757
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Literal string edit shared by the edit_file preview + execution paths.
|
|
761
|
+
* Uses split/join so `old_string` and `new_string` are treated as LITERAL text
|
|
762
|
+
* (no regex / `$&` / `$1` pattern interpretation — a plain `String.replace`
|
|
763
|
+
* with a string pattern still expands `$` sequences in the replacement).
|
|
764
|
+
* Returns the occurrence count so the caller can require a UNIQUE match
|
|
765
|
+
* (Claude-Code Edit parity): replacing the first of several identical strings
|
|
766
|
+
* silently edits the wrong place.
|
|
767
|
+
*
|
|
768
|
+
* @returns {{ count:number, newContent:string }}
|
|
769
|
+
*/
|
|
770
|
+
function applyLiteralEdit(content, oldStr, newStr, replaceAll) {
|
|
771
|
+
const parts = content.split(oldStr);
|
|
772
|
+
const count = parts.length - 1;
|
|
773
|
+
if (count === 0) return { count: 0, newContent: content };
|
|
774
|
+
const newContent = replaceAll
|
|
775
|
+
? parts.join(newStr)
|
|
776
|
+
: parts[0] + newStr + parts.slice(1).join(oldStr); // first occurrence only
|
|
777
|
+
return { count, newContent };
|
|
778
|
+
}
|
|
779
|
+
|
|
758
780
|
/**
|
|
759
781
|
* Compute the content an edit tool WOULD write, without writing it — the
|
|
760
782
|
* left/right sides for an IDE diff review. Mirrors the corresponding
|
|
@@ -781,15 +803,22 @@ export function computeProposedEdit(name, args = {}, cwd = process.cwd()) {
|
|
|
781
803
|
const content = fs.readFileSync(filePath, "utf8");
|
|
782
804
|
if (
|
|
783
805
|
typeof args.old_string !== "string" ||
|
|
784
|
-
|
|
806
|
+
args.old_string === "" ||
|
|
807
|
+
typeof args.new_string !== "string"
|
|
785
808
|
) {
|
|
786
809
|
return null;
|
|
787
810
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
811
|
+
const replaceAll = args.replace_all === true;
|
|
812
|
+
const { count, newContent } = applyLiteralEdit(
|
|
813
|
+
content,
|
|
814
|
+
args.old_string,
|
|
815
|
+
args.new_string,
|
|
816
|
+
replaceAll,
|
|
817
|
+
);
|
|
818
|
+
// No match, or a non-unique match without replace_all → the edit will be
|
|
819
|
+
// rejected, so show no (misleading) diff and let the tool report it.
|
|
820
|
+
if (count === 0 || (count > 1 && !replaceAll)) return null;
|
|
821
|
+
return { filePath, newContent, originalText: content };
|
|
793
822
|
}
|
|
794
823
|
if (name === "edit_file_hashed") {
|
|
795
824
|
if (!fs.existsSync(filePath)) return null;
|
|
@@ -965,7 +994,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
965
994
|
// 1. settings deny
|
|
966
995
|
if (settingsVerdict.decision === "deny") {
|
|
967
996
|
return {
|
|
968
|
-
error: `[Permission] Tool "${name}" denied by settings rule: ${settingsVerdict.rule}
|
|
997
|
+
error: `[Permission] Tool "${name}" denied by settings rule: ${settingsVerdict.rule}. This is a configured policy — retrying won't help; tell the user if the task genuinely needs it.`,
|
|
969
998
|
policy: { decision: "deny", rule: settingsVerdict.rule, via: "settings" },
|
|
970
999
|
};
|
|
971
1000
|
}
|
|
@@ -1037,7 +1066,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1037
1066
|
: false;
|
|
1038
1067
|
if (!ok) {
|
|
1039
1068
|
return {
|
|
1040
|
-
error: `[Permission] Tool "${name}" requires confirmation (settings rule: ${settingsVerdict.rule}) — denied.`,
|
|
1069
|
+
error: `[Permission] Tool "${name}" requires confirmation (settings rule: ${settingsVerdict.rule}) but this run is non-interactive — denied. Do not retry; tell the user this action needs their approval.`,
|
|
1041
1070
|
policy: {
|
|
1042
1071
|
decision: "ask",
|
|
1043
1072
|
rule: settingsVerdict.rule,
|
|
@@ -1594,6 +1623,47 @@ export function renderNotebook(text) {
|
|
|
1594
1623
|
return lines.join("\n");
|
|
1595
1624
|
}
|
|
1596
1625
|
|
|
1626
|
+
/**
|
|
1627
|
+
* Ingest the raw stdout of a search_files command into the shared hit
|
|
1628
|
+
* accumulator: split into lines, dedup via `seen`, redact credential-file
|
|
1629
|
+
* content hits, label multi-root results, and stop at the hit cap. Pure aside
|
|
1630
|
+
* from mutating the passed-in `seen`/`hits`/`redactedCreds` collectors.
|
|
1631
|
+
*
|
|
1632
|
+
* Shared by the success path AND the maxBuffer-overflow salvage path: a search
|
|
1633
|
+
* on a large tree (especially Windows, where `findstr`/`dir /s` have no `head`
|
|
1634
|
+
* cap) can exceed execSync's maxBuffer and throw ENOBUFS — but the error still
|
|
1635
|
+
* carries the first maxBuffer of matches in `err.stdout`, so the same ingest is
|
|
1636
|
+
* run on it instead of dropping every match as a false "no matches".
|
|
1637
|
+
*
|
|
1638
|
+
* @param {string} output raw command stdout (or a truncated partial)
|
|
1639
|
+
* @param {object} ctx { isContent, root, multiRoot, seen, hits, redactedCreds,
|
|
1640
|
+
* credentialFileReason, limit? }
|
|
1641
|
+
*/
|
|
1642
|
+
export function _ingestSearchOutput(output, ctx) {
|
|
1643
|
+
const limit = ctx.limit ?? 20;
|
|
1644
|
+
for (const line of String(output || "")
|
|
1645
|
+
.trim()
|
|
1646
|
+
.split("\n")) {
|
|
1647
|
+
const v = line.trim();
|
|
1648
|
+
if (!v || ctx.seen.has(v)) continue;
|
|
1649
|
+
if (ctx.isContent) {
|
|
1650
|
+
// content hit is `file:line:text` (findstr /n) or a bare filename
|
|
1651
|
+
// (grep -l); pull the source file and redact credential matches.
|
|
1652
|
+
const src = v.match(/^(.+?):\d+:/)?.[1] ?? v;
|
|
1653
|
+
if (ctx.credentialFileReason(src)) {
|
|
1654
|
+
ctx.redactedCreds.add(src.replace(/\\/g, "/"));
|
|
1655
|
+
ctx.seen.add(v);
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
// Qualify with the root so multi-root results stay unambiguous.
|
|
1660
|
+
const labeled = ctx.multiRoot ? `${ctx.root}: ${v}` : v;
|
|
1661
|
+
ctx.seen.add(v);
|
|
1662
|
+
ctx.hits.push(labeled);
|
|
1663
|
+
if (ctx.hits.length >= limit) return;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1597
1667
|
/**
|
|
1598
1668
|
* Inner tool execution — no hooks, no plan-mode checks.
|
|
1599
1669
|
*/
|
|
@@ -1612,6 +1682,7 @@ async function executeToolInner(
|
|
|
1612
1682
|
mcpClient,
|
|
1613
1683
|
llmOptions,
|
|
1614
1684
|
shellPolicyOverrides,
|
|
1685
|
+
classifyAllShell = false,
|
|
1615
1686
|
approvalGate,
|
|
1616
1687
|
shellConfirm,
|
|
1617
1688
|
additionalDirectories,
|
|
@@ -1663,6 +1734,13 @@ async function executeToolInner(
|
|
|
1663
1734
|
if (!fs.existsSync(filePath)) {
|
|
1664
1735
|
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
1665
1736
|
}
|
|
1737
|
+
// A clear, self-correcting error beats the cryptic "EISDIR: illegal
|
|
1738
|
+
// operation on a directory" that readFileSync throws on a directory.
|
|
1739
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
1740
|
+
return attachDescriptor({
|
|
1741
|
+
error: `Path is a directory, not a file: ${filePath}. Use list_dir to see its contents.`,
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1666
1744
|
const content = fs.readFileSync(filePath, "utf8");
|
|
1667
1745
|
// Jupyter notebooks: render a compact cell listing (index/id/type/source,
|
|
1668
1746
|
// outputs summarized) so the model can find cells for notebook_edit
|
|
@@ -1763,20 +1841,40 @@ async function executeToolInner(
|
|
|
1763
1841
|
if (!fs.existsSync(filePath)) {
|
|
1764
1842
|
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
1765
1843
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1844
|
+
if (typeof args.old_string !== "string" || args.old_string === "") {
|
|
1845
|
+
return attachDescriptor({
|
|
1846
|
+
error: "old_string must be a non-empty string",
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
if (typeof args.new_string !== "string") {
|
|
1850
|
+
return attachDescriptor({ error: "new_string must be a string" });
|
|
1769
1851
|
}
|
|
1770
|
-
const
|
|
1852
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
1853
|
+
const replaceAll = args.replace_all === true;
|
|
1854
|
+
const { count, newContent } = applyLiteralEdit(
|
|
1855
|
+
content,
|
|
1771
1856
|
args.old_string,
|
|
1772
|
-
|
|
1857
|
+
args.new_string,
|
|
1858
|
+
replaceAll,
|
|
1773
1859
|
);
|
|
1860
|
+
if (count === 0) {
|
|
1861
|
+
return attachDescriptor({ error: "old_string not found in file" });
|
|
1862
|
+
}
|
|
1863
|
+
// Require a UNIQUE match (Claude-Code Edit parity) — replacing the first
|
|
1864
|
+
// of several identical strings silently edits the wrong occurrence.
|
|
1865
|
+
if (count > 1 && !replaceAll) {
|
|
1866
|
+
return attachDescriptor({
|
|
1867
|
+
error: `old_string is not unique — it appears ${count} times. Add surrounding context so it matches exactly one occurrence, or pass replace_all:true to change all of them.`,
|
|
1868
|
+
occurrences: count,
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1774
1871
|
const wrote = writeFileVerified(filePath, newContent);
|
|
1775
1872
|
if (wrote.error) return attachDescriptor({ error: wrote.error });
|
|
1776
1873
|
return attachDescriptor({
|
|
1777
1874
|
success: true,
|
|
1778
1875
|
path: filePath,
|
|
1779
1876
|
size: wrote.size,
|
|
1877
|
+
replaced: replaceAll ? count : 1,
|
|
1780
1878
|
});
|
|
1781
1879
|
}
|
|
1782
1880
|
|
|
@@ -1831,9 +1929,12 @@ async function executeToolInner(
|
|
|
1831
1929
|
}
|
|
1832
1930
|
|
|
1833
1931
|
case "run_shell": {
|
|
1834
|
-
const shellPolicyOpts =
|
|
1835
|
-
|
|
1836
|
-
|
|
1932
|
+
const shellPolicyOpts = {
|
|
1933
|
+
...(shellPolicyOverrides
|
|
1934
|
+
? { overrideRuleIds: shellPolicyOverrides }
|
|
1935
|
+
: {}),
|
|
1936
|
+
...(classifyAllShell ? { classifyAllShell: true } : {}),
|
|
1937
|
+
};
|
|
1837
1938
|
const override = getRuntimeToolDescriptorByCommand(args.command);
|
|
1838
1939
|
let shellPolicy;
|
|
1839
1940
|
let approvalOutcome = null;
|
|
@@ -1857,12 +1958,18 @@ async function executeToolInner(
|
|
|
1857
1958
|
policy: gated.policy,
|
|
1858
1959
|
};
|
|
1859
1960
|
if (!gated.allowed) {
|
|
1961
|
+
// Make a policy denial ACTIONABLE for the model (Claude-Code 2.1.193
|
|
1962
|
+
// "denial reasons to transcripts"): tell it this won't change on
|
|
1963
|
+
// retry and to involve the user, so it stops re-issuing the same
|
|
1964
|
+
// blocked command (which otherwise just burns turns + tokens).
|
|
1965
|
+
const tierLabel =
|
|
1966
|
+
typeof gated.policy === "string" ? `"${gated.policy}" ` : "";
|
|
1860
1967
|
return attachDescriptor(
|
|
1861
1968
|
{
|
|
1862
1969
|
error:
|
|
1863
1970
|
gated.via === "shell-policy"
|
|
1864
|
-
? `[Shell Policy] ${gated.reason}
|
|
1865
|
-
: `[ApprovalGate] command denied (${gated.via})
|
|
1971
|
+
? `[Shell Policy] ${gated.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`
|
|
1972
|
+
: `[ApprovalGate] command denied by the ${tierLabel}approval policy (via ${gated.via}). Retrying the same command will not help — it needs user approval. Tell the user (they can run it themselves, approve it, or relax the policy) and continue with other work.`,
|
|
1866
1973
|
shellCommandPolicy: shellPolicy,
|
|
1867
1974
|
approval: approvalOutcome,
|
|
1868
1975
|
},
|
|
@@ -1874,7 +1981,7 @@ async function executeToolInner(
|
|
|
1874
1981
|
if (!shellPolicy.allowed) {
|
|
1875
1982
|
return attachDescriptor(
|
|
1876
1983
|
{
|
|
1877
|
-
error: `[Shell Policy] ${shellPolicy.reason}
|
|
1984
|
+
error: `[Shell Policy] ${shellPolicy.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`,
|
|
1878
1985
|
shellCommandPolicy: shellPolicy,
|
|
1879
1986
|
},
|
|
1880
1987
|
override || runtimeDescriptor,
|
|
@@ -2137,12 +2244,17 @@ async function executeToolInner(
|
|
|
2137
2244
|
args: { language: args.language },
|
|
2138
2245
|
});
|
|
2139
2246
|
if (gate.decision !== APPROVAL_DECISION.ALLOW) {
|
|
2247
|
+
// Actionable denial (matches run_shell, Claude-Code 2.1.193): tell the
|
|
2248
|
+
// model retrying won't help and to involve the user.
|
|
2249
|
+
const tierLabel =
|
|
2250
|
+
typeof gate.policy === "string" ? `"${gate.policy}" ` : "";
|
|
2140
2251
|
return attachDescriptor({
|
|
2141
|
-
error: `[ApprovalGate] run_code denied (${gate.via})
|
|
2252
|
+
error: `[ApprovalGate] run_code denied by the ${tierLabel}approval policy (via ${gate.via}). Retrying won't help — running arbitrary code needs user approval. Tell the user (they can approve it or relax the policy) and continue with other work.`,
|
|
2142
2253
|
approval: {
|
|
2143
2254
|
decision: gate.decision,
|
|
2144
2255
|
via: gate.via,
|
|
2145
2256
|
riskLevel: "high",
|
|
2257
|
+
policy: gate.policy,
|
|
2146
2258
|
},
|
|
2147
2259
|
});
|
|
2148
2260
|
}
|
|
@@ -2303,13 +2415,14 @@ async function executeToolInner(
|
|
|
2303
2415
|
? [path.resolve(cwd, args.directory)]
|
|
2304
2416
|
: [cwd, ...extraRoots];
|
|
2305
2417
|
const isContent = Boolean(args.content_search);
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2418
|
+
// Pattern is model/user-supplied and flows into a shell — build the
|
|
2419
|
+
// command with it SAFELY quoted (see search-command.js). Raw interpolation
|
|
2420
|
+
// here was a command-injection bypass of the run_shell guards.
|
|
2421
|
+
const built = buildSearchCommand({ pattern: args.pattern, isContent });
|
|
2422
|
+
if (built.error) {
|
|
2423
|
+
return attachDescriptor({ error: built.error });
|
|
2424
|
+
}
|
|
2425
|
+
const cmd = built.cmd;
|
|
2313
2426
|
|
|
2314
2427
|
// Credential guard (Claude-Code 2.1.189 parity): a CONTENT search must not
|
|
2315
2428
|
// become a side channel that exfils secrets the read_file / run_shell
|
|
@@ -2326,34 +2439,34 @@ async function executeToolInner(
|
|
|
2326
2439
|
const redactedCreds = new Set();
|
|
2327
2440
|
for (const root of roots) {
|
|
2328
2441
|
if (hits.length >= 20) break;
|
|
2442
|
+
const ictx = {
|
|
2443
|
+
isContent,
|
|
2444
|
+
root,
|
|
2445
|
+
multiRoot: roots.length > 1,
|
|
2446
|
+
seen,
|
|
2447
|
+
hits,
|
|
2448
|
+
redactedCreds,
|
|
2449
|
+
credentialFileReason,
|
|
2450
|
+
};
|
|
2329
2451
|
try {
|
|
2330
2452
|
if (!fs.existsSync(root)) continue;
|
|
2331
2453
|
const output = execSync(cmd, {
|
|
2332
2454
|
cwd: root,
|
|
2333
2455
|
encoding: "utf8",
|
|
2334
2456
|
timeout: 10000,
|
|
2457
|
+
// Windows `findstr`/`dir /s` have no `head` cap (unlike the POSIX
|
|
2458
|
+
// `| head -20`), so a large tree can blow past execSync's 1 MB
|
|
2459
|
+
// default and throw ENOBUFS. Give it real headroom AND salvage the
|
|
2460
|
+
// partial below — otherwise a busy repo silently reports "no matches".
|
|
2461
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
2335
2462
|
});
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
if (credentialFileReason(src)) {
|
|
2344
|
-
redactedCreds.add(src.replace(/\\/g, "/"));
|
|
2345
|
-
seen.add(v);
|
|
2346
|
-
continue;
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
// Qualify with the root so multi-root results stay unambiguous.
|
|
2350
|
-
const labeled = roots.length > 1 ? `${root}: ${v}` : v;
|
|
2351
|
-
seen.add(v);
|
|
2352
|
-
hits.push(labeled);
|
|
2353
|
-
if (hits.length >= 20) break;
|
|
2354
|
-
}
|
|
2355
|
-
} catch {
|
|
2356
|
-
// No matches in this root — continue to the next.
|
|
2463
|
+
_ingestSearchOutput(output, ictx);
|
|
2464
|
+
} catch (err) {
|
|
2465
|
+
// A maxBuffer overflow still carries the first 8 MB of matches in
|
|
2466
|
+
// err.stdout — ingest those rather than dropping every hit as a false
|
|
2467
|
+
// "No matches found". A genuine no-match / command failure leaves
|
|
2468
|
+
// err.stdout empty, so hits stay unchanged (same as before).
|
|
2469
|
+
if (err && err.stdout) _ingestSearchOutput(err.stdout, ictx);
|
|
2357
2470
|
}
|
|
2358
2471
|
}
|
|
2359
2472
|
|
|
@@ -2376,6 +2489,12 @@ async function executeToolInner(
|
|
|
2376
2489
|
if (!fs.existsSync(dirPath)) {
|
|
2377
2490
|
return attachDescriptor({ error: `Directory not found: ${dirPath}` });
|
|
2378
2491
|
}
|
|
2492
|
+
// Clear error instead of the cryptic "ENOTDIR" readdirSync throws on a file.
|
|
2493
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
2494
|
+
return attachDescriptor({
|
|
2495
|
+
error: `Path is a file, not a directory: ${dirPath}. Use read_file to read it.`,
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2379
2498
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
2380
2499
|
return attachDescriptor({
|
|
2381
2500
|
entries: entries.map((e) => ({
|
|
@@ -3685,7 +3804,9 @@ const _STREAM_STALL = Symbol("stream-stall");
|
|
|
3685
3804
|
* @param {object} opts { signal?, retries?, baseDelayMs?, onRetry?, sleep? }
|
|
3686
3805
|
*/
|
|
3687
3806
|
export async function _retryStreamingChat(streamFn, opts = {}) {
|
|
3688
|
-
|
|
3807
|
+
// Default budget honors CC_MAX_RETRIES / CLAUDE_CODE_MAX_RETRIES (capped 15);
|
|
3808
|
+
// an explicit opts.retries still wins (tests / callers that pin it).
|
|
3809
|
+
const retries = opts.retries ?? resolveStreamRetryMax();
|
|
3689
3810
|
const base = opts.baseDelayMs ?? STREAM_RETRY_BASE_MS;
|
|
3690
3811
|
const signal = opts.signal;
|
|
3691
3812
|
const sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -4501,6 +4622,10 @@ export async function* agentLoop(messages, options) {
|
|
|
4501
4622
|
parentMessages: messages, // pass parent messages for sub-agent auto-condensation
|
|
4502
4623
|
interaction: options.interaction || null,
|
|
4503
4624
|
shellPolicyOverrides: options.shellPolicyOverrides || null,
|
|
4625
|
+
// autoMode.classifyAllShell (Claude-Code 2.1.193): when true, the built-in
|
|
4626
|
+
// verification allowlist (npm test / rg / …) is classified through the
|
|
4627
|
+
// ApprovalGate instead of fast-pathed, so no shell command auto-runs.
|
|
4628
|
+
classifyAllShell: options.classifyAllShell || false,
|
|
4504
4629
|
approvalGate: options.approvalGate || null,
|
|
4505
4630
|
shellConfirm: options.shellConfirm || null,
|
|
4506
4631
|
// Interactive sessions (the REPL) set this so run_code is gated through the
|
|
@@ -141,19 +141,26 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
|
141
141
|
title: "Edit File",
|
|
142
142
|
kind: "filesystem",
|
|
143
143
|
tier: "mvp",
|
|
144
|
-
description:
|
|
144
|
+
description:
|
|
145
|
+
"Replace a string in a file. old_string must match EXACTLY ONE place — include enough surrounding context to make it unique, or the edit is rejected. Pass replace_all:true to change every occurrence instead.",
|
|
145
146
|
inputSchema: {
|
|
146
147
|
type: "object",
|
|
147
148
|
properties: {
|
|
148
149
|
path: { type: "string", description: "File path" },
|
|
149
150
|
old_string: {
|
|
150
151
|
type: "string",
|
|
151
|
-
description:
|
|
152
|
+
description:
|
|
153
|
+
"Exact string to replace; must be unique in the file unless replace_all is true",
|
|
152
154
|
},
|
|
153
155
|
new_string: {
|
|
154
156
|
type: "string",
|
|
155
157
|
description: "Replacement string",
|
|
156
158
|
},
|
|
159
|
+
replace_all: {
|
|
160
|
+
type: "boolean",
|
|
161
|
+
description:
|
|
162
|
+
"Replace every occurrence of old_string instead of requiring a unique match (default false)",
|
|
163
|
+
},
|
|
157
164
|
},
|
|
158
165
|
required: ["path", "old_string", "new_string"],
|
|
159
166
|
},
|
|
@@ -288,7 +288,7 @@ const DECISION_SEVERITY = Object.freeze({
|
|
|
288
288
|
});
|
|
289
289
|
|
|
290
290
|
/** Classify ONE command segment (no separators). */
|
|
291
|
-
function evaluateSegmentPolicy(segment, overrideRuleIds) {
|
|
291
|
+
function evaluateSegmentPolicy(segment, overrideRuleIds, classifyAllShell) {
|
|
292
292
|
const normalized = normalizeShellCommand(segment);
|
|
293
293
|
const tokens = tokenizeShellCommand(normalized);
|
|
294
294
|
const firstToken = (tokens[0] || "").toLowerCase();
|
|
@@ -326,6 +326,20 @@ function evaluateSegmentPolicy(segment, overrideRuleIds) {
|
|
|
326
326
|
rule.test(context),
|
|
327
327
|
);
|
|
328
328
|
if (allowlistedRule) {
|
|
329
|
+
// `classifyAllShell` (Claude-Code 2.1.193 `autoMode.classifyAllShell`):
|
|
330
|
+
// don't fast-path the built-in verification allowlist to ALLOW (risk LOW,
|
|
331
|
+
// auto-approved). Downgrade to WARN so EVERY shell command is classified
|
|
332
|
+
// and routed through the ApprovalGate confirm — the allowlist still
|
|
333
|
+
// documents intent, but the agent can't silently run even `npm test`.
|
|
334
|
+
if (classifyAllShell) {
|
|
335
|
+
return {
|
|
336
|
+
allowed: true,
|
|
337
|
+
decision: SHELL_POLICY_DECISIONS.WARN,
|
|
338
|
+
reason: `${allowlistedRule.reason} (classifyAllShell: confirm required)`,
|
|
339
|
+
ruleId: allowlistedRule.id,
|
|
340
|
+
normalizedCommand: normalized,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
329
343
|
return {
|
|
330
344
|
allowed: true,
|
|
331
345
|
decision: SHELL_POLICY_DECISIONS.ALLOW,
|
|
@@ -350,6 +364,9 @@ function evaluateShellCommandPolicy(command, options = {}) {
|
|
|
350
364
|
const overrideRuleIds = Array.isArray(options.overrideRuleIds)
|
|
351
365
|
? new Set(options.overrideRuleIds)
|
|
352
366
|
: new Set();
|
|
367
|
+
// When set, allowlisted commands are classified (WARN) instead of fast-pathed
|
|
368
|
+
// (ALLOW) — see evaluateSegmentPolicy.
|
|
369
|
+
const classifyAllShell = options.classifyAllShell === true;
|
|
353
370
|
|
|
354
371
|
// Inspect EVERY segment of a compound command, not just the first — a
|
|
355
372
|
// dangerous segment after `&&` / `;` / `|` must still be caught. The most
|
|
@@ -368,7 +385,11 @@ function evaluateShellCommandPolicy(command, options = {}) {
|
|
|
368
385
|
|
|
369
386
|
let worst = null;
|
|
370
387
|
for (const segment of segments) {
|
|
371
|
-
const result = evaluateSegmentPolicy(
|
|
388
|
+
const result = evaluateSegmentPolicy(
|
|
389
|
+
segment,
|
|
390
|
+
overrideRuleIds,
|
|
391
|
+
classifyAllShell,
|
|
392
|
+
);
|
|
372
393
|
const sev = DECISION_SEVERITY[result.decision] ?? 1;
|
|
373
394
|
const worstSev = worst ? (DECISION_SEVERITY[worst.decision] ?? 1) : -1;
|
|
374
395
|
if (sev > worstSev) worst = result;
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
import fsDefault from "fs";
|
|
26
26
|
import pathDefault from "path";
|
|
27
|
+
import {
|
|
28
|
+
credentialFileReasonResolved,
|
|
29
|
+
credentialGuardDisabled,
|
|
30
|
+
} from "../lib/credential-guard.js";
|
|
27
31
|
|
|
28
32
|
export const DEFAULT_MAX_BYTES = 100 * 1024; // 100 KB per referenced file
|
|
29
33
|
export const DEFAULT_MAX_DIR_ENTRIES = 200;
|
|
@@ -181,6 +185,27 @@ function resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries }) {
|
|
|
181
185
|
};
|
|
182
186
|
}
|
|
183
187
|
if (stat.isFile()) {
|
|
188
|
+
// Refuse to inline a credential / secret file. `@file` expansion pulls
|
|
189
|
+
// file contents straight into the (possibly cloud) LLM prompt, so it is a
|
|
190
|
+
// second channel for file→LLM exfiltration that must honour the same guard
|
|
191
|
+
// as read_file / run_shell / the project-memory @import path. Checked on
|
|
192
|
+
// the RESOLVED path (the read follows symlinks, so a notes.txt → id_rsa
|
|
193
|
+
// link would otherwise leak the key) and BEFORE any read, so the secret
|
|
194
|
+
// bytes are never loaded. CC_CREDENTIAL_GUARD=0 opts out.
|
|
195
|
+
if (!credentialGuardDisabled()) {
|
|
196
|
+
const credReason = credentialFileReasonResolved(abs, {
|
|
197
|
+
cwd,
|
|
198
|
+
deps: { realpathSync: fs.realpathSync, resolve: path.resolve },
|
|
199
|
+
});
|
|
200
|
+
if (credReason) {
|
|
201
|
+
return {
|
|
202
|
+
kind: "blocked",
|
|
203
|
+
raw: cand,
|
|
204
|
+
rel: fsPath,
|
|
205
|
+
reason: credReason,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
184
209
|
// PDFs (pdf-parse needs the bytes) and line ranges (must reach the end
|
|
185
210
|
// line) need the whole file; a plain inline only ever shows the first
|
|
186
211
|
// maxBytes, so cap the read there instead of slurping the entire file.
|
|
@@ -222,7 +247,12 @@ function resolveRef(raw, { cwd, fs, path, maxBytes, maxDirEntries }) {
|
|
|
222
247
|
}
|
|
223
248
|
if (range) {
|
|
224
249
|
// Slice the requested lines (clamped to the file), then byte-cap them.
|
|
225
|
-
const sl = sliceLines(
|
|
250
|
+
const sl = sliceLines(
|
|
251
|
+
buf.toString("utf-8"),
|
|
252
|
+
range.start,
|
|
253
|
+
range.end,
|
|
254
|
+
maxBytes,
|
|
255
|
+
);
|
|
226
256
|
return {
|
|
227
257
|
kind: "file",
|
|
228
258
|
raw: cand,
|
|
@@ -296,7 +326,9 @@ function renderBlock(refs) {
|
|
|
296
326
|
} else if (ref.kind === "pdf") {
|
|
297
327
|
if (typeof ref.content === "string") {
|
|
298
328
|
const pageAttr =
|
|
299
|
-
ref.pageStart != null
|
|
329
|
+
ref.pageStart != null
|
|
330
|
+
? ` pages="${ref.pageStart}-${ref.pageEnd}"`
|
|
331
|
+
: "";
|
|
300
332
|
const attrs =
|
|
301
333
|
`path="${escapeAttr(ref.rel)}" bytes="${ref.bytes}" type="pdf"` +
|
|
302
334
|
pageAttr +
|
|
@@ -306,7 +338,9 @@ function renderBlock(refs) {
|
|
|
306
338
|
parts.push(`<file ${attrs}>`);
|
|
307
339
|
parts.push(ref.content);
|
|
308
340
|
if (ref.truncated) {
|
|
309
|
-
parts.push(
|
|
341
|
+
parts.push(
|
|
342
|
+
`\n… [truncated — PDF text exceeds ${DEFAULT_MAX_BYTES} bytes]`,
|
|
343
|
+
);
|
|
310
344
|
}
|
|
311
345
|
parts.push("</file>");
|
|
312
346
|
} else {
|
|
@@ -369,6 +403,13 @@ function _resolveAllRefs(text, opts) {
|
|
|
369
403
|
warnings.push(`@${ref.raw} — ${ref.message} (left as-is)`);
|
|
370
404
|
continue;
|
|
371
405
|
}
|
|
406
|
+
if (ref.kind === "blocked") {
|
|
407
|
+
// Credential/secret file — never read or injected; only a visible warning.
|
|
408
|
+
warnings.push(
|
|
409
|
+
`@${ref.raw} — refused: ${ref.reason}; not injected into the prompt`,
|
|
410
|
+
);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
372
413
|
refs.push(ref);
|
|
373
414
|
}
|
|
374
415
|
return { refs, warnings, maxBytes };
|
|
@@ -440,7 +481,10 @@ export async function expandFileRefsAsync(prompt, opts = {}) {
|
|
|
440
481
|
* when those are null), byte-capped at maxBytes. Throws code `PDF_LIB_MISSING`
|
|
441
482
|
* when the optional dependency is not installed.
|
|
442
483
|
*/
|
|
443
|
-
async function _extractPdfPages(
|
|
484
|
+
async function _extractPdfPages(
|
|
485
|
+
buffer,
|
|
486
|
+
{ firstPage, lastPage, maxBytes } = {},
|
|
487
|
+
) {
|
|
444
488
|
let pdfParse;
|
|
445
489
|
try {
|
|
446
490
|
// pdf-parse's index.js runs debug code under ESM (no module.parent), so
|
|
@@ -459,9 +503,9 @@ async function _extractPdfPages(buffer, { firstPage, lastPage, maxBytes } = {})
|
|
|
459
503
|
if (firstPage && n < firstPage) return "";
|
|
460
504
|
if (lastPage && n > lastPage) return "";
|
|
461
505
|
}
|
|
462
|
-
return pageData
|
|
463
|
-
|
|
464
|
-
|
|
506
|
+
return pageData
|
|
507
|
+
.getTextContent()
|
|
508
|
+
.then((tc) => (tc.items || []).map((it) => it.str).join(" "));
|
|
465
509
|
},
|
|
466
510
|
});
|
|
467
511
|
let out = String(data?.text || "").trim();
|