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.
Files changed (190) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DcqNhZhA.js → AIOps-BrTOcqIJ.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BigtmPoq.js → ActionButton-na9cocmf.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-1J_X_HF4.js → Analytics-BtDygkpt.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-DlcDG_a_.js → AppLayout-D_t5CS18.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-Do-y8_3w.js → Audit-BYMZccCy.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-S2Df-50Y.js → Backup-D2qDfAdL.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-D5kgWJfk.js → BaseInput-kj456Ju9.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-aUcp3kN1.js → Chat-BY0A0ZLE.js} +5 -5
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-DTg1U7Xs.js → Checkbox-DiBhtXsh.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-YO9ZZ4Ky.js → Codegen-B1ER0L0j.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-BHonE9fU.js → Col-CjLmza7D.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DA2AlEFh.js → Community-BNId05U7.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DULxLRNg.js → Compact-BuuMj7K1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-BZqfuuZL.js → Compliance-DUuDycaJ.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CJe3vyMS.js → Cowork-BKHiF6uL.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-CeV8AtRu.js → Cron-C7p-lHnC.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CAg66zS5.js → Crosschain-BvSOx8a6.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-Dtup5JZm.js → DID-D1k9jgHv.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-DAR_8PNy.js → Dashboard-B9mCCnqY.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-K5bTaCYN.js → Dropdown-ge9UHSXF.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-C890uErx.js → EmailListRenderer-TZO7ppXi.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-B-Tj_8yM.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-C6WZ98ni.js → Federation-JLuA_8zp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-D-LTfGWd.js → FormItemContext-C4w-rzNg.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-C80DK4W7.js → GenericCardRenderer-Btns4sV9.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-Cjvvv3zW.js → Git-BheXMjzC.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-C0BND77H.js → Governance-D9rLlatH.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-BL9kTtSu.js → Inference-DiklIMcd.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-CnVTBmJf.js → KnowledgeGraph-C70EWL03.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CbCbg7K6.js → Logs-Zt_MLI10.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CG5On-fH.js → Marketplace-suQxES99.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-AYYDZZK7.js → McpTools-CclpCDpY.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-tMWbMDQq.js → Memory-B2lHOUwF.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-BAOsf4wB.js → MobileBridge-BMXKoDAD.js} +1 -1
  37. package/src/assets/web-panel/assets/{MobileProjects-BT-2komQ.js → MobileProjects-a6mWVdBV.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-BFwfC63n.js → Mtc-BFpM4Fkj.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-DYG3CWdH.js → MtcAudit-DDQkxkbS.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-KJwd0yWT.js → Multisig-Crkd_zKQ.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BmL5dNWm.js → NLProgramming-Dpk5An7B.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DmkaVaMc.js → Notes-BrFnOMNz.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-tmVQszDZ.js → NotificationSettings-CM0iApFM.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-BtrR7anf.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-pppktQyW.js → Organization-CHFW_38T.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-D3lBoQDA.js → Overflow-C-vl3EjV.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-BgE2qGlZ.js → P2P-BT7Slavq.js} +2 -2
  48. package/src/assets/web-panel/assets/PdhVaultBrowser-DRUaULap.js +7 -0
  49. package/src/assets/web-panel/assets/{Permissions-BR8no2Xe.js → Permissions-DkhOAvMz.js} +3 -3
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DabbyQEF.js → PersonalDataHub-Dl7dSOvV.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-n4sNpEz8.js → Pipeline-B3nVI4p6.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CGNp4n8M.js → Privacy-Dv1SXx1Q.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-CAVA5VsX.js → ProjectInit-DrAhVi5p.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-GCgkfM7A.js → ProjectSettings-h-ggCYN_.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-BYK17p52.js → Projects-CM91hYdr.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-DpUT5W-d.js → Providers-rfkZel3N.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-ZJxTb7vi.js → QuickAsk-XepZB7h_.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CIzFRDk1.js → Recommend-DzQWsh1a.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-lQ_WDNZn.js → Reputation-CDeaal0j.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-1MEtrgtF.js → Row-BN4aKhFH.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-SSgcPPl4.js → RssFeed-INP6VYAA.js} +2 -2
  62. package/src/assets/web-panel/assets/{Search-CcTMOGNY.js → Search-ChPbwatu.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-BA1KMaDx.js → Security-C97ZCY8N.js} +3 -3
  64. package/src/assets/web-panel/assets/{Services-_aj7czZn.js → Services-Cbtl2Ajs.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-BWgg_0Zr.js → Skeleton-B13sFxBQ.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-C2ZIziYM.js → Skills-vI9zSIKX.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CYxp4axY.js → Sla-Cr3oBH39.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-C7Csp1jV.js → SpeechSettings-BwemqPPV.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-svGeswiw.js → SyncSettings-CLA78P-Z.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-ClISj6oK.js → Tasks-DBjQ_DOM.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-CmO-Fp7r.js → Templates-BkSLDkBs.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-a3Ac3lxz.js → Tenant-Dn0nSlib.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DyJIRh81.js → Terminal-C8f4boru.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-acXS5N5h.js → TimelineRenderer-DW_rMtJR.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-DJW0KqV4.js → Tokens-BdffrKYV.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-0HeTi4r6.js → Trigger-CX4CUg-Q.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-aFym34eo.js → Trust-Cq9Bl7Od.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-CYhszHJv.js → UkeySign-BQy18_VY.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BbJxPF9X.js → VideoEditing-h5hzK7Vw.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-DDthEwiu.js → Wallet-BLotT3gw.js} +2 -2
  81. package/src/assets/web-panel/assets/{WebAuthn-DQ6McYPg.js → WebAuthn-8cCANYLE.js} +2 -2
  82. package/src/assets/web-panel/assets/{WorkflowEditor-CnF8zRsi.js → WorkflowEditor-Db6NwtAv.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-Dzkx2gTP.js → chat-BPk1LbpL.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-35T51Oo5.js → colors-BnNal71p.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-TtzNrlu1.js → compact-item-W2VC0tcy.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-Y1LkWrYb.js → createContext-DceVsgnZ.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-BOYw1BgS.js → hasIn-BEq6EDcp.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-BtlJWaSP.js → index-5gIBUFBn.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-CLqC2sRT.js → index-APCHxOkM.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-C7Wt5feN.js → index-B2dMhwZi.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CqhwG1ox.js → index-B8cPjrEH.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BxWUEFKy.js → index-BDC5HsxZ.js} +1 -1
  94. package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
  95. package/src/assets/web-panel/assets/{index-NM9Oke2k.js → index-BUkDcIwJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BWgNn9As.js → index-BWcCyH6-.js} +3 -3
  97. package/src/assets/web-panel/assets/{index-CVrUs6rf.js → index-BgZikQoh.js} +1 -1
  98. package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
  99. package/src/assets/web-panel/assets/{index-D6tG4B25.js → index-BzSzRWsw.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DjeUZxE5.js → index-C1K9CsGr.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DmkG479D.js → index-CBZVISK6.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bw1iOGhM.js → index-CEJN-R2L.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-G4ClSmJc.js → index-CG5dGC9E.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D47robkX.js → index-CMGLpstm.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BKRlWHUp.js → index-CWrGCndd.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DhMSOd_2.js → index-CbcvfpCV.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dx0hIfC-.js → index-Ceqv4lI2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CdyFg4FI.js → index-CsT6frT8.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-B7aKAZzS.js → index-D8PQKsDT.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-8jffdb6b.js → index-DGncdA-j.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-XIuZV9by.js → index-DHanQHO0.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Dhfw-BXs.js → index-DaUBk28E.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-hyqEKkBT.js → index-Di2J4b4V.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DAizH273.js → index-DtuLvWZN.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CPGNc2tF.js → index-DvS1J8xc.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-KgXrlfNF.js → index-DxePJdwP.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DFhlelzN.js → index-Et4XvL49.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-zF77jnI2.js → index-GlFxgcj4.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Cs82BHAj.js → index-HV119Oui.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dof7lWA_.js → index-InGW87pj.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Bs69SI96.js → index-P1-s8JR7.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D4BgCBa3.js → index-SdABCNoi.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BSpFe6j5.js → index-WTAZbN-c.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D7dCBw_M.js → index-_2I-KzOj.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BGPEaeB1.js → index-m1sVaWVU.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CZ16GlOs.js → index-s37wwyeN.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D2od7Bgx.js → index-tyWABqp9.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-DD5y5msp.js → initDefaultProps-DzrCDb3j.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BuoutMia.js → motion-BQERVnE3.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DkpjqOXg.js → move-I9ACA5XJ.js} +1 -1
  131. package/src/assets/web-panel/assets/{mtc-parser-BmGJD-v-.js → mtc-parser-Dd2Pw-tr.js} +1 -1
  132. package/src/assets/web-panel/assets/{omit-DftUE0Sz.js → omit-DwzYRzWV.js} +1 -1
  133. package/src/assets/web-panel/assets/{pickAttrs-DDRb9FIu.js → pickAttrs-BSGULyIL.js} +1 -1
  134. package/src/assets/web-panel/assets/{placementArrow-DPgtxOOD.js → placementArrow-tSYYjAts.js} +1 -1
  135. package/src/assets/web-panel/assets/{responsiveObserve-BQFRzFeZ.js → responsiveObserve-Dy2lqikT.js} +1 -1
  136. package/src/assets/web-panel/assets/{slide-DQ4lmUSz.js → slide-RaB4Uq0c.js} +1 -1
  137. package/src/assets/web-panel/assets/{statusUtils-C1TR4ZiQ.js → statusUtils-CBpC_wZg.js} +1 -1
  138. package/src/assets/web-panel/assets/{styleChecker-B-Suj978.js → styleChecker-BEKwPWt5.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFlexGapSupport-Q7bDZ3EI.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
  140. package/src/assets/web-panel/assets/{useFs-DHjRLyQH.js → useFs-n24jutw5.js} +1 -1
  141. package/src/assets/web-panel/assets/{usePersonalDataHub-lQOvCvCr.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
  142. package/src/assets/web-panel/assets/{vnode-Ie0g4-p3.js → vnode-BSp2KYcj.js} +1 -1
  143. package/src/assets/web-panel/assets/{zoom-CHP0YEoH.js → zoom-7UfQtTPh.js} +1 -1
  144. package/src/assets/web-panel/index.html +1 -1
  145. package/src/commands/agent.js +41 -2
  146. package/src/commands/compliance.js +3 -1
  147. package/src/commands/review.js +47 -17
  148. package/src/commands/session.js +11 -2
  149. package/src/gateways/ws/message-dispatcher.js +145 -165
  150. package/src/gateways/ws/ws-session-gateway.js +39 -5
  151. package/src/harness/jsonl-session-store.js +39 -12
  152. package/src/harness/worktree-isolator.js +5 -0
  153. package/src/lib/agent-core.js +1 -0
  154. package/src/lib/audit-logger.js +3 -1
  155. package/src/lib/chat-core.js +5 -2
  156. package/src/lib/chat-intent-service.js +68 -21
  157. package/src/lib/config-manager.js +25 -1
  158. package/src/lib/credential-guard.js +43 -1
  159. package/src/lib/git-integration.js +5 -0
  160. package/src/lib/interaction-adapter.js +32 -11
  161. package/src/lib/jsonl-session-store.js +1 -0
  162. package/src/lib/llm-config-defaults.js +37 -0
  163. package/src/lib/mcp-oauth.js +76 -10
  164. package/src/lib/permission-engine.js +4 -1
  165. package/src/lib/pipeline-orchestrator.js +20 -2
  166. package/src/lib/project-instructions.js +13 -0
  167. package/src/lib/repl-denials.js +137 -0
  168. package/src/lib/safe-json.js +22 -0
  169. package/src/lib/sandbox-v2.js +7 -6
  170. package/src/lib/search-command.js +65 -0
  171. package/src/lib/settings-hooks.cjs +133 -0
  172. package/src/lib/settings-loader.cjs +16 -7
  173. package/src/lib/social-graph.js +3 -1
  174. package/src/lib/stream-retry.js +27 -0
  175. package/src/lib/turn-context.js +15 -5
  176. package/src/repl/agent-repl.js +81 -8
  177. package/src/runtime/__tests__/message-roles.test.js +36 -3
  178. package/src/runtime/agent-core.js +175 -50
  179. package/src/runtime/coding-agent-contract-shared.cjs +9 -2
  180. package/src/runtime/coding-agent-shell-policy.cjs +23 -2
  181. package/src/runtime/file-ref-expander.js +51 -7
  182. package/src/runtime/headless-runner.js +83 -2
  183. package/src/runtime/headless-stream.js +69 -3
  184. package/src/runtime/mcp-config.js +42 -1
  185. package/src/runtime/message-roles.js +14 -1
  186. package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +0 -1
  187. package/src/assets/web-panel/assets/PdhVaultBrowser-D26Bz5gS.js +0 -7
  188. package/src/assets/web-panel/assets/devWarning-D_lwLZ6O.js +0 -1
  189. package/src/assets/web-panel/assets/index-CEwuCM44.js +0 -1
  190. 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
- !content.includes(args.old_string)
806
+ args.old_string === "" ||
807
+ typeof args.new_string !== "string"
785
808
  ) {
786
809
  return null;
787
810
  }
788
- return {
789
- filePath,
790
- newContent: content.replace(args.old_string, () => args.new_string),
791
- originalText: content,
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
- const content = fs.readFileSync(filePath, "utf8");
1767
- if (!content.includes(args.old_string)) {
1768
- return attachDescriptor({ error: "old_string not found in file" });
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 newContent = content.replace(
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
- () => args.new_string,
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 = shellPolicyOverrides
1835
- ? { overrideRuleIds: shellPolicyOverrides }
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
- const cmd = isContent
2307
- ? process.platform === "win32"
2308
- ? `findstr /s /i /n "${args.pattern}" *`
2309
- : `grep -r -l -i "${args.pattern}" . --include="*" 2>/dev/null | head -20`
2310
- : process.platform === "win32"
2311
- ? `dir /s /b *${args.pattern}* 2>NUL`
2312
- : `find . -name "*${args.pattern}*" -type f 2>/dev/null | head -20`;
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
- for (const line of output.trim().split("\n")) {
2337
- const v = line.trim();
2338
- if (!v || seen.has(v)) continue;
2339
- if (isContent) {
2340
- // content hit is `file:line:text` (findstr /n) or a bare filename
2341
- // (grep -l); pull the source file and redact credential matches.
2342
- const src = v.match(/^(.+?):\d+:/)?.[1] ?? v;
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
- const retries = opts.retries ?? STREAM_RETRY_MAX;
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: "Replace a specific string in a file with new content",
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: "Exact string to find and replace",
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(segment, overrideRuleIds);
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(buf.toString("utf-8"), range.start, range.end, maxBytes);
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 ? ` pages="${ref.pageStart}-${ref.pageEnd}"` : "";
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(`\n… [truncated — PDF text exceeds ${DEFAULT_MAX_BYTES} bytes]`);
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(buffer, { firstPage, lastPage, maxBytes } = {}) {
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.getTextContent().then((tc) =>
463
- (tc.items || []).map((it) => it.str).join(" "),
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();