chainlesschain 0.166.32 → 0.166.36

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 (174) hide show
  1. package/README.md +24 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-DKUOkYex.js → AIOps-Zs-zPwVG.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-B3luFrPA.js → ActionButton-HZlxM6H0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-jbZ8R2S3.js → Analytics-CXyThRDb.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CFS2vzPS.js → AppLayout-DeyYh_EF.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-sS0Qpy5q.js → Artifacts-BQDtMwkx.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CZ7kfjCG.js → Audit-DNH4UOux.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BpWc5SgP.js → BackgroundAgents-BkKdIxE6.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DpEYnvoy.js → Backup-DYy4aJom.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-kFzUGLnl.js → BaseInput-DjoyGVYm.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BP_8aAfJ.js → Chat-D86ZYgOD.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-yFeZ15D4.js → ChatBubbleRenderer-RxI_YYth.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-HR1HQ06d.js → Checkbox-C7p8UY99.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-DxAqDPjT.js → Codegen-DnapqgV5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BaCLih7J.js → Col-C29J2DBT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-ZX64uW8D.js → Community-Bf3KeLCr.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-CLtMoXTt.js → Compact-DA83wB48.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BlyxBDrs.js → Compliance-CXXVgRFG.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-CtQZIYPh.js → Cowork-B28Ftuqv.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-BebN-ajI.js → Cron-CO-BcnDf.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-3gRU0-58.js → Crosschain-BmgPMFCs.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-DSVTtLnb.js → DID-CYW9WslA.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CvnhOWqc.js → Dashboard-CWWhVWds.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BhdofmCJ.js → Dropdown-CjTuc1-n.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-Dhpua9Lo.js → EmailListRenderer-BrROAu4d.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DA1mLqQ4.js → FamilyGuardDashboard-Ci8Lmk8W.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-D6SUyKGb.js → Federation-CKzKVLE_.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BcMiwguB.js → FormItemContext-CC2CK5PQ.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CGEB1WcH.js → GenericCardRenderer-BYa3ElyT.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DGCNNqR_.js → Git-Bmn3ucA4.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-D4HVyFdL.js → Governance-DqEOXitg.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BOoEmvKl.js → Inference-BjfeExba.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-ECLI0C0_.js → KnowledgeGraph-CBTyIFlJ.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Cf9GjHtX.js → Logs-KZfA_M9Q.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-C3J59f8v.js → MarkdownRenderer-BG7vi3V4.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-DeL4rOAJ.js → Marketplace-BFeK4he0.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-Dvn3KpLW.js → McpTools-Brg7uUoK.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-CSlLy-aw.js → Memory-Ce4AmyX3.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-yNIe8-74.js → MobileBridge-Dx1VBL_E.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-CsS8Lozw.js → MobileProjects-DPyRr2Xl.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-Q7iMzSds.js → Mtc-BzRmtDQU.js} +3 -3
  43. package/src/assets/web-panel/assets/{MtcAudit-CW71IQrK.js → MtcAudit-DvZ5kLvJ.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-pursAeeT.js → Multisig-D8uGUjX1.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DtNo1qqO.js → NLProgramming-igc-TQE5.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-B1xUFwUT.js → Notes-CTqDYlek.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BgmQs_J8.js → NotificationSettings-CkcLa2rb.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DI9t8YZl.js → OrderTableRenderer-BfScYvnH.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BbrrT6gR.js → Organization-BjKK9SdB.js} +2 -2
  50. package/src/assets/web-panel/assets/{Overflow-BcPi4YUc.js → Overflow-BkcghTPE.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DfRteW76.js → P2P-l9f34Xu8.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-De81lu5Q.js → PdhVaultBrowser-yVXWmwWe.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-DLCRaOe3.js → Permissions-DeSYs0VV.js} +3 -3
  54. package/src/assets/web-panel/assets/{PersonalDataHub-fuTL6z8u.js → PersonalDataHub-CmKmhy0q.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-D3ztsQDw.js → Pipeline-DBxS4R_r.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-4f-SQ4ie.js → Privacy-CKngdGw3.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CC_ZFjrV.js → ProjectInit-0Pso0jXN.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-C9FWHcne.js → ProjectSettings-BHrqWQ6r.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-CR3ETMQQ.js → Projects-B8wIh9PK.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-BWcbdgXx.js → Providers-CXFTOHRV.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BavxpdIK.js → QrScannerModal-DfN0c_BN.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-CZ7gupY4.js → QuickAsk-v1Z5hty_.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BQNpBYLL.js → Recommend-DOaVmZUi.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-2LU0Vr44.js → RemoteSession--EWcPBY1.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-oYWo7qyp.js → Reputation-DUWFSNvV.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-B8Yj-Csx.js → Row-0-bFnXWF.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CbxKbHJr.js → RssFeed-B_uTfubW.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-DGe-u9s0.js → Search-CqxiTLfB.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-BwjfMAFI.js → Security-CGoHOz5B.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-BB5NQHwQ.js → Services-COhAbS45.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-BPSVUive.js → Skeleton-B97crp6v.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DU2D-IYQ.js → Skills-er_XdavV.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DLX0vcyj.js → Sla-RpkWkzOL.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DDVXmT5y.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-QuDslViv.js → SyncSettings-Bbiw6ohw.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-C-jF6Kwk.js → Tasks-CRghr7lo.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-B6tY1zm-.js → Templates-BUQkfydG.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BXx5FAY1.js → Tenant-CfUFOsZu.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CNk8KI4J.js → Terminal-BGY7gi4M.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-Bo2YVc2l.js → TimelineRenderer-B3Len0l7.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-Bs1Oq8t9.js → Tokens-BjeHn2zI.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CtDhiQxG.js → Trigger-ctcZ2mcv.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-riAZiPHp.js → Trust-oncF3TpO.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DZRzYaNL.js → UkeySign-BwKbVYCd.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DTwOdg2x.js → VideoEditing-D1dgL-3U.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-ByuDECxb.js → Wallet-4hXAfjBL.js} +3 -3
  87. package/src/assets/web-panel/assets/{WebAuthn-CkSKfId0.js → WebAuthn-C8p3cotb.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-BmyJJndu.js → WorkflowEditor-BnrjG77E.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-s1LiqLff.js → chat-B-YNK7EI.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-CMKVIFUx.js → colors-B9HYvK-e.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DQGdG4Y5.js → compact-item-BJr0quBA.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-Csln9jj1.js → createContext-CXfbQEWD.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DNpXMZaQ.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D1RYDhMF.js → hasIn-B7Q02hdZ.js} +1 -1
  95. package/src/assets/web-panel/assets/index-2hcdmWpb.js +1 -0
  96. package/src/assets/web-panel/assets/{index-DmIeFdLq.js → index-4gZEpDAG.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CUVbDS_3.js → index-6wWd08X-.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CiB2ZOQm.js → index-B6h86qjh.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Cew8V1UQ.js → index-BB4KIBso.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DIFiRztW.js → index-BClzZrmx.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Bqhj13Jf.js → index-BNL9lmWL.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D1kWd1be.js → index-BU_adeUp.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-rHRlGEnX.js → index-Bd3nX6aj.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CTg3OrkW.js → index-Bf0OxvOh.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C1y8O1ZP.js → index-BleXwr2w.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CGBHLqy3.js → index-BmCD1_HC.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B39ap5Fq.js → index-BmvpVSmM.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-D6eoC11O.js → index-BoNVnnAq.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CUlRsoXi.js → index-BtunVZuS.js} +1 -1
  110. package/src/assets/web-panel/assets/{index--G_4msyB.js → index-BvRqE3Un.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B0vls3Cv.js → index-C3M8M9_U.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-ugL-2h6d.js → index-C7IZWdoB.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BxVtT9q8.js → index-CEujQqdl.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C8sTim5H.js → index-COdAugDt.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DHic4uvV.js → index-CQ7qYZS4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D3TPgm-r.js → index-CU7cBzr9.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BFHhLE91.js → index-CXgHZ_HG.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DauplA4h.js → index-CcyxVqzn.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D2v6xn0e.js → index-D0zuXz4s.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CHmRaTdN.js → index-D1GP2MQz.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-FLnF1aVs.js → index-D5g5_oua.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CmAjtQdv.js → index-DMoUa3dF.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-8mlgjcck.js → index-DPUg6cVc.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BJsa5jwk.js → index-D_UneDq3.js} +1 -1
  125. package/src/assets/web-panel/assets/index-DbQCUG4D.js +1 -0
  126. package/src/assets/web-panel/assets/{index-BsmEoBNu.js → index-DsV1HA0Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Bl5BNEwF.js → index-DutYFyC0.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BvrkxTu0.js → index-G7Z_On_n.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BiT8auXA.js → index-Gjegl5Ig.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-jPM2-LMz.js → index-HzCaU25O.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DQ86qdvJ.js → index-cq5Q-RHa.js} +19 -19
  132. package/src/assets/web-panel/assets/{index-CJwH7xWo.js → index-iCKFl_jt.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DU23ertt.js → index-kAggAkrB.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DWJU65d6.js → initDefaultProps-DrzGM6fO.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-B0cCdvX2.js → motion-SE_xFnGL.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-CSWGW4YZ.js → move-CrPjKNiG.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-_P9unVrC.js → mtc-parser-CpcyFPK6.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-eUFaswv5.js → omit-BV0g7vf4.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-Hmk4wpqj.js → pickAttrs-lWpsc-k6.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-COIVVq2l.js → placementArrow-CiewvU4X.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CkXZS7fs.js → responsiveObserve-DbF8pamO.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BGdyZ1Ue.js → slide-Bn82Ip9-.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-rbVNSZAN.js → statusUtils-J76gk_-G.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-CcdFI5tK.js → styleChecker-BL95J74a.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-C9yiJQMh.js → useFlexGapSupport-BUzhdQsv.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-B73ulQmg.js → useFs-B2Fppepb.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-DNN-hD_j.js → usePersonalDataHub-DjTASfYE.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BN1KizIr.js → vnode-BwrOgBqP.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-0KE4Sg53.js → zoom-B6MeT_Gw.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/llm.js +4 -3
  152. package/src/data/changelog.json +52 -0
  153. package/src/lib/evolution/evolution-artifact-ports.js +40 -3
  154. package/src/lib/evolution/evolution-file-identity.js +50 -0
  155. package/src/lib/evolution/evolution-file-witness.js +206 -91
  156. package/src/lib/evolution/evolution-ledger-file-backend.js +4 -0
  157. package/src/lib/evolution/evolution-ledger.js +37 -5
  158. package/src/lib/evolution/evolution-witness-segments.js +330 -0
  159. package/src/lib/evolution/progressive-canary-external-watchdog.js +6 -4
  160. package/src/lib/evolution/skill-candidate-registry.js +45 -11
  161. package/src/lib/evolution/skill-release-registry.js +46 -12
  162. package/src/lib/llm-connection-probe.js +21 -4
  163. package/src/lib/remote-read-loop-guard.js +72 -13
  164. package/src/lib/text-file-search-worker.js +147 -0
  165. package/src/lib/text-file-search.js +116 -0
  166. package/src/lib/web-fetch-snapshots.js +211 -0
  167. package/src/lib/web-fetch.js +192 -18
  168. package/src/lib/web-search.js +118 -10
  169. package/src/runtime/agent-core.js +44 -0
  170. package/src/runtime/coding-agent-contract-shared.cjs +98 -4
  171. package/src/assets/web-panel/assets/SpeechSettings-CsPt1rpa.js +0 -1
  172. package/src/assets/web-panel/assets/devWarning-C8YOOF96.js +0 -1
  173. package/src/assets/web-panel/assets/index-BUXHmygu.js +0 -1
  174. package/src/assets/web-panel/assets/index-CsNpDb_W.js +0 -1
@@ -10,8 +10,11 @@ import https from "node:https";
10
10
  import { lookup as dnsLookup } from "node:dns";
11
11
  import { URL } from "node:url";
12
12
  import { createHash } from "node:crypto";
13
+ import { StringDecoder } from "node:string_decoder";
14
+ import { webFetchSnapshots } from "./web-fetch-snapshots.js";
13
15
 
14
- const DEFAULT_MAX_BYTES = 2_000_000;
16
+ const DEFAULT_MAX_BYTES = 10_000_000;
17
+ const DEFAULT_MAX_CHARS = 20_000;
15
18
  const DEFAULT_TIMEOUT_MS = 15_000;
16
19
 
17
20
  // RFC 1918 + loopback + link-local (IPv4 and IPv6). Blocked by default unless
@@ -99,13 +102,23 @@ export function checkAllowed(urlStr, config = {}) {
99
102
  return { allowed: true, url: parsed };
100
103
  }
101
104
 
102
- export function htmlToMarkdown(html) {
103
- if (!html) return "";
105
+ function stripHtmlNoise(html) {
104
106
  let text = String(html);
105
107
  // Strip scripts/styles entirely
106
108
  text = text.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
107
109
  text = text.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
108
110
  text = text.replace(/<!--[\s\S]*?-->/g, "");
111
+ // A bounded download may end inside a script, stylesheet, comment or tag.
112
+ // Closed blocks were removed above; discard any unfinished non-content tail.
113
+ return text
114
+ .replace(/<(?:script|style)\b[^>]*>[\s\S]*$/gi, "")
115
+ .replace(/<!--[\s\S]*$/g, "")
116
+ .replace(/<[^>]*$/g, "");
117
+ }
118
+
119
+ export function htmlToMarkdown(html) {
120
+ if (!html) return "";
121
+ let text = stripHtmlNoise(html);
109
122
  // Convert headings
110
123
  text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, n, inner) => {
111
124
  return `\n\n${"#".repeat(Number(n))} ${inner.trim()}\n\n`;
@@ -141,7 +154,10 @@ function stripTags(html) {
141
154
  return String(html).replace(/<[^>]+>/g, "");
142
155
  }
143
156
 
144
- function webFetchCacheKey(parsed, { format, maxBytes, maxRedirects, headers }) {
157
+ function webFetchCacheKey(
158
+ parsed,
159
+ { format, maxBytes, maxChars, offset, onOverflow, maxRedirects, headers },
160
+ ) {
145
161
  // A caller-provided header can carry credentials or change representation.
146
162
  // Keep those requests out of the shared host cache entirely.
147
163
  if (
@@ -155,6 +171,9 @@ function webFetchCacheKey(parsed, { format, maxBytes, maxRedirects, headers }) {
155
171
  url: parsed.toString(),
156
172
  format,
157
173
  maxBytes,
174
+ maxChars,
175
+ offset,
176
+ onOverflow,
158
177
  maxRedirects,
159
178
  });
160
179
  // Do not retain a raw URL/query as a long-lived Map key in the host cache.
@@ -211,12 +230,15 @@ export function makeSafeLookup(allowPrivateHosts, deps = _deps) {
211
230
 
212
231
  async function _doRequest(
213
232
  parsed,
214
- { maxBytes, timeout, headers, allowPrivateHosts },
233
+ { maxBytes, timeout, headers, allowPrivateHosts, truncateOnOverflow },
215
234
  ) {
216
235
  const lib = parsed.protocol === "https:" ? https : http;
217
236
  return new Promise((resolve, reject) => {
218
237
  let timer;
238
+ let settled = false;
219
239
  const settle = (error, result) => {
240
+ if (settled) return;
241
+ settled = true;
220
242
  clearTimeout(timer);
221
243
  if (error) reject(error);
222
244
  else resolve(result);
@@ -250,13 +272,38 @@ async function _doRequest(
250
272
  }
251
273
  const chunks = [];
252
274
  let size = 0;
275
+ const complete = (downloadTruncated = false) => {
276
+ const buffer = Buffer.concat(chunks);
277
+ settle(null, {
278
+ statusCode: res.statusCode,
279
+ headers: res.headers,
280
+ // With a partial response, discard an incomplete final UTF-8
281
+ // sequence instead of inventing a replacement character.
282
+ body: downloadTruncated
283
+ ? new StringDecoder("utf8").write(buffer)
284
+ : buffer.toString("utf8"),
285
+ bytes: buffer.length,
286
+ downloadTruncated,
287
+ });
288
+ };
253
289
  res.on("data", (chunk) => {
290
+ if (settled) return;
291
+ const available = maxBytes - size;
254
292
  size += chunk.length;
255
293
  if (size > maxBytes) {
294
+ if (truncateOnOverflow) {
295
+ if (available > 0) chunks.push(chunk.subarray(0, available));
296
+ complete(true);
297
+ res.destroy();
298
+ req.destroy();
299
+ return;
300
+ }
256
301
  const error = Object.assign(
257
302
  new Error(`response exceeds maxBytes (${maxBytes})`),
258
303
  {
259
304
  code: "ERR_RESPONSE_TOO_LARGE",
305
+ maxBytes,
306
+ receivedBytes: size,
260
307
  },
261
308
  );
262
309
  settle(error);
@@ -267,11 +314,7 @@ async function _doRequest(
267
314
  chunks.push(chunk);
268
315
  });
269
316
  res.on("end", () => {
270
- settle(null, {
271
- statusCode: res.statusCode,
272
- headers: res.headers,
273
- body: Buffer.concat(chunks).toString("utf8"),
274
- });
317
+ if (!settled) complete();
275
318
  });
276
319
  res.on("error", (error) => settle(error));
277
320
  res.on("aborted", () =>
@@ -303,6 +346,14 @@ export async function webFetch(url, options = {}) {
303
346
  const {
304
347
  format = "markdown",
305
348
  maxBytes = DEFAULT_MAX_BYTES,
349
+ maxChars = DEFAULT_MAX_CHARS,
350
+ offset = 0,
351
+ snapshotId = null,
352
+ query = null,
353
+ maxMatches = 20,
354
+ contextChars = 150,
355
+ caseSensitive = false,
356
+ onOverflow = "truncate",
306
357
  timeout = DEFAULT_TIMEOUT_MS,
307
358
  config = {},
308
359
  headers = {},
@@ -321,29 +372,115 @@ export async function webFetch(url, options = {}) {
321
372
  if (
322
373
  !Number.isSafeInteger(maxBytes) ||
323
374
  maxBytes <= 0 ||
375
+ !Number.isSafeInteger(maxChars) ||
376
+ maxChars <= 0 ||
377
+ !Number.isSafeInteger(offset) ||
378
+ offset < 0 ||
379
+ (snapshotId !== null && (typeof snapshotId !== "string" || !snapshotId)) ||
324
380
  !Number.isSafeInteger(timeout) ||
325
381
  timeout <= 0 ||
326
382
  timeout > 2_147_483_647 ||
327
383
  !Number.isSafeInteger(maxRedirects) ||
328
- maxRedirects < 0
384
+ maxRedirects < 0 ||
385
+ !["truncate", "error"].includes(onOverflow)
329
386
  ) {
330
387
  return {
331
- error: "invalid web_fetch size, timeout or redirect limit",
388
+ error: "invalid web_fetch size, output length, timeout or redirect limit",
332
389
  code: "ERR_FETCH_OPTIONS",
333
390
  retryable: false,
334
391
  };
335
392
  }
336
393
 
394
+ if (query !== null && (!snapshotId || typeof query !== "string" || !query)) {
395
+ return {
396
+ error:
397
+ "Download the URL first, then pass snapshotId and a non-empty query to search its saved text",
398
+ code: "ERR_FETCH_OPTIONS",
399
+ retryable: false,
400
+ };
401
+ }
402
+ if (format === "json" && (snapshotId || offset !== 0)) {
403
+ return {
404
+ error:
405
+ "JSON values cannot be paged. Use format=text to save and read a JSON document in chunks.",
406
+ code: "ERR_FETCH_OPTIONS",
407
+ retryable: false,
408
+ };
409
+ }
410
+ const snapshotIdentity = createHash("sha256")
411
+ .update(
412
+ JSON.stringify({
413
+ url: check.url.toString(),
414
+ format,
415
+ headers,
416
+ allowPrivateHosts: config.allowPrivateHosts === true,
417
+ allowedDomains: config.allowedDomains || ["*"],
418
+ }),
419
+ )
420
+ .digest("hex");
421
+ const readSnapshot = (id) =>
422
+ webFetchSnapshots.read(
423
+ id,
424
+ snapshotIdentity,
425
+ hostResourceBudget,
426
+ offset,
427
+ maxChars,
428
+ );
429
+ if (snapshotId) {
430
+ try {
431
+ const result = readSnapshot(snapshotId);
432
+ if (result.downloadTruncated && onOverflow === "error") {
433
+ return {
434
+ error:
435
+ "The saved download is incomplete. Fetch again without snapshotId and with a larger maxBytes.",
436
+ code: "ERR_RESPONSE_TOO_LARGE",
437
+ retryable: false,
438
+ };
439
+ }
440
+ if (query !== null && !result.error) {
441
+ const search = await webFetchSnapshots.search(
442
+ snapshotId,
443
+ snapshotIdentity,
444
+ hostResourceBudget,
445
+ {
446
+ pattern: query,
447
+ offset,
448
+ maxMatches,
449
+ contextChars,
450
+ caseSensitive,
451
+ url: check.url.toString(),
452
+ },
453
+ );
454
+ return search.error ? search : { ...search, cached: true };
455
+ }
456
+ return result.error ? result : { ...result, cached: true };
457
+ } catch {
458
+ return {
459
+ error:
460
+ "Unable to read the local webpage snapshot. Fetch the URL again without snapshotId.",
461
+ code: "ERR_FETCH_SNAPSHOT_READ",
462
+ retryable: false,
463
+ };
464
+ }
465
+ }
466
+
337
467
  const cacheKey = webFetchCacheKey(check.url, {
338
468
  format,
339
469
  maxBytes,
470
+ maxChars,
471
+ offset,
472
+ onOverflow,
340
473
  maxRedirects,
341
474
  headers,
342
475
  });
343
476
  if (cacheKey && hostResourceBudget?.getWebFetch) {
344
477
  try {
345
478
  const cached = hostResourceBudget.getWebFetch(cacheKey);
346
- if (cached) return { ...cached, cached: true };
479
+ if (cached) {
480
+ if (!cached.snapshotId) return { ...cached, cached: true };
481
+ const page = readSnapshot(cached.snapshotId);
482
+ if (!page.error) return { ...page, cached: true };
483
+ }
347
484
  } catch (error) {
348
485
  return hostBudgetUnavailable("web_fetch", error);
349
486
  }
@@ -380,6 +517,8 @@ export async function webFetch(url, options = {}) {
380
517
  timeout: remaining,
381
518
  headers,
382
519
  allowPrivateHosts: config.allowPrivateHosts,
520
+ // An incomplete JSON document cannot be returned as a valid value.
521
+ truncateOnOverflow: onOverflow === "truncate" && format !== "json",
383
522
  });
384
523
  if (!response.redirect) break;
385
524
  if (++redirects > maxRedirects) {
@@ -436,7 +575,9 @@ export async function webFetch(url, options = {}) {
436
575
  if (format === "markdown") {
437
576
  output = /html/i.test(contentType) ? htmlToMarkdown(body) : body;
438
577
  } else if (format === "text") {
439
- output = /html/i.test(contentType) ? stripTags(body) : body;
578
+ output = /html/i.test(contentType)
579
+ ? stripTags(stripHtmlNoise(body))
580
+ : body;
440
581
  } else if (format === "json") {
441
582
  try {
442
583
  output = JSON.parse(body);
@@ -451,15 +592,37 @@ export async function webFetch(url, options = {}) {
451
592
  }
452
593
  }
453
594
 
454
- const result = {
595
+ // Download limits apply to the raw page, which can contain much more
596
+ // markup than the extracted text. Bound model context AFTER extraction;
597
+ // a short requested answer must not abort an otherwise readable page.
598
+ const totalChars =
599
+ format !== "json" && typeof output === "string" ? output.length : null;
600
+ const downloadTruncated = response.downloadTruncated === true;
601
+ let result = {
455
602
  url: parsed.toString(),
456
603
  statusCode,
457
604
  contentType,
458
605
  format: outputFormat,
459
- bytes: Buffer.byteLength(body, "utf8"),
606
+ bytes: response.bytes,
460
607
  content: output,
608
+ ...(downloadTruncated ? { downloadTruncated: true, maxBytes } : {}),
609
+ ...(downloadTruncated
610
+ ? {
611
+ hint: `The raw response exceeded maxBytes=${maxBytes}. Only the downloaded prefix is included; totalChars measures that prefix after extraction, not the complete page. Increase maxBytes to fetch more, or request a focused page/API endpoint. Use maxChars to control returned text. Do not treat this as the complete page.`,
612
+ }
613
+ : {}),
461
614
  };
462
- if (cacheKey && hostResourceBudget?.putWebFetch) {
615
+ if (totalChars !== null) {
616
+ // Save the extracted document once, then read only the requested range
617
+ // from disk. Subsequent snapshot reads never contact the origin server.
618
+ const id = webFetchSnapshots.save(
619
+ result,
620
+ snapshotIdentity,
621
+ hostResourceBudget,
622
+ );
623
+ result = readSnapshot(id);
624
+ }
625
+ if (!downloadTruncated && cacheKey && hostResourceBudget?.putWebFetch) {
463
626
  try {
464
627
  hostResourceBudget.putWebFetch(cacheKey, result);
465
628
  } catch {
@@ -472,6 +635,15 @@ export async function webFetch(url, options = {}) {
472
635
  return {
473
636
  error: `web_fetch failed: ${error.message}`,
474
637
  code,
638
+ ...(code === "ERR_RESPONSE_TOO_LARGE"
639
+ ? {
640
+ maxBytes,
641
+ receivedBytes: error.receivedBytes,
642
+ ...(maxBytes < DEFAULT_MAX_BYTES
643
+ ? { suggestedMaxBytes: DEFAULT_MAX_BYTES }
644
+ : {}),
645
+ }
646
+ : {}),
475
647
  retryable: [
476
648
  "ETIMEDOUT",
477
649
  "ECONNRESET",
@@ -480,7 +652,9 @@ export async function webFetch(url, options = {}) {
480
652
  ].includes(code),
481
653
  hint:
482
654
  code === "ERR_RESPONSE_TOO_LARGE"
483
- ? "Request a smaller resource or a focused log excerpt. Do not repeatedly download the same full log."
655
+ ? maxBytes < DEFAULT_MAX_BYTES
656
+ ? `The raw response exceeded maxBytes=${maxBytes}, before text extraction. To limit returned text, use maxChars and omit maxBytes (default ${DEFAULT_MAX_BYTES}). Retry once with corrected limits, not the same small download limit.`
657
+ : "The raw response exceeded the download limit. Request a smaller resource or focused log excerpt, or explicitly raise maxBytes within the permitted resource budget. maxChars only limits the returned text."
484
658
  : "Use the error code to check connectivity, DNS or access. Retry a transient failure at most once, then change approach or report the blocker.",
485
659
  };
486
660
  } finally {
@@ -15,7 +15,7 @@ import http from "http";
15
15
  import https from "https";
16
16
  import { URL } from "url";
17
17
 
18
- const DEFAULT_MAX_BYTES = 2_000_000;
18
+ const DEFAULT_MAX_BYTES = 10_000_000;
19
19
  const DEFAULT_TIMEOUT_MS = 15_000;
20
20
  const DEFAULT_MAX_RESULTS = 8;
21
21
  export const MAX_WEB_SEARCH_RESULTS = 20;
@@ -98,6 +98,15 @@ function _request(
98
98
  const parsed = new URL(urlStr);
99
99
  const lib = parsed.protocol === "https:" ? _deps.https : _deps.http;
100
100
  return new Promise((resolve, reject) => {
101
+ let timer;
102
+ let settled = false;
103
+ const settle = (error, result) => {
104
+ if (settled) return;
105
+ settled = true;
106
+ clearTimeout(timer);
107
+ if (error) reject(error);
108
+ else resolve(result);
109
+ };
101
110
  const req = lib.request(
102
111
  {
103
112
  method,
@@ -113,28 +122,66 @@ function _request(
113
122
  timeout,
114
123
  },
115
124
  (res) => {
125
+ if (res.statusCode >= 400) {
126
+ settle(
127
+ Object.assign(new Error(`HTTP ${res.statusCode}`), {
128
+ code: "ERR_HTTP_STATUS",
129
+ statusCode: res.statusCode,
130
+ retryAfter: res.headers?.["retry-after"],
131
+ }),
132
+ );
133
+ res.destroy?.();
134
+ req.destroy();
135
+ return;
136
+ }
116
137
  const chunks = [];
117
138
  let size = 0;
118
139
  res.on("data", (chunk) => {
140
+ if (settled) return;
119
141
  size += chunk.length;
120
142
  if (size > maxBytes) {
121
- req.destroy(new Error(`response exceeds maxBytes (${maxBytes})`));
143
+ settle(
144
+ Object.assign(
145
+ new Error(`response exceeds maxBytes (${maxBytes})`),
146
+ {
147
+ code: "ERR_RESPONSE_TOO_LARGE",
148
+ },
149
+ ),
150
+ );
151
+ res.destroy?.();
152
+ req.destroy();
122
153
  return;
123
154
  }
124
155
  chunks.push(chunk);
125
156
  });
126
157
  res.on("end", () =>
127
- resolve({
158
+ settle(null, {
128
159
  statusCode: res.statusCode,
129
160
  headers: res.headers,
130
161
  body: Buffer.concat(chunks).toString("utf8"),
131
162
  }),
132
163
  );
133
- res.on("error", reject);
164
+ res.on("error", (error) => settle(error));
165
+ res.on("aborted", () =>
166
+ settle(
167
+ Object.assign(new Error("response aborted"), {
168
+ code: "ECONNRESET",
169
+ }),
170
+ ),
171
+ );
134
172
  },
135
173
  );
136
- req.on("error", reject);
137
- req.on("timeout", () => req.destroy(new Error("request timeout")));
174
+ const abortTimeout = () => {
175
+ settle(
176
+ Object.assign(new Error("request timeout"), { code: "ETIMEDOUT" }),
177
+ );
178
+ req.destroy();
179
+ };
180
+ req.on("error", (error) => settle(error));
181
+ req.on("timeout", abortTimeout);
182
+ // An overall deadline also bounds DNS and continuously streaming results.
183
+ timer = setTimeout(abortTimeout, timeout);
184
+ timer.unref?.();
138
185
  if (body != null) req.write(body);
139
186
  req.end();
140
187
  });
@@ -285,6 +332,19 @@ async function _searchDuckDuckGo(query, { maxResults, timeout, maxBytes }) {
285
332
  return { error: `duckduckgo HTTP ${res.statusCode}` };
286
333
  }
287
334
  const html = res.body;
335
+ if (
336
+ res.statusCode === 202 ||
337
+ (res.statusCode >= 300 && res.statusCode < 400) ||
338
+ /anomaly-modal|challenge-form|anomaly\.js|captcha/i.test(html)
339
+ ) {
340
+ return {
341
+ error:
342
+ "duckduckgo: verification challenge; search results are unavailable",
343
+ code: "ERR_SEARCH_CHALLENGE",
344
+ retryable: false,
345
+ hint: "Do not report this as no results or repeat the same request. Configure a keyed search provider (Tavily, Brave, Bocha or Qianfan), or use your configured SearXNG instance.",
346
+ };
347
+ }
288
348
  const results = [];
289
349
  const anchorRe =
290
350
  /<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
@@ -453,8 +513,26 @@ export async function webSearch(query, options = {}) {
453
513
  const config = options.config || {};
454
514
  const provider = resolveProvider(options, config);
455
515
  let maxResults = normalizeMaxResults(options.maxResults ?? config.maxResults);
456
- const timeout = Number(options.timeout) || DEFAULT_TIMEOUT_MS;
457
- const maxBytes = Number(options.maxBytes) || DEFAULT_MAX_BYTES;
516
+ const timeout = options.timeout ?? config.timeout ?? DEFAULT_TIMEOUT_MS;
517
+ const maxBytes = options.maxBytes ?? config.maxBytes ?? DEFAULT_MAX_BYTES;
518
+ const maxSnippetChars =
519
+ options.maxSnippetChars ?? config.maxSnippetChars ?? 2000;
520
+ if (
521
+ !Number.isSafeInteger(timeout) ||
522
+ timeout <= 0 ||
523
+ timeout > 2_147_483_647 ||
524
+ !Number.isSafeInteger(maxBytes) ||
525
+ maxBytes <= 0 ||
526
+ !Number.isSafeInteger(maxSnippetChars) ||
527
+ maxSnippetChars <= 0 ||
528
+ maxSnippetChars > 10000
529
+ ) {
530
+ return {
531
+ error: "web_search: invalid download, snippet or timeout limit",
532
+ code: "ERR_SEARCH_OPTIONS",
533
+ retryable: false,
534
+ };
535
+ }
458
536
 
459
537
  if (!SUPPORTED_PROVIDERS.includes(provider)) {
460
538
  return {
@@ -533,17 +611,47 @@ export async function webSearch(query, options = {}) {
533
611
  return {
534
612
  error: `web_search (${provider}) failed: ${err.message}`,
535
613
  provider,
614
+ code: err.code || "ERR_WEB_SEARCH",
615
+ ...(err.statusCode ? { statusCode: err.statusCode } : {}),
616
+ ...(err.retryAfter ? { retryAfter: String(err.retryAfter) } : {}),
617
+ retryable:
618
+ err.statusCode === 429 ||
619
+ err.statusCode >= 500 ||
620
+ ["ETIMEDOUT", "ECONNRESET", "EAI_AGAIN"].includes(err.code),
621
+ hint:
622
+ err.code === "ERR_RESPONSE_TOO_LARGE"
623
+ ? "Search response exceeds maxBytes. Raise maxBytes for a larger download; use maxResults and maxSnippetChars to limit returned results, not maxBytes."
624
+ : err.statusCode === 429
625
+ ? "Honor Retry-After before a bounded retry. Do not repeat the same search immediately."
626
+ : "Check the search provider's configuration, API key and connectivity. Do not treat a provider failure as no results or invent source URLs.",
536
627
  };
537
628
  }
538
629
 
539
630
  if (out && out.error) return { ...out, provider };
540
- const results = (out && out.results) || [];
631
+ let truncated = false;
632
+ const bound = (value, limit) => {
633
+ const text = String(value || "");
634
+ if (text.length <= limit) return text;
635
+ truncated = true;
636
+ return text.slice(
637
+ 0,
638
+ /[\uD800-\uDBFF]/.test(text[limit - 1]) ? limit - 1 : limit,
639
+ );
640
+ };
641
+ const results = ((out && out.results) || []).map((result) => ({
642
+ ...result,
643
+ title: bound(result.title, 512),
644
+ snippet: bound(result.snippet, maxSnippetChars),
645
+ }));
646
+ const answer = bound(out?.answer, 8000);
541
647
  return {
542
648
  query: q,
543
649
  provider,
544
650
  count: results.length,
545
651
  results,
546
- answer: (out && out.answer) || "",
652
+ answer,
653
+ ...(truncated ? { truncated: true, maxSnippetChars } : {}),
654
+ hint: "These are search results, not full webpages. Cite the returned source URLs. Use web_fetch only when a page needs closer reading; download it once, then continue with snapshotId and nextOffset as offset.",
547
655
  };
548
656
  } finally {
549
657
  toolLease?.release();
@@ -1097,6 +1097,8 @@ Key behaviors:
1097
1097
  - For long-running commands (builds, full test suites, dev servers) set run_shell { run_in_background: true } to get a task_id back immediately, then poll output and completion with check_shell { task_id }. Kill a backgrounded server with check_shell { task_id, kill: true } when finished
1098
1098
  - When asked about git status, diff, log, or other repository operations, use the git tool instead of run_shell
1099
1099
  - When asked about files or code, use search_files to locate relevant sections, then read_file with offset/limit. Follow nextRead for large files. Reuse unchanged content already in context instead of repeatedly reading the same page; re-read when the file changes or the earlier content is no longer available.
1100
+ - For long text files, use search_files with path and pattern to find matching lines/columns and nearby text; read the returned nextRead location instead of paging from the top.
1101
+ - For online discovery without a known URL, use web_search with keywords. Return relevant source links and snippets; use web_fetch only when the source needs closer reading. For a known URL, fetch it directly. Download large pages once, then use snapshotId to search or read local chunks. Never invent URLs or claim a blocked search returned no results.
1100
1102
  - Large task documents include a sampled outline with line numbers. Use it to locate relevant unfinished work and inspect those sections instead of scanning the entire document unless the task requires full coverage. File coverage and excerpts survive compaction; reading to EOF is not completing the user's task.
1101
1103
  - For task counts or completion status, search the named document for the latest status/summary table first, then read that section. Distinguish historical entries from current status and repository work from external acceptance. Once the relevant entries are verified, answer; do not read every implementation detail just to count tasks.
1102
1104
  - Before renaming or changing a symbol, use code_intelligence (action: references/definition) to find every real usage instead of guessing with text search. It degrades to "unavailable" when no language server is installed — fall back to search_files then.
@@ -1704,6 +1706,8 @@ function agentFileToolPathRequests(name, args = {}, workspaceRoots = []) {
1704
1706
  },
1705
1707
  ];
1706
1708
  case "search_files":
1709
+ if (typeof args.path === "string" && args.path.length > 0)
1710
+ return one("path", "read");
1707
1711
  return typeof args.directory === "string" && args.directory.length > 0
1708
1712
  ? [
1709
1713
  {
@@ -7076,6 +7080,14 @@ async function executeToolInner(
7076
7080
  const result = await webFetch(args.url, {
7077
7081
  format: args.format,
7078
7082
  maxBytes: args.maxBytes,
7083
+ maxChars: args.maxChars,
7084
+ onOverflow: args.onOverflow,
7085
+ snapshotId: args.snapshotId,
7086
+ query: args.query,
7087
+ maxMatches: args.maxMatches,
7088
+ contextChars: args.contextChars,
7089
+ caseSensitive: args.caseSensitive,
7090
+ offset: args.offset,
7079
7091
  timeout: args.timeout,
7080
7092
  config: webFetchConfig,
7081
7093
  hostResourceBudget,
@@ -7104,6 +7116,8 @@ async function executeToolInner(
7104
7116
  const result = await webSearch(args.query, {
7105
7117
  provider: args.provider,
7106
7118
  maxResults: args.maxResults,
7119
+ maxBytes: args.maxBytes,
7120
+ maxSnippetChars: args.maxSnippetChars,
7107
7121
  timeout: args.timeout,
7108
7122
  config: webSearchConfig,
7109
7123
  hostResourceBudget,
@@ -7660,6 +7674,36 @@ async function executeToolInner(
7660
7674
  }
7661
7675
 
7662
7676
  case "search_files": {
7677
+ if (typeof args.path === "string" && args.path.length > 0) {
7678
+ const file = path.resolve(cwd, args.path);
7679
+ const { credentialFileReason } =
7680
+ await import("../lib/credential-guard.js");
7681
+ const reason =
7682
+ credentialFileReason(file) ||
7683
+ (fs.existsSync(file)
7684
+ ? credentialFileReason(fs.realpathSync(file))
7685
+ : null);
7686
+ if (reason)
7687
+ return attachDescriptor({
7688
+ error:
7689
+ "Credential file content is protected; use read_file with confirmation",
7690
+ code: "ERR_CREDENTIAL_FILE",
7691
+ });
7692
+ const { searchTextFile } = await import("../lib/text-file-search.js");
7693
+ const result = await searchTextFile(file, { ...args, hostResourceBudget });
7694
+ return attachDescriptor({
7695
+ ...result,
7696
+ path: file,
7697
+ ...(result.matches
7698
+ ? {
7699
+ matches: result.matches.map((match) => ({
7700
+ ...match,
7701
+ nextRead: { path: args.path, ...match.nextRead },
7702
+ })),
7703
+ }
7704
+ : {}),
7705
+ });
7706
+ }
7663
7707
  // An explicit directory scopes the search to one root; otherwise span
7664
7708
  // cwd plus any --add-dir roots so cross-package searches find matches.
7665
7709
  const extraRoots = Array.isArray(additionalDirectories)