chainlesschain 0.166.30 → 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 +27 -2
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-D0L1OM_i.js → AIOps-D8PJmSrh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CeZUkX_V.js → ActionButton-DG6DqHA3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-P_7CIOew.js → Analytics-DiLleeWs.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-NuqDYrlO.js → AppLayout-DxYkhlHK.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D1BTQqJM.js → Artifacts-BAW3Kdsi.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-D9XL6DVf.js → Audit-DBr4uY6E.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Ci2DB2gV.js → BackgroundAgents-Dw0r-bHX.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CreK3Yqn.js → Backup-AyhwYsio.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BkIRKFXX.js → BaseInput-Cj8qJotr.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BoJjlCDT.js → Chat-DcvimQ0u.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer--joJ1e2f.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BbA932P3.js → Checkbox-C2mlU77l.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BS_f3kQy.js → Codegen-CAfc3YaV.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BJ1tzpRh.js → Col-CIxoxtJI.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DYsZZlgI.js → Community-DvufZoRg.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Cq41A43W.js → Compact-55JTa99J.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CBqSz1op.js → Compliance-BllWog6P.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-ZAn8buD_.js → Cowork-ThsFGc5p.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DM4WTZQT.js → Cron-CZjl5Ig2.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-S65sJZwe.js → Crosschain-DIAKAoQT.js} +1 -1
- package/src/assets/web-panel/assets/{DID-0Av7FTMv.js → DID-ayygwEil.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D-1vFtiV.js → Dashboard-C3Xjm2UO.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BZufOCir.js → Dropdown-C1S3zf-j.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DJE8szQZ.js → EmailListRenderer-DRvriU-H.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqqPHaZn.js → FamilyGuardDashboard-iSX2bmsD.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CslSyj60.js → Federation-Dk0Stkv-.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-uTfAl63o.js → FormItemContext-DasXSGff.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BIP2obER.js → GenericCardRenderer-Dv9XGlxI.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CoVpUKSg.js → Git-B5PgXL3Z.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-zW6mlFcK.js → Governance-B78x11e_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-zfu2tMaa.js → Inference-b7CX1RqW.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Dd38pMCe.js → KnowledgeGraph--MgBzMVp.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BhX28L73.js → Logs-CYCiGmVc.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-yrl10SWw.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DGBKtVL6.js → Marketplace-CsbwQ0eM.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C0jSXrYN.js → McpTools-0GBWHVra.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-_xyOJV55.js → Memory-fAywmAYr.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGm6cS5Y.js → MobileBridge-BeNEyy3s.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Zt1cpErd.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CSfoeF9f.js → Mtc-CqRhJB70.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-DuXFbCYL.js → MtcAudit-BEyGZflc.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CdnOBFhs.js → Multisig-luz3EBT2.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C0URZ4u4.js → NLProgramming-Dkbvp8IX.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CJznx6ft.js → Notes-Cf9I2K8j.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DJcqYoZP.js → NotificationSettings-BTfuzynL.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-DBmCtl2H.js +1 -0
- package/src/assets/web-panel/assets/{Organization-ucvP5Gzp.js → Organization-BbG3-vDy.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Be3DoWvY.js → Overflow-CMtFKEIQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BXV17CWM.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-Bn0mjFJA.js → Permissions-ClZxZKZl.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CQ2uLeuD.js → PersonalDataHub-BYkyoZDT.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DWv7KJKL.js → Pipeline-B28ZgADI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B4I0G5_J.js → Privacy-BxtZywbB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CDGy6B7j.js → ProjectInit-DPqoRnxK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CXUq9nGH.js → ProjectSettings-QjKvMkau.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-B-IA5qsB.js → Projects-Cp0Z8ZG0.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Card8pXM.js → Providers-CrS-rbyb.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DrU0hFRp.js → QrScannerModal-xTrhbVAU.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-cB-AFOxA.js → QuickAsk-DFOqJoq-.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CvAvFr0u.js → Recommend-DXlKqu2-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BIrtatCm.js → RemoteSession-BLLaZH8K.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CrRpi112.js → Reputation-CUgAdJ11.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DmaenKNU.js → Row-DzOO3wP-.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CGZBcNLM.js → RssFeed-B6GrRvh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Bvxjc4AU.js → Search-Cj3zKLb3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-1Z-5c2n6.js → Security-LnktzidI.js} +3 -3
- package/src/assets/web-panel/assets/{Services-3zYh5_28.js → Services-BQ6sIc2K.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DEyGi_mq.js → Skeleton-CCSV3Jbw.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BjWR0303.js → Skills-CpoQ4SNk.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C6rE_C6V.js → Sla-DbAbgRPq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DJ1DtDuf.js → SpeechSettings-C4cJ_hoD.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B2j0LEry.js → SyncSettings-BKFzjN9Z.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-Cf4bl7NS.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Eb0UjdGe.js → Templates-DTEQfFAQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BlkC3jZ8.js → Tenant-wToJWKXB.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-MMaa8P6V.js → Terminal-1s3c7kmF.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-B4EsSPro.js → TimelineRenderer-BcfsYW3u.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Chk0rule.js → Tokens-Byez6FY0.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Cww9ZKjK.js → Trigger-Cq8mZO88.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-xSwx_Zhk.js → Trust-DUrMmEwR.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CNaC0gRj.js → UkeySign-CVHwXrVU.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-gpoRF70F.js → VideoEditing-CDKbTRUS.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-eSjvqmUV.js → Wallet-BqnEBIOy.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-C4T03Dyz.js → WebAuthn-CeTMpj7_.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-eR0JIHbW.js → WorkflowEditor-F6OgjV3C.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CPRK3APB.js → chat-CMHUCwq3.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BP4ZS2SA.js → colors-DzVM_fpJ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C2bvxEX0.js → compact-item-Hhmg51yT.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DfgFYASc.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-BlwCGOVd.js → hasIn-HGNKkn9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bg_1EOhP.js → index--iOqM_AI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDavuQ3n.js → index-AQcDW8bB.js} +3 -3
- package/src/assets/web-panel/assets/{index-CBJrpDfv.js → index-B6YQN5_R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPezy3IJ.js → index-BDE-WiE5.js} +1 -1
- package/src/assets/web-panel/assets/{index-JhYgZWbg.js → index-BDhCx17_.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2n5VKtr.js → index-BDt4wSl6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BENgpvOm.js → index-BJqNTF1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKZNLHhD.js → index-BRikmONJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3qzM-P9.js → index-B_HgLPC5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLTzHq3a.js → index-Ba2Kszdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DEohnDSJ.js → index-BlHB5eax.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ-CB1zc.js → index-BmPYmoY3.js} +1 -1
- package/src/assets/web-panel/assets/{index-5b_mMYB_.js → index-Bq9MMKno.js} +1 -1
- package/src/assets/web-panel/assets/{index-IYshwMo1.js → index-BstLYy9q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzHzRE9E.js → index-C-YkiVyL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZHYspFu.js → index-C1QJxiOL.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_LuOKcc.js → index-CDtzUt75.js} +1 -1
- package/src/assets/web-panel/assets/{index-9VWjFlVU.js → index-CMG_2fK2.js} +1 -1
- package/src/assets/web-panel/assets/{index-QE8jhGyd.js → index-COOcGOyK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6Zbp8DY.js → index-CWkWh48N.js} +1 -1
- package/src/assets/web-panel/assets/{index-_5mGFZRu.js → index-Cc-d6f0F.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO1fPYye.js → index-ChLHLBkF.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-ubYfKF.js → index-Cnm4zroJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgUnGGL8.js → index-Co4RcBua.js} +1 -1
- package/src/assets/web-panel/assets/{index-AbVd9bHP.js → index-D5dcWBqg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZbQQtMn.js → index-DQT5thlV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DR2Tg8uN.js → index-Dcg7nQDe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdaniU1x.js → index-DgoefbLs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyMwW_p5.js → index-DyZUrnje.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dx3HnzcX.js → index-E-bI8HpV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2D-mqRz.js → index-F7Grz3-G.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDGliY6u.js → index-MDdTkUpA.js} +1 -1
- package/src/assets/web-panel/assets/{index-XBrnfoX6.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-B0HiXNYh.js → index-jrRtmiUh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnD3N8YJ.js → index-oz0Xxs8D.js} +1 -1
- package/src/assets/web-panel/assets/{index-Sk-fx_32.js → index-pwLuJGFh.js} +1 -1
- package/src/assets/web-panel/assets/{index-WSC6WoDZ.js → index-sYkFp9A4.js} +1 -1
- package/src/assets/web-panel/assets/index-x0DDgUaT.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-CmR0B8Wx.js → initDefaultProps-C_2QM45F.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BC0UJWJb.js → motion-DkxY04tR.js} +1 -1
- package/src/assets/web-panel/assets/{move-cvEJQRZ_.js → move-D0CJlA7D.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-0ZPAo5px.js → mtc-parser-DjP38YUD.js} +1 -1
- package/src/assets/web-panel/assets/{omit-UBXYakkw.js → omit-C9CJDaGT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CRZgdSvi.js → pickAttrs-D5Wq2fa8.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B_Y8sKr_.js → placementArrow-DJ6Dixs7.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CFgbiMsJ.js → responsiveObserve-DMmM0Dee.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CF6LYcYS.js → slide-DipmpYA-.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cj2PusGV.js → statusUtils-DSqw6_nU.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Czg7dDLr.js → styleChecker-t828MYjI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Bhi9P67E.js → useFlexGapSupport-COsSNzDQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-X9lqJXBc.js → useFs-Bq5IEzv_.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iL34aL71.js → usePersonalDataHub-CUKM0HdW.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DJ7TA9fc.js → vnode-l-JwAt2M.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DfQDsJXd.js → zoom-D7xkW-gD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +52 -0
- package/src/lib/evolution/evolution-file-witness.js +59 -13
- package/src/lib/evolution/evolution-ledger-file-backend.js +16 -1
- package/src/lib/remote-read-loop-guard.js +290 -0
- package/src/lib/task-progress-tracker.js +5 -1
- 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 +317 -26
- package/src/lib/web-search.js +118 -10
- package/src/runtime/agent-core.js +85 -1
- package/src/runtime/coding-agent-contract-shared.cjs +98 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D68YIuni.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-CbOmzhyX.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-DqNyu4pO.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CEQJHoU9.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-CZDiOia9.js +0 -7
- package/src/assets/web-panel/assets/Tasks-ClYwVLRE.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CahG3JuS.js +0 -1
- package/src/assets/web-panel/assets/index-BZ3kGfit.js +0 -1
- package/src/assets/web-panel/assets/index-Dm5MZg8A.js +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
/** Search an already-authorized text file with bounded memory/output/time. */
|
|
4
|
+
export function searchTextFile(file, options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
pattern,
|
|
7
|
+
regex = false,
|
|
8
|
+
caseSensitive = false,
|
|
9
|
+
offset = 0,
|
|
10
|
+
maxMatches = 20,
|
|
11
|
+
contextChars = 150,
|
|
12
|
+
timeout = 5000,
|
|
13
|
+
encoding = "utf8",
|
|
14
|
+
} = options;
|
|
15
|
+
if (
|
|
16
|
+
typeof pattern !== "string" ||
|
|
17
|
+
!pattern ||
|
|
18
|
+
pattern.length > 2048 ||
|
|
19
|
+
!Number.isSafeInteger(offset) ||
|
|
20
|
+
offset < 0 ||
|
|
21
|
+
!Number.isSafeInteger(maxMatches) ||
|
|
22
|
+
maxMatches < 1 ||
|
|
23
|
+
maxMatches > 100 ||
|
|
24
|
+
!Number.isSafeInteger(contextChars) ||
|
|
25
|
+
contextChars < 0 ||
|
|
26
|
+
contextChars > 1000 ||
|
|
27
|
+
!Number.isSafeInteger(timeout) ||
|
|
28
|
+
timeout < 1 ||
|
|
29
|
+
timeout > 30000 ||
|
|
30
|
+
!["utf8", "utf16le"].includes(encoding)
|
|
31
|
+
) {
|
|
32
|
+
return Promise.resolve({
|
|
33
|
+
error: "Invalid text search options",
|
|
34
|
+
code: "ERR_TEXT_SEARCH_OPTIONS",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
let toolLease = null;
|
|
38
|
+
if (options.hostResourceBudget) {
|
|
39
|
+
try {
|
|
40
|
+
toolLease = options.hostResourceBudget.admitTool({ kind: "text-search" });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return Promise.resolve({
|
|
43
|
+
error: "Text search unavailable: host resource budget",
|
|
44
|
+
code: "ERR_HOST_RESOURCE_BUDGET",
|
|
45
|
+
reason: String(error?.budgetReason || "unavailable").slice(0, 128),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
let worker;
|
|
51
|
+
let timer;
|
|
52
|
+
let settled = false;
|
|
53
|
+
let progress = { matches: [], nextOffset: offset };
|
|
54
|
+
const finish = (result) => {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
settled = true;
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
Promise.resolve(worker?.terminate())
|
|
59
|
+
.catch(() => {})
|
|
60
|
+
.finally(() => {
|
|
61
|
+
try {
|
|
62
|
+
toolLease?.release();
|
|
63
|
+
} finally {
|
|
64
|
+
resolve(result);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
worker = new Worker(
|
|
70
|
+
new URL("./text-file-search-worker.js", import.meta.url),
|
|
71
|
+
{
|
|
72
|
+
workerData: {
|
|
73
|
+
file,
|
|
74
|
+
pattern,
|
|
75
|
+
regex,
|
|
76
|
+
caseSensitive,
|
|
77
|
+
offset,
|
|
78
|
+
maxMatches,
|
|
79
|
+
contextChars,
|
|
80
|
+
encoding,
|
|
81
|
+
},
|
|
82
|
+
execArgv: [],
|
|
83
|
+
resourceLimits: { maxOldGenerationSizeMb: 64 },
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
worker.on("message", (message) => {
|
|
87
|
+
if (message.progress) progress = message.progress;
|
|
88
|
+
else finish(message);
|
|
89
|
+
});
|
|
90
|
+
worker.on("error", (error) =>
|
|
91
|
+
finish({ error: error.message, code: "ERR_TEXT_SEARCH" }),
|
|
92
|
+
);
|
|
93
|
+
worker.on("exit", () =>
|
|
94
|
+
finish({
|
|
95
|
+
error: "Text search worker stopped before completion",
|
|
96
|
+
code: "ERR_TEXT_SEARCH",
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
timer = setTimeout(
|
|
100
|
+
() =>
|
|
101
|
+
finish({
|
|
102
|
+
...progress,
|
|
103
|
+
error:
|
|
104
|
+
"Text search deadline exceeded; the full file was not searched",
|
|
105
|
+
code: "ERR_TEXT_SEARCH_TIMEOUT",
|
|
106
|
+
truncated: true,
|
|
107
|
+
hasMore: true,
|
|
108
|
+
hint: "Use a simpler literal query or continue from nextOffset. Do not treat partial results as a complete search.",
|
|
109
|
+
}),
|
|
110
|
+
timeout,
|
|
111
|
+
);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
finish({ error: error.message, code: "ERR_TEXT_SEARCH" });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { searchTextFile } from "./text-file-search.js";
|
|
6
|
+
|
|
7
|
+
/** Bounded process-local snapshots. IDs never resolve arbitrary caller paths. */
|
|
8
|
+
export class WebFetchSnapshots {
|
|
9
|
+
constructor({
|
|
10
|
+
maxBytes = 128_000_000,
|
|
11
|
+
maxEntries = 32,
|
|
12
|
+
ttlMs = 30 * 60_000,
|
|
13
|
+
now = Date.now,
|
|
14
|
+
} = {}) {
|
|
15
|
+
this.maxBytes = maxBytes;
|
|
16
|
+
this.maxEntries = maxEntries;
|
|
17
|
+
this.ttlMs = ttlMs;
|
|
18
|
+
this.now = now;
|
|
19
|
+
this.entries = new Map();
|
|
20
|
+
this.bytes = 0;
|
|
21
|
+
this.dir = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
remove(id) {
|
|
25
|
+
const entry = this.entries.get(id);
|
|
26
|
+
if (!entry) return;
|
|
27
|
+
// Only files created by this instance are ever deleted.
|
|
28
|
+
fs.rmSync(entry.file, { force: true });
|
|
29
|
+
this.entries.delete(id);
|
|
30
|
+
this.bytes -= entry.size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
expire() {
|
|
34
|
+
for (const [id, entry] of this.entries) {
|
|
35
|
+
if (entry.expiresAt <= this.now()) this.remove(id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
save(result, identity, owner) {
|
|
40
|
+
this.expire();
|
|
41
|
+
// UTF-16LE makes character offsets seekable without rereading the entire
|
|
42
|
+
// downloaded document for every page. Reads preserve surrogate pairs.
|
|
43
|
+
const size = Buffer.byteLength(result.content, "utf16le");
|
|
44
|
+
if (size > this.maxBytes || this.maxEntries < 1) {
|
|
45
|
+
throw Object.assign(
|
|
46
|
+
new Error(
|
|
47
|
+
"Webpage exceeds the local snapshot storage budget; request a focused page or API endpoint.",
|
|
48
|
+
),
|
|
49
|
+
{ code: "ERR_FETCH_SNAPSHOT_LIMIT" },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
while (
|
|
53
|
+
this.entries.size >= this.maxEntries ||
|
|
54
|
+
this.bytes + size > this.maxBytes
|
|
55
|
+
) {
|
|
56
|
+
this.remove(this.entries.keys().next().value);
|
|
57
|
+
}
|
|
58
|
+
this.dir ||= fs.mkdtempSync(path.join(os.tmpdir(), "cc-web-fetch-"));
|
|
59
|
+
const id = randomUUID();
|
|
60
|
+
const file = path.join(this.dir, `${id}.txt`);
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(file, result.content, {
|
|
63
|
+
encoding: "utf16le",
|
|
64
|
+
flag: "wx",
|
|
65
|
+
mode: 0o600,
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
try {
|
|
69
|
+
fs.rmSync(file, { force: true });
|
|
70
|
+
} catch {
|
|
71
|
+
/* preserve original failure */
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
const metadata = { ...result };
|
|
76
|
+
delete metadata.content;
|
|
77
|
+
this.entries.set(id, {
|
|
78
|
+
file,
|
|
79
|
+
size,
|
|
80
|
+
metadata,
|
|
81
|
+
identity,
|
|
82
|
+
owner,
|
|
83
|
+
expiresAt: this.now() + this.ttlMs,
|
|
84
|
+
});
|
|
85
|
+
this.bytes += size;
|
|
86
|
+
return id;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
read(id, identity, owner, offset, maxChars) {
|
|
90
|
+
this.expire();
|
|
91
|
+
const entry = this.entries.get(id);
|
|
92
|
+
if (!entry || entry.identity !== identity || entry.owner !== owner) {
|
|
93
|
+
return {
|
|
94
|
+
error:
|
|
95
|
+
"Webpage snapshot is unavailable or expired. Fetch the original URL again without snapshotId.",
|
|
96
|
+
code: "ERR_FETCH_SNAPSHOT_MISSING",
|
|
97
|
+
retryable: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const totalChars = entry.size / 2;
|
|
101
|
+
if (offset > totalChars) {
|
|
102
|
+
return {
|
|
103
|
+
error: `offset exceeds the saved text length (${totalChars})`,
|
|
104
|
+
code: "ERR_FETCH_OFFSET",
|
|
105
|
+
retryable: false,
|
|
106
|
+
totalChars,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const length = Math.min(maxChars, totalChars - offset);
|
|
110
|
+
const buffer = Buffer.alloc(Math.min(length + 1, totalChars - offset) * 2);
|
|
111
|
+
const fd = fs.openSync(entry.file, "r");
|
|
112
|
+
let read = 0;
|
|
113
|
+
try {
|
|
114
|
+
while (read < buffer.length) {
|
|
115
|
+
const n = fs.readSync(
|
|
116
|
+
fd,
|
|
117
|
+
buffer,
|
|
118
|
+
read,
|
|
119
|
+
buffer.length - read,
|
|
120
|
+
offset * 2 + read,
|
|
121
|
+
);
|
|
122
|
+
if (!n) throw new Error("Webpage snapshot was truncated on disk");
|
|
123
|
+
read += n;
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
fs.closeSync(fd);
|
|
127
|
+
}
|
|
128
|
+
const text = buffer.toString("utf16le");
|
|
129
|
+
let content = text.slice(0, length);
|
|
130
|
+
if (/[\uD800-\uDBFF]$/.test(content) && offset + length < totalChars) {
|
|
131
|
+
// A one-character request must still advance over an astral character.
|
|
132
|
+
content = length === 1 ? text.slice(0, 2) : content.slice(0, -1);
|
|
133
|
+
}
|
|
134
|
+
const nextOffset = offset + content.length;
|
|
135
|
+
const hasMore = nextOffset < totalChars;
|
|
136
|
+
this.entries.delete(id);
|
|
137
|
+
this.entries.set(id, entry);
|
|
138
|
+
return {
|
|
139
|
+
...entry.metadata,
|
|
140
|
+
snapshotId: id,
|
|
141
|
+
offset,
|
|
142
|
+
nextOffset: hasMore ? nextOffset : null,
|
|
143
|
+
hasMore,
|
|
144
|
+
totalChars,
|
|
145
|
+
maxChars,
|
|
146
|
+
truncated: hasMore || entry.metadata.downloadTruncated === true,
|
|
147
|
+
...(hasMore
|
|
148
|
+
? {
|
|
149
|
+
hint: `More saved text is available. Call web_fetch with the same URL, format and snapshotId, offset=${nextOffset}, and maxChars. This reads the local snapshot without another download.${entry.metadata.downloadTruncated ? " The downloaded page is incomplete; increase maxBytes in a new fetch for the missing remainder." : ""}`,
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
content,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async search(id, identity, owner, options) {
|
|
157
|
+
const page = this.read(id, identity, owner, 0, 1);
|
|
158
|
+
if (page.error) return page;
|
|
159
|
+
const result = await searchTextFile(this.entries.get(id).file, {
|
|
160
|
+
...options,
|
|
161
|
+
encoding: "utf16le",
|
|
162
|
+
hostResourceBudget: owner,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
...result,
|
|
166
|
+
snapshotId: id,
|
|
167
|
+
url: page.url,
|
|
168
|
+
totalChars: page.totalChars,
|
|
169
|
+
...(page.downloadTruncated
|
|
170
|
+
? {
|
|
171
|
+
downloadTruncated: true,
|
|
172
|
+
hint: "Only the downloaded prefix was searched. Increase maxBytes in a new download to search the missing remainder.",
|
|
173
|
+
}
|
|
174
|
+
: {}),
|
|
175
|
+
...(result.matches
|
|
176
|
+
? {
|
|
177
|
+
matches: result.matches.map((match) => ({
|
|
178
|
+
...match,
|
|
179
|
+
nextRead: {
|
|
180
|
+
url: options.url || page.url,
|
|
181
|
+
snapshotId: id,
|
|
182
|
+
format: page.format,
|
|
183
|
+
offset: Math.max(
|
|
184
|
+
0,
|
|
185
|
+
match.offset - (options.contextChars ?? 150),
|
|
186
|
+
),
|
|
187
|
+
maxChars: 2000,
|
|
188
|
+
},
|
|
189
|
+
})),
|
|
190
|
+
}
|
|
191
|
+
: {}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
dispose() {
|
|
196
|
+
for (const id of this.entries.keys()) this.remove(id);
|
|
197
|
+
if (this.dir) {
|
|
198
|
+
fs.rmdirSync(this.dir);
|
|
199
|
+
this.dir = null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const webFetchSnapshots = new WebFetchSnapshots();
|
|
205
|
+
process.once("exit", () => {
|
|
206
|
+
try {
|
|
207
|
+
webFetchSnapshots.dispose();
|
|
208
|
+
} catch {
|
|
209
|
+
/* OS temp cleanup can reclaim leftovers */
|
|
210
|
+
}
|
|
211
|
+
});
|