chainlesschain 0.166.32 → 0.166.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DKUOkYex.js → AIOps-D8PJmSrh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-B3luFrPA.js → ActionButton-DG6DqHA3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-jbZ8R2S3.js → Analytics-DiLleeWs.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CFS2vzPS.js → AppLayout-DxYkhlHK.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-sS0Qpy5q.js → Artifacts-BAW3Kdsi.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CZ7kfjCG.js → Audit-DBr4uY6E.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BpWc5SgP.js → BackgroundAgents-Dw0r-bHX.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DpEYnvoy.js → Backup-AyhwYsio.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-kFzUGLnl.js → BaseInput-Cj8qJotr.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BP_8aAfJ.js → Chat-DcvimQ0u.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer--joJ1e2f.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-HR1HQ06d.js → Checkbox-C2mlU77l.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DxAqDPjT.js → Codegen-CAfc3YaV.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BaCLih7J.js → Col-CIxoxtJI.js} +1 -1
- package/src/assets/web-panel/assets/{Community-ZX64uW8D.js → Community-DvufZoRg.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CLtMoXTt.js → Compact-55JTa99J.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BlyxBDrs.js → Compliance-BllWog6P.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CtQZIYPh.js → Cowork-ThsFGc5p.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BebN-ajI.js → Cron-CZjl5Ig2.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-3gRU0-58.js → Crosschain-DIAKAoQT.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DSVTtLnb.js → DID-ayygwEil.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CvnhOWqc.js → Dashboard-C3Xjm2UO.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BhdofmCJ.js → Dropdown-C1S3zf-j.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Dhpua9Lo.js → EmailListRenderer-DRvriU-H.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DA1mLqQ4.js → FamilyGuardDashboard-iSX2bmsD.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D6SUyKGb.js → Federation-Dk0Stkv-.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BcMiwguB.js → FormItemContext-DasXSGff.js} +1 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-Dv9XGlxI.js +1 -0
- package/src/assets/web-panel/assets/{Git-DGCNNqR_.js → Git-B5PgXL3Z.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D4HVyFdL.js → Governance-B78x11e_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BOoEmvKl.js → Inference-b7CX1RqW.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ECLI0C0_.js → KnowledgeGraph--MgBzMVp.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Cf9GjHtX.js → Logs-CYCiGmVc.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-C3J59f8v.js → MarkdownRenderer-yrl10SWw.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DeL4rOAJ.js → Marketplace-CsbwQ0eM.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Dvn3KpLW.js → McpTools-0GBWHVra.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CSlLy-aw.js → Memory-fAywmAYr.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yNIe8-74.js → MobileBridge-BeNEyy3s.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Zt1cpErd.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-Q7iMzSds.js → Mtc-CqRhJB70.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CW71IQrK.js → MtcAudit-BEyGZflc.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-pursAeeT.js → Multisig-luz3EBT2.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DtNo1qqO.js → NLProgramming-Dkbvp8IX.js} +1 -1
- package/src/assets/web-panel/assets/Notes-Cf9I2K8j.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-BgmQs_J8.js → NotificationSettings-BTfuzynL.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DBmCtl2H.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BbrrT6gR.js → Organization-BbG3-vDy.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BcPi4YUc.js → Overflow-CMtFKEIQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DfRteW76.js → P2P-6SAEb_lU.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-CWG2OLvh.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DLCRaOe3.js → Permissions-ClZxZKZl.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-fuTL6z8u.js → PersonalDataHub-BYkyoZDT.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-D3ztsQDw.js → Pipeline-B28ZgADI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-4f-SQ4ie.js → Privacy-BxtZywbB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CC_ZFjrV.js → ProjectInit-DPqoRnxK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C9FWHcne.js → ProjectSettings-QjKvMkau.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CR3ETMQQ.js → Projects-Cp0Z8ZG0.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BWcbdgXx.js → Providers-CrS-rbyb.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BavxpdIK.js → QrScannerModal-xTrhbVAU.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CZ7gupY4.js → QuickAsk-DFOqJoq-.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BQNpBYLL.js → Recommend-DXlKqu2-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-2LU0Vr44.js → RemoteSession-BLLaZH8K.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-oYWo7qyp.js → Reputation-CUgAdJ11.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B8Yj-Csx.js → Row-DzOO3wP-.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CbxKbHJr.js → RssFeed-B6GrRvh8.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DGe-u9s0.js → Search-Cj3zKLb3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BwjfMAFI.js → Security-LnktzidI.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BB5NQHwQ.js → Services-BQ6sIc2K.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BPSVUive.js → Skeleton-CCSV3Jbw.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DU2D-IYQ.js → Skills-CpoQ4SNk.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DLX0vcyj.js → Sla-DbAbgRPq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CsPt1rpa.js → SpeechSettings-C4cJ_hoD.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-QuDslViv.js → SyncSettings-BKFzjN9Z.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-Cf4bl7NS.js +1 -0
- package/src/assets/web-panel/assets/{Templates-B6tY1zm-.js → Templates-DTEQfFAQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BXx5FAY1.js → Tenant-wToJWKXB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CNk8KI4J.js → Terminal-1s3c7kmF.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Bo2YVc2l.js → TimelineRenderer-BcfsYW3u.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bs1Oq8t9.js → Tokens-Byez6FY0.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CtDhiQxG.js → Trigger-Cq8mZO88.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-riAZiPHp.js → Trust-DUrMmEwR.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DZRzYaNL.js → UkeySign-CVHwXrVU.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DTwOdg2x.js → VideoEditing-CDKbTRUS.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ByuDECxb.js → Wallet-BqnEBIOy.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CkSKfId0.js → WebAuthn-CeTMpj7_.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BmyJJndu.js → WorkflowEditor-F6OgjV3C.js} +1 -1
- package/src/assets/web-panel/assets/{chat-s1LiqLff.js → chat-CMHUCwq3.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CMKVIFUx.js → colors-DzVM_fpJ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DQGdG4Y5.js → compact-item-Hhmg51yT.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Csln9jj1.js → createContext-SwHt7x3Q.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-5q_Y0E88.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D1RYDhMF.js → hasIn-HGNKkn9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiB2ZOQm.js → index--iOqM_AI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ86qdvJ.js → index-AQcDW8bB.js} +19 -19
- package/src/assets/web-panel/assets/{index-DHic4uvV.js → index-B6YQN5_R.js} +1 -1
- package/src/assets/web-panel/assets/{index-FLnF1aVs.js → index-BDE-WiE5.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3TPgm-r.js → index-BDhCx17_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvrkxTu0.js → index-BDt4wSl6.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6eoC11O.js → index-BJqNTF1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqhj13Jf.js → index-BRikmONJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl5BNEwF.js → index-B_HgLPC5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGBHLqy3.js → index-Ba2Kszdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1y8O1ZP.js → index-BlHB5eax.js} +1 -1
- package/src/assets/web-panel/assets/{index-rHRlGEnX.js → index-BmPYmoY3.js} +1 -1
- package/src/assets/web-panel/assets/{index-B39ap5Fq.js → index-Bq9MMKno.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTg3OrkW.js → index-BstLYy9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1kWd1be.js → index-C-YkiVyL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHmRaTdN.js → index-C1QJxiOL.js} +1 -1
- package/src/assets/web-panel/assets/{index-8mlgjcck.js → index-CDtzUt75.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFHhLE91.js → index-CMG_2fK2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIFiRztW.js → index-COOcGOyK.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUVbDS_3.js → index-CWkWh48N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsmEoBNu.js → index-Cc-d6f0F.js} +1 -1
- package/src/assets/web-panel/assets/{index-DU23ertt.js → index-ChLHLBkF.js} +1 -1
- package/src/assets/web-panel/assets/{index-jPM2-LMz.js → index-Cnm4zroJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0vls3Cv.js → index-Co4RcBua.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAjtQdv.js → index-D5dcWBqg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmIeFdLq.js → index-DQT5thlV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJsa5jwk.js → index-Dcg7nQDe.js} +1 -1
- package/src/assets/web-panel/assets/{index-ugL-2h6d.js → index-DgoefbLs.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2v6xn0e.js → index-DyZUrnje.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiT8auXA.js → index-E-bI8HpV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxVtT9q8.js → index-F7Grz3-G.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUlRsoXi.js → index-MDdTkUpA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJwH7xWo.js → index-aKV4Py_2.js} +1 -1
- package/src/assets/web-panel/assets/index-dl9TJMh5.js +1 -0
- package/src/assets/web-panel/assets/{index--G_4msyB.js → index-jrRtmiUh.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8sTim5H.js → index-oz0Xxs8D.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cew8V1UQ.js → index-pwLuJGFh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DauplA4h.js → index-sYkFp9A4.js} +1 -1
- package/src/assets/web-panel/assets/index-x0DDgUaT.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-DWJU65d6.js → initDefaultProps-C_2QM45F.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B0cCdvX2.js → motion-DkxY04tR.js} +1 -1
- package/src/assets/web-panel/assets/{move-CSWGW4YZ.js → move-D0CJlA7D.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-_P9unVrC.js → mtc-parser-DjP38YUD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-eUFaswv5.js → omit-C9CJDaGT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Hmk4wpqj.js → pickAttrs-D5Wq2fa8.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-COIVVq2l.js → placementArrow-DJ6Dixs7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CkXZS7fs.js → responsiveObserve-DMmM0Dee.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BGdyZ1Ue.js → slide-DipmpYA-.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-rbVNSZAN.js → statusUtils-DSqw6_nU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CcdFI5tK.js → styleChecker-t828MYjI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C9yiJQMh.js → useFlexGapSupport-COsSNzDQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B73ulQmg.js → useFs-Bq5IEzv_.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DNN-hD_j.js → usePersonalDataHub-CUKM0HdW.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BN1KizIr.js → vnode-l-JwAt2M.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-0KE4Sg53.js → zoom-D7xkW-gD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +26 -0
- package/src/lib/remote-read-loop-guard.js +72 -13
- package/src/lib/text-file-search-worker.js +147 -0
- package/src/lib/text-file-search.js +116 -0
- package/src/lib/web-fetch-snapshots.js +211 -0
- package/src/lib/web-fetch.js +192 -18
- package/src/lib/web-search.js +118 -10
- package/src/runtime/agent-core.js +44 -0
- package/src/runtime/coding-agent-contract-shared.cjs +98 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-yFeZ15D4.js +0 -1
- package/src/assets/web-panel/assets/GenericCardRenderer-CGEB1WcH.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CsS8Lozw.js +0 -1
- package/src/assets/web-panel/assets/Notes-B1xUFwUT.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-DI9t8YZl.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-De81lu5Q.js +0 -7
- package/src/assets/web-panel/assets/Tasks-C-jF6Kwk.js +0 -1
- package/src/assets/web-panel/assets/devWarning-C8YOOF96.js +0 -1
- package/src/assets/web-panel/assets/index-BUXHmygu.js +0 -1
- package/src/assets/web-panel/assets/index-CsNpDb_W.js +0 -1
package/src/lib/web-fetch.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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
|
-
?
|
|
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 {
|
package/src/lib/web-search.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
137
|
-
|
|
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 =
|
|
457
|
-
const maxBytes =
|
|
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
|
-
|
|
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
|
|
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)
|