chainlesschain 0.162.124 → 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/.build-hash +1 -1
- package/src/assets/web-panel/assets/AIOps-BrTOcqIJ.js +1 -0
- package/src/assets/web-panel/assets/{ActionButton-COjZZKWG.js → ActionButton-na9cocmf.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-4ZDZE7lc.js → Analytics-BtDygkpt.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CYH5k2Rp.js → AppLayout-D_t5CS18.js} +5 -5
- package/src/assets/web-panel/assets/Audit-BYMZccCy.js +1 -0
- package/src/assets/web-panel/assets/{Backup-CiX61Qc2.js → Backup-D2qDfAdL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CDxIQokd.js → BaseInput-kj456Ju9.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CaKgdRab.js → Chat-BY0A0ZLE.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CjiWfu4s.js → Checkbox-DiBhtXsh.js} +1 -1
- package/src/assets/web-panel/assets/Codegen-B1ER0L0j.js +1 -0
- package/src/assets/web-panel/assets/{Col-ZKWREyNs.js → Col-CjLmza7D.js} +1 -1
- package/src/assets/web-panel/assets/Community-BNId05U7.js +1 -0
- package/src/assets/web-panel/assets/{Compact-DqknM98n.js → Compact-BuuMj7K1.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-DUuDycaJ.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-Dp29kHsC.js → Cowork-BKHiF6uL.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-zmEJAetz.js → Cron-C7p-lHnC.js} +2 -2
- package/src/assets/web-panel/assets/Crosschain-BvSOx8a6.js +1 -0
- package/src/assets/web-panel/assets/{DID-D_UFNzJ5.js → DID-D1k9jgHv.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-B9mCCnqY.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-CH7l3KCs.js → Dropdown-ge9UHSXF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bud2M3XX.js → EmailListRenderer-TZO7ppXi.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bd2_8uME.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
- package/src/assets/web-panel/assets/Federation-JLuA_8zp.js +1 -0
- package/src/assets/web-panel/assets/{FormItemContext-CpSH464Y.js → FormItemContext-C4w-rzNg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CANo8O-y.js → GenericCardRenderer-Btns4sV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-hvuZVoD3.js → Git-BheXMjzC.js} +2 -2
- package/src/assets/web-panel/assets/Governance-D9rLlatH.js +1 -0
- package/src/assets/web-panel/assets/Inference-DiklIMcd.js +1 -0
- package/src/assets/web-panel/assets/KnowledgeGraph-C70EWL03.js +1 -0
- package/src/assets/web-panel/assets/{Logs-DDkTnedu.js → Logs-Zt_MLI10.js} +2 -2
- package/src/assets/web-panel/assets/Marketplace-suQxES99.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-Qx78XyOT.js → McpTools-CclpCDpY.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXYDO1oT.js → Memory-B2lHOUwF.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-BMXKoDAD.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-DEDbWNIk.js → MobileProjects-a6mWVdBV.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dwa_vlR8.js → Mtc-BFpM4Fkj.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DAVkxhJv.js → MtcAudit-DDQkxkbS.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-C3upmgPN.js → Multisig-Crkd_zKQ.js} +3 -3
- package/src/assets/web-panel/assets/NLProgramming-Dpk5An7B.js +1 -0
- package/src/assets/web-panel/assets/{Notes-B1Oy3FbC.js → Notes-BrFnOMNz.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Bs4-Fc6b.js → NotificationSettings-CM0iApFM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CEy1pIeq.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-D4cJ1UNo.js → Organization-CHFW_38T.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-fdzvlF7x.js → Overflow-C-vl3EjV.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Y13PwXBA.js → P2P-BT7Slavq.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Ck1zCLe6.js → PdhVaultBrowser-DRUaULap.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CvEXP6LC.js → Permissions-DkhOAvMz.js} +2 -2
- package/src/assets/web-panel/assets/{PersonalDataHub-JeP7IWMW.js → PersonalDataHub-Dl7dSOvV.js} +3 -3
- package/src/assets/web-panel/assets/Pipeline-B3nVI4p6.js +1 -0
- package/src/assets/web-panel/assets/Privacy-Dv1SXx1Q.js +1 -0
- package/src/assets/web-panel/assets/{ProjectInit-DeWd9UcS.js → ProjectInit-DrAhVi5p.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DUpVuU9F.js → ProjectSettings-h-ggCYN_.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CM91hYdr.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DECW00ek.js → Providers-rfkZel3N.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Dl-pK9Io.js → QuickAsk-XepZB7h_.js} +1 -1
- package/src/assets/web-panel/assets/Recommend-DzQWsh1a.js +1 -0
- package/src/assets/web-panel/assets/Reputation-CDeaal0j.js +1 -0
- package/src/assets/web-panel/assets/Row-BN4aKhFH.js +1 -0
- package/src/assets/web-panel/assets/{RssFeed-iyQT5q9l.js → RssFeed-INP6VYAA.js} +3 -3
- package/src/assets/web-panel/assets/Search-ChPbwatu.js +1 -0
- package/src/assets/web-panel/assets/{Security-c4Jxf5Ih.js → Security-C97ZCY8N.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CRuisolj.js → Services-Cbtl2Ajs.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CCnzJkb-.js → Skeleton-B13sFxBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CZURCwZg.js → Skills-vI9zSIKX.js} +1 -1
- package/src/assets/web-panel/assets/Sla-Cr3oBH39.js +1 -0
- package/src/assets/web-panel/assets/SpeechSettings-BwemqPPV.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CJWVtd87.js → SyncSettings-CLA78P-Z.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-61lrQ_lS.js → Tasks-DBjQ_DOM.js} +1 -1
- package/src/assets/web-panel/assets/Templates-BkSLDkBs.js +1 -0
- package/src/assets/web-panel/assets/Tenant-Dn0nSlib.js +1 -0
- package/src/assets/web-panel/assets/{Terminal-CP15hcNS.js → Terminal-C8f4boru.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Sl5uXrkN.js → TimelineRenderer-DW_rMtJR.js} +1 -1
- package/src/assets/web-panel/assets/Tokens-BdffrKYV.js +1 -0
- package/src/assets/web-panel/assets/{Trigger-BLCQEOF2.js → Trigger-CX4CUg-Q.js} +1 -1
- package/src/assets/web-panel/assets/Trust-Cq9Bl7Od.js +1 -0
- package/src/assets/web-panel/assets/{UkeySign-Dwp0zXQ6.js → UkeySign-BQy18_VY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CIHA55Sx.js → VideoEditing-h5hzK7Vw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Hjajvnto.js → Wallet-BLotT3gw.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-DqSURUvI.js → WebAuthn-8cCANYLE.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B5bHRwUG.js → WorkflowEditor-Db6NwtAv.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DsheLKkG.js → chat-BPk1LbpL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CST2UkgL.js → colors-BnNal71p.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-yqEoy1L7.js → compact-item-W2VC0tcy.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Jwp78HFY.js → createContext-DceVsgnZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CVS5mFEP.js → hasIn-BEq6EDcp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLTUVd0V.js → index-5gIBUFBn.js} +2 -2
- package/src/assets/web-panel/assets/index-APCHxOkM.js +3 -0
- package/src/assets/web-panel/assets/index-B2dMhwZi.js +1 -0
- package/src/assets/web-panel/assets/{index-C0FZScMe.js → index-B8cPjrEH.js} +4 -4
- package/src/assets/web-panel/assets/{index-CjfN2CpJ.js → index-BDC5HsxZ.js} +1 -1
- package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
- package/src/assets/web-panel/assets/{index-SjxCCf5h.js → index-BUkDcIwJ.js} +2 -2
- package/src/assets/web-panel/assets/{index-Cjig5i3a.js → index-BWcCyH6-.js} +28 -26
- package/src/assets/web-panel/assets/{index-AnW25bEq.js → index-BgZikQoh.js} +1 -1
- package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
- package/src/assets/web-panel/assets/index-BzSzRWsw.js +55 -0
- package/src/assets/web-panel/assets/index-C1K9CsGr.js +1 -0
- package/src/assets/web-panel/assets/{index-DDAigsel.js → index-CBZVISK6.js} +2 -2
- package/src/assets/web-panel/assets/index-CEJN-R2L.js +1 -0
- package/src/assets/web-panel/assets/index-CG5dGC9E.js +1 -0
- package/src/assets/web-panel/assets/{index-DxaU_lza.js → index-CMGLpstm.js} +1 -1
- package/src/assets/web-panel/assets/index-CWrGCndd.js +1 -0
- package/src/assets/web-panel/assets/index-CbcvfpCV.js +1 -0
- package/src/assets/web-panel/assets/{index-De600Jjm.js → index-Ceqv4lI2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy0dNzkp.js → index-CsT6frT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDAPnZUs.js → index-D8PQKsDT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crk4KWLW.js → index-DGncdA-j.js} +1 -1
- package/src/assets/web-panel/assets/{index-R-VGFpi6.js → index-DHanQHO0.js} +3 -3
- package/src/assets/web-panel/assets/index-DaUBk28E.js +12 -0
- package/src/assets/web-panel/assets/{index-BzetOasL.js → index-Di2J4b4V.js} +1 -1
- package/src/assets/web-panel/assets/index-DtuLvWZN.js +21 -0
- package/src/assets/web-panel/assets/{index-Dgyn8-wN.js → index-DvS1J8xc.js} +2 -2
- package/src/assets/web-panel/assets/{index-CtyEOGuj.js → index-DxePJdwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzsKK0tQ.js → index-Et4XvL49.js} +2 -2
- package/src/assets/web-panel/assets/{index-BuI27WSx.js → index-GlFxgcj4.js} +2 -2
- package/src/assets/web-panel/assets/index-HV119Oui.js +1 -0
- package/src/assets/web-panel/assets/index-InGW87pj.js +13 -0
- package/src/assets/web-panel/assets/{index-Dxljh9Fu.js → index-P1-s8JR7.js} +2 -2
- package/src/assets/web-panel/assets/{index-CSBPc-sI.js → index-SdABCNoi.js} +2 -2
- package/src/assets/web-panel/assets/{index-BCHAUdel.js → index-WTAZbN-c.js} +1 -1
- package/src/assets/web-panel/assets/index-_2I-KzOj.js +1 -0
- package/src/assets/web-panel/assets/index-m1sVaWVU.js +1 -0
- package/src/assets/web-panel/assets/index-s37wwyeN.js +1 -0
- package/src/assets/web-panel/assets/{index-CRBbVS43.js → index-tyWABqp9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ClAjrsQ-.js → initDefaultProps-DzrCDb3j.js} +1 -1
- package/src/assets/web-panel/assets/motion-BQERVnE3.js +11 -0
- package/src/assets/web-panel/assets/{move-DMelzIW-.js → move-I9ACA5XJ.js} +1 -1
- package/src/assets/web-panel/assets/mtc-parser-Dd2Pw-tr.js +1 -0
- package/src/assets/web-panel/assets/{omit-oxecfU3b.js → omit-DwzYRzWV.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DJ5F5M6x.js → pickAttrs-BSGULyIL.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DdbKsant.js → placementArrow-tSYYjAts.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BUuM5cmS.js → responsiveObserve-Dy2lqikT.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DSV0ccvR.js → slide-RaB4Uq0c.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B-6oLAXf.js → statusUtils-CBpC_wZg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DdCAHtsZ.js → styleChecker-BEKwPWt5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-ZORkcoZd.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BFp46LoP.js → useFs-n24jutw5.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-H7rxgbdW.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-eIq4Tk0J.js → vnode-BSp2KYcj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CTNrv8-H.js → zoom-7UfQtTPh.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +51 -2
- package/src/commands/compliance.js +3 -1
- package/src/commands/ide.js +60 -5
- package/src/commands/incentive.js +22 -22
- package/src/commands/mtc.js +5 -1
- package/src/commands/review.js +47 -17
- package/src/commands/session.js +11 -2
- package/src/gateways/ws/message-dispatcher.js +149 -166
- package/src/gateways/ws/ws-session-gateway.js +95 -9
- package/src/harness/background-task-manager.js +5 -1
- package/src/harness/jsonl-session-store.js +90 -12
- package/src/harness/mcp-client.js +62 -21
- package/src/harness/prompt-compressor.js +24 -5
- package/src/harness/worktree-isolator.js +10 -1
- package/src/lib/agent-core.js +2 -0
- package/src/lib/audit-logger.js +3 -1
- package/src/lib/autonomous-agent.js +29 -0
- package/src/lib/chat-core.js +5 -2
- package/src/lib/chat-intent-service.js +82 -23
- package/src/lib/checkpoint-store.js +31 -4
- package/src/lib/claude-code-bridge.js +48 -3
- package/src/lib/config-manager.js +25 -1
- package/src/lib/cost-budget.js +13 -2
- package/src/lib/credential-guard.js +81 -1
- package/src/lib/git-integration.js +16 -2
- package/src/lib/goal-store.js +11 -0
- package/src/lib/hook-manager.js +4 -0
- package/src/lib/interaction-adapter.js +32 -11
- package/src/lib/jetbrains-bridge.js +147 -0
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/llm-config-defaults.js +37 -0
- package/src/lib/llm-pricing.js +11 -4
- package/src/lib/llm-providers.js +11 -1
- package/src/lib/mcp-oauth.js +88 -10
- package/src/lib/mcp-serve.js +29 -0
- package/src/lib/orchestrator.js +38 -2
- 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-bang-memorize.js +9 -1
- package/src/lib/repl-denials.js +137 -0
- package/src/lib/runnable-provider.js +7 -1
- 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/session-insights.js +13 -6
- package/src/lib/session-usage.js +21 -3
- 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/status-line.cjs +4 -1
- package/src/lib/stream-retry.js +27 -0
- package/src/lib/sub-agent-context.js +9 -0
- package/src/lib/turn-context.js +15 -5
- package/src/repl/agent-repl.js +110 -8
- package/src/runtime/__tests__/message-roles.test.js +36 -3
- package/src/runtime/agent-core.js +179 -52
- 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 +96 -2
- package/src/runtime/headless-stream.js +78 -3
- package/src/runtime/mcp-config.js +114 -4
- package/src/runtime/message-roles.js +14 -1
- package/src/runtime/pipe-safety.js +51 -0
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/AIOps-BsDJlD_l.js +0 -1
- package/src/assets/web-panel/assets/Audit-BKlFbLdl.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +0 -1
- package/src/assets/web-panel/assets/Codegen-tKWGzajw.js +0 -1
- package/src/assets/web-panel/assets/Community-CmLuPBUU.js +0 -1
- package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +0 -1
- package/src/assets/web-panel/assets/Crosschain-Sb-uM7Ty.js +0 -1
- package/src/assets/web-panel/assets/Dashboard-Btag698v.js +0 -3
- package/src/assets/web-panel/assets/Federation-1jhstF5P.js +0 -1
- package/src/assets/web-panel/assets/Governance-BQrWksKt.js +0 -1
- package/src/assets/web-panel/assets/Inference-jEBTp12v.js +0 -1
- package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-9Yi32avt.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +0 -1
- package/src/assets/web-panel/assets/NLProgramming-DO3CZ0_z.js +0 -1
- package/src/assets/web-panel/assets/Pipeline-BZ7wRzG1.js +0 -1
- package/src/assets/web-panel/assets/Privacy-BJ6qj9Hv.js +0 -1
- package/src/assets/web-panel/assets/Projects-y1WbI337.js +0 -1
- package/src/assets/web-panel/assets/Recommend-Bju04wTd.js +0 -1
- package/src/assets/web-panel/assets/Reputation-BUPa3nEk.js +0 -1
- package/src/assets/web-panel/assets/Row-WYqqaq_5.js +0 -1
- package/src/assets/web-panel/assets/Search-CrfwJF0R.js +0 -1
- package/src/assets/web-panel/assets/Sla-CrMzaEi2.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +0 -1
- package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +0 -1
- package/src/assets/web-panel/assets/Tenant-DrDv1FFc.js +0 -1
- package/src/assets/web-panel/assets/Tokens-yRIZTVsR.js +0 -1
- package/src/assets/web-panel/assets/Trust-D5xq8z6s.js +0 -1
- package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +0 -1
- package/src/assets/web-panel/assets/index-BeblNJDH.js +0 -1
- package/src/assets/web-panel/assets/index-C130LLPq.js +0 -1
- package/src/assets/web-panel/assets/index-C8rq85gu.js +0 -1
- package/src/assets/web-panel/assets/index-CIE2n8k_.js +0 -1
- package/src/assets/web-panel/assets/index-CTd3lDxp.js +0 -13
- package/src/assets/web-panel/assets/index-CUWj5L2s.js +0 -1
- package/src/assets/web-panel/assets/index-ChRL6rgA.js +0 -3
- package/src/assets/web-panel/assets/index-CrlRa054.js +0 -1
- package/src/assets/web-panel/assets/index-Cu6Ql64n.js +0 -1
- package/src/assets/web-panel/assets/index-Cx_t5rWw.js +0 -12
- package/src/assets/web-panel/assets/index-DZfnYE6e.js +0 -1
- package/src/assets/web-panel/assets/index-D_1b7uh6.js +0 -55
- package/src/assets/web-panel/assets/index-D_e9gwfn.js +0 -1
- package/src/assets/web-panel/assets/index-DbxAlpPC.js +0 -21
- package/src/assets/web-panel/assets/index-DiK4vSZa.js +0 -1
- package/src/assets/web-panel/assets/index-DpYn5v2k.js +0 -1
- package/src/assets/web-panel/assets/index-diWkx2Wq.js +0 -1
- package/src/assets/web-panel/assets/motion-BalXv_60.js +0 -11
- package/src/assets/web-panel/assets/mtc-parser-CEQffxds.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,
|
|
@@ -1127,14 +1156,16 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1127
1156
|
// customizations), this safety surface stays on under --safe-mode by design.
|
|
1128
1157
|
if (!ruleAllowed && (name === "read_file" || name === "run_shell")) {
|
|
1129
1158
|
const {
|
|
1130
|
-
|
|
1159
|
+
credentialFileReasonResolved,
|
|
1131
1160
|
commandReadsCredentials,
|
|
1132
1161
|
credentialGuardDisabled,
|
|
1133
1162
|
} = await import("../lib/credential-guard.js");
|
|
1134
1163
|
if (!credentialGuardDisabled(process.env)) {
|
|
1135
1164
|
let credReason = null;
|
|
1136
1165
|
if (name === "read_file" && args?.path) {
|
|
1137
|
-
|
|
1166
|
+
// Resolve symlinks: an innocent-named link to a credential file must not
|
|
1167
|
+
// skip the prompt (fs.readFileSync follows the link).
|
|
1168
|
+
credReason = credentialFileReasonResolved(args.path, { cwd });
|
|
1138
1169
|
} else if (name === "run_shell" && args?.command) {
|
|
1139
1170
|
const hit = commandReadsCredentials(args.command);
|
|
1140
1171
|
credReason = hit ? hit.reason : null;
|
|
@@ -1592,6 +1623,47 @@ export function renderNotebook(text) {
|
|
|
1592
1623
|
return lines.join("\n");
|
|
1593
1624
|
}
|
|
1594
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
|
+
|
|
1595
1667
|
/**
|
|
1596
1668
|
* Inner tool execution — no hooks, no plan-mode checks.
|
|
1597
1669
|
*/
|
|
@@ -1610,6 +1682,7 @@ async function executeToolInner(
|
|
|
1610
1682
|
mcpClient,
|
|
1611
1683
|
llmOptions,
|
|
1612
1684
|
shellPolicyOverrides,
|
|
1685
|
+
classifyAllShell = false,
|
|
1613
1686
|
approvalGate,
|
|
1614
1687
|
shellConfirm,
|
|
1615
1688
|
additionalDirectories,
|
|
@@ -1661,6 +1734,13 @@ async function executeToolInner(
|
|
|
1661
1734
|
if (!fs.existsSync(filePath)) {
|
|
1662
1735
|
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
1663
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
|
+
}
|
|
1664
1744
|
const content = fs.readFileSync(filePath, "utf8");
|
|
1665
1745
|
// Jupyter notebooks: render a compact cell listing (index/id/type/source,
|
|
1666
1746
|
// outputs summarized) so the model can find cells for notebook_edit
|
|
@@ -1761,20 +1841,40 @@ async function executeToolInner(
|
|
|
1761
1841
|
if (!fs.existsSync(filePath)) {
|
|
1762
1842
|
return attachDescriptor({ error: `File not found: ${filePath}` });
|
|
1763
1843
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
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" });
|
|
1767
1851
|
}
|
|
1768
|
-
const
|
|
1852
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
1853
|
+
const replaceAll = args.replace_all === true;
|
|
1854
|
+
const { count, newContent } = applyLiteralEdit(
|
|
1855
|
+
content,
|
|
1769
1856
|
args.old_string,
|
|
1770
|
-
|
|
1857
|
+
args.new_string,
|
|
1858
|
+
replaceAll,
|
|
1771
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
|
+
}
|
|
1772
1871
|
const wrote = writeFileVerified(filePath, newContent);
|
|
1773
1872
|
if (wrote.error) return attachDescriptor({ error: wrote.error });
|
|
1774
1873
|
return attachDescriptor({
|
|
1775
1874
|
success: true,
|
|
1776
1875
|
path: filePath,
|
|
1777
1876
|
size: wrote.size,
|
|
1877
|
+
replaced: replaceAll ? count : 1,
|
|
1778
1878
|
});
|
|
1779
1879
|
}
|
|
1780
1880
|
|
|
@@ -1829,9 +1929,12 @@ async function executeToolInner(
|
|
|
1829
1929
|
}
|
|
1830
1930
|
|
|
1831
1931
|
case "run_shell": {
|
|
1832
|
-
const shellPolicyOpts =
|
|
1833
|
-
|
|
1834
|
-
|
|
1932
|
+
const shellPolicyOpts = {
|
|
1933
|
+
...(shellPolicyOverrides
|
|
1934
|
+
? { overrideRuleIds: shellPolicyOverrides }
|
|
1935
|
+
: {}),
|
|
1936
|
+
...(classifyAllShell ? { classifyAllShell: true } : {}),
|
|
1937
|
+
};
|
|
1835
1938
|
const override = getRuntimeToolDescriptorByCommand(args.command);
|
|
1836
1939
|
let shellPolicy;
|
|
1837
1940
|
let approvalOutcome = null;
|
|
@@ -1855,12 +1958,18 @@ async function executeToolInner(
|
|
|
1855
1958
|
policy: gated.policy,
|
|
1856
1959
|
};
|
|
1857
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}" ` : "";
|
|
1858
1967
|
return attachDescriptor(
|
|
1859
1968
|
{
|
|
1860
1969
|
error:
|
|
1861
1970
|
gated.via === "shell-policy"
|
|
1862
|
-
? `[Shell Policy] ${gated.reason}
|
|
1863
|
-
: `[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.`,
|
|
1864
1973
|
shellCommandPolicy: shellPolicy,
|
|
1865
1974
|
approval: approvalOutcome,
|
|
1866
1975
|
},
|
|
@@ -1872,7 +1981,7 @@ async function executeToolInner(
|
|
|
1872
1981
|
if (!shellPolicy.allowed) {
|
|
1873
1982
|
return attachDescriptor(
|
|
1874
1983
|
{
|
|
1875
|
-
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.`,
|
|
1876
1985
|
shellCommandPolicy: shellPolicy,
|
|
1877
1986
|
},
|
|
1878
1987
|
override || runtimeDescriptor,
|
|
@@ -2135,12 +2244,17 @@ async function executeToolInner(
|
|
|
2135
2244
|
args: { language: args.language },
|
|
2136
2245
|
});
|
|
2137
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}" ` : "";
|
|
2138
2251
|
return attachDescriptor({
|
|
2139
|
-
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.`,
|
|
2140
2253
|
approval: {
|
|
2141
2254
|
decision: gate.decision,
|
|
2142
2255
|
via: gate.via,
|
|
2143
2256
|
riskLevel: "high",
|
|
2257
|
+
policy: gate.policy,
|
|
2144
2258
|
},
|
|
2145
2259
|
});
|
|
2146
2260
|
}
|
|
@@ -2301,13 +2415,14 @@ async function executeToolInner(
|
|
|
2301
2415
|
? [path.resolve(cwd, args.directory)]
|
|
2302
2416
|
: [cwd, ...extraRoots];
|
|
2303
2417
|
const isContent = Boolean(args.content_search);
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
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;
|
|
2311
2426
|
|
|
2312
2427
|
// Credential guard (Claude-Code 2.1.189 parity): a CONTENT search must not
|
|
2313
2428
|
// become a side channel that exfils secrets the read_file / run_shell
|
|
@@ -2324,34 +2439,34 @@ async function executeToolInner(
|
|
|
2324
2439
|
const redactedCreds = new Set();
|
|
2325
2440
|
for (const root of roots) {
|
|
2326
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
|
+
};
|
|
2327
2451
|
try {
|
|
2328
2452
|
if (!fs.existsSync(root)) continue;
|
|
2329
2453
|
const output = execSync(cmd, {
|
|
2330
2454
|
cwd: root,
|
|
2331
2455
|
encoding: "utf8",
|
|
2332
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,
|
|
2333
2462
|
});
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
if (credentialFileReason(src)) {
|
|
2342
|
-
redactedCreds.add(src.replace(/\\/g, "/"));
|
|
2343
|
-
seen.add(v);
|
|
2344
|
-
continue;
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
// Qualify with the root so multi-root results stay unambiguous.
|
|
2348
|
-
const labeled = roots.length > 1 ? `${root}: ${v}` : v;
|
|
2349
|
-
seen.add(v);
|
|
2350
|
-
hits.push(labeled);
|
|
2351
|
-
if (hits.length >= 20) break;
|
|
2352
|
-
}
|
|
2353
|
-
} catch {
|
|
2354
|
-
// 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);
|
|
2355
2470
|
}
|
|
2356
2471
|
}
|
|
2357
2472
|
|
|
@@ -2374,6 +2489,12 @@ async function executeToolInner(
|
|
|
2374
2489
|
if (!fs.existsSync(dirPath)) {
|
|
2375
2490
|
return attachDescriptor({ error: `Directory not found: ${dirPath}` });
|
|
2376
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
|
+
}
|
|
2377
2498
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
2378
2499
|
return attachDescriptor({
|
|
2379
2500
|
entries: entries.map((e) => ({
|
|
@@ -3683,7 +3804,9 @@ const _STREAM_STALL = Symbol("stream-stall");
|
|
|
3683
3804
|
* @param {object} opts { signal?, retries?, baseDelayMs?, onRetry?, sleep? }
|
|
3684
3805
|
*/
|
|
3685
3806
|
export async function _retryStreamingChat(streamFn, opts = {}) {
|
|
3686
|
-
|
|
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();
|
|
3687
3810
|
const base = opts.baseDelayMs ?? STREAM_RETRY_BASE_MS;
|
|
3688
3811
|
const signal = opts.signal;
|
|
3689
3812
|
const sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -4499,6 +4622,10 @@ export async function* agentLoop(messages, options) {
|
|
|
4499
4622
|
parentMessages: messages, // pass parent messages for sub-agent auto-condensation
|
|
4500
4623
|
interaction: options.interaction || null,
|
|
4501
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,
|
|
4502
4629
|
approvalGate: options.approvalGate || null,
|
|
4503
4630
|
shellConfirm: options.shellConfirm || null,
|
|
4504
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();
|