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.
Files changed (171) hide show
  1. package/README.md +27 -2
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-D0L1OM_i.js → AIOps-D8PJmSrh.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CeZUkX_V.js → ActionButton-DG6DqHA3.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-P_7CIOew.js → Analytics-DiLleeWs.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-NuqDYrlO.js → AppLayout-DxYkhlHK.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-D1BTQqJM.js → Artifacts-BAW3Kdsi.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-D9XL6DVf.js → Audit-DBr4uY6E.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-Ci2DB2gV.js → BackgroundAgents-Dw0r-bHX.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CreK3Yqn.js → Backup-AyhwYsio.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-BkIRKFXX.js → BaseInput-Cj8qJotr.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BoJjlCDT.js → Chat-DcvimQ0u.js} +6 -6
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer--joJ1e2f.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-BbA932P3.js → Checkbox-C2mlU77l.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BS_f3kQy.js → Codegen-CAfc3YaV.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BJ1tzpRh.js → Col-CIxoxtJI.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DYsZZlgI.js → Community-DvufZoRg.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-Cq41A43W.js → Compact-55JTa99J.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-CBqSz1op.js → Compliance-BllWog6P.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-ZAn8buD_.js → Cowork-ThsFGc5p.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DM4WTZQT.js → Cron-CZjl5Ig2.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-S65sJZwe.js → Crosschain-DIAKAoQT.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-0Av7FTMv.js → DID-ayygwEil.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-D-1vFtiV.js → Dashboard-C3Xjm2UO.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BZufOCir.js → Dropdown-C1S3zf-j.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-DJE8szQZ.js → EmailListRenderer-DRvriU-H.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqqPHaZn.js → FamilyGuardDashboard-iSX2bmsD.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CslSyj60.js → Federation-Dk0Stkv-.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-uTfAl63o.js → FormItemContext-DasXSGff.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-BIP2obER.js → GenericCardRenderer-Dv9XGlxI.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-CoVpUKSg.js → Git-B5PgXL3Z.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-zW6mlFcK.js → Governance-B78x11e_.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-zfu2tMaa.js → Inference-b7CX1RqW.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Dd38pMCe.js → KnowledgeGraph--MgBzMVp.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-BhX28L73.js → Logs-CYCiGmVc.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-yrl10SWw.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-DGBKtVL6.js → Marketplace-CsbwQ0eM.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-C0jSXrYN.js → McpTools-0GBWHVra.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-_xyOJV55.js → Memory-fAywmAYr.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-DGm6cS5Y.js → MobileBridge-BeNEyy3s.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-Zt1cpErd.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-CSfoeF9f.js → Mtc-CqRhJB70.js} +6 -6
  43. package/src/assets/web-panel/assets/{MtcAudit-DuXFbCYL.js → MtcAudit-BEyGZflc.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-CdnOBFhs.js → Multisig-luz3EBT2.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-C0URZ4u4.js → NLProgramming-Dkbvp8IX.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-CJznx6ft.js → Notes-Cf9I2K8j.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-DJcqYoZP.js → NotificationSettings-BTfuzynL.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-DBmCtl2H.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-ucvP5Gzp.js → Organization-BbG3-vDy.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-Be3DoWvY.js → Overflow-CMtFKEIQ.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BXV17CWM.js → P2P-6SAEb_lU.js} +2 -2
  52. package/src/assets/web-panel/assets/PdhVaultBrowser-CWG2OLvh.js +7 -0
  53. package/src/assets/web-panel/assets/{Permissions-Bn0mjFJA.js → Permissions-ClZxZKZl.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CQ2uLeuD.js → PersonalDataHub-BYkyoZDT.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-DWv7KJKL.js → Pipeline-B28ZgADI.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-B4I0G5_J.js → Privacy-BxtZywbB.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-CDGy6B7j.js → ProjectInit-DPqoRnxK.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CXUq9nGH.js → ProjectSettings-QjKvMkau.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-B-IA5qsB.js → Projects-Cp0Z8ZG0.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-Card8pXM.js → Providers-CrS-rbyb.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DrU0hFRp.js → QrScannerModal-xTrhbVAU.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-cB-AFOxA.js → QuickAsk-DFOqJoq-.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-CvAvFr0u.js → Recommend-DXlKqu2-.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-BIrtatCm.js → RemoteSession-BLLaZH8K.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-CrRpi112.js → Reputation-CUgAdJ11.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DmaenKNU.js → Row-DzOO3wP-.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CGZBcNLM.js → RssFeed-B6GrRvh8.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-Bvxjc4AU.js → Search-Cj3zKLb3.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-1Z-5c2n6.js → Security-LnktzidI.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-3zYh5_28.js → Services-BQ6sIc2K.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DEyGi_mq.js → Skeleton-CCSV3Jbw.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-BjWR0303.js → Skills-CpoQ4SNk.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-C6rE_C6V.js → Sla-DbAbgRPq.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-DJ1DtDuf.js → SpeechSettings-C4cJ_hoD.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-B2j0LEry.js → SyncSettings-BKFzjN9Z.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-Cf4bl7NS.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-Eb0UjdGe.js → Templates-DTEQfFAQ.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BlkC3jZ8.js → Tenant-wToJWKXB.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-MMaa8P6V.js → Terminal-1s3c7kmF.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-B4EsSPro.js → TimelineRenderer-BcfsYW3u.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-Chk0rule.js → Tokens-Byez6FY0.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-Cww9ZKjK.js → Trigger-Cq8mZO88.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-xSwx_Zhk.js → Trust-DUrMmEwR.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CNaC0gRj.js → UkeySign-CVHwXrVU.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-gpoRF70F.js → VideoEditing-CDKbTRUS.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-eSjvqmUV.js → Wallet-BqnEBIOy.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-C4T03Dyz.js → WebAuthn-CeTMpj7_.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-eR0JIHbW.js → WorkflowEditor-F6OgjV3C.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-CPRK3APB.js → chat-CMHUCwq3.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BP4ZS2SA.js → colors-DzVM_fpJ.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-C2bvxEX0.js → compact-item-Hhmg51yT.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-DfgFYASc.js → createContext-SwHt7x3Q.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-5q_Y0E88.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-BlwCGOVd.js → hasIn-HGNKkn9A.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Bg_1EOhP.js → index--iOqM_AI.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DDavuQ3n.js → index-AQcDW8bB.js} +3 -3
  97. package/src/assets/web-panel/assets/{index-CBJrpDfv.js → index-B6YQN5_R.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BPezy3IJ.js → index-BDE-WiE5.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-JhYgZWbg.js → index-BDhCx17_.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C2n5VKtr.js → index-BDt4wSl6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BENgpvOm.js → index-BJqNTF1S.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CKZNLHhD.js → index-BRikmONJ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-C3qzM-P9.js → index-B_HgLPC5.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DLTzHq3a.js → index-Ba2Kszdy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEohnDSJ.js → index-BlHB5eax.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DQ-CB1zc.js → index-BmPYmoY3.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-5b_mMYB_.js → index-Bq9MMKno.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-IYshwMo1.js → index-BstLYy9q.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DzHzRE9E.js → index-C-YkiVyL.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CZHYspFu.js → index-C1QJxiOL.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B_LuOKcc.js → index-CDtzUt75.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-9VWjFlVU.js → index-CMG_2fK2.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-QE8jhGyd.js → index-COOcGOyK.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C6Zbp8DY.js → index-CWkWh48N.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-_5mGFZRu.js → index-Cc-d6f0F.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BO1fPYye.js → index-ChLHLBkF.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-B-ubYfKF.js → index-Cnm4zroJ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BgUnGGL8.js → index-Co4RcBua.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-AbVd9bHP.js → index-D5dcWBqg.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DZbQQtMn.js → index-DQT5thlV.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DR2Tg8uN.js → index-Dcg7nQDe.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DdaniU1x.js → index-DgoefbLs.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CyMwW_p5.js → index-DyZUrnje.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dx3HnzcX.js → index-E-bI8HpV.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B2D-mqRz.js → index-F7Grz3-G.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DDGliY6u.js → index-MDdTkUpA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-XBrnfoX6.js → index-aKV4Py_2.js} +1 -1
  128. package/src/assets/web-panel/assets/index-dl9TJMh5.js +1 -0
  129. package/src/assets/web-panel/assets/{index-B0HiXNYh.js → index-jrRtmiUh.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DnD3N8YJ.js → index-oz0Xxs8D.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Sk-fx_32.js → index-pwLuJGFh.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-WSC6WoDZ.js → index-sYkFp9A4.js} +1 -1
  133. package/src/assets/web-panel/assets/index-x0DDgUaT.js +1 -0
  134. package/src/assets/web-panel/assets/{initDefaultProps-CmR0B8Wx.js → initDefaultProps-C_2QM45F.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BC0UJWJb.js → motion-DkxY04tR.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-cvEJQRZ_.js → move-D0CJlA7D.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-0ZPAo5px.js → mtc-parser-DjP38YUD.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-UBXYakkw.js → omit-C9CJDaGT.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-CRZgdSvi.js → pickAttrs-D5Wq2fa8.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-B_Y8sKr_.js → placementArrow-DJ6Dixs7.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CFgbiMsJ.js → responsiveObserve-DMmM0Dee.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CF6LYcYS.js → slide-DipmpYA-.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-Cj2PusGV.js → statusUtils-DSqw6_nU.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-Czg7dDLr.js → styleChecker-t828MYjI.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Bhi9P67E.js → useFlexGapSupport-COsSNzDQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-X9lqJXBc.js → useFs-Bq5IEzv_.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-iL34aL71.js → usePersonalDataHub-CUKM0HdW.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DJ7TA9fc.js → vnode-l-JwAt2M.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DfQDsJXd.js → zoom-D7xkW-gD.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/data/changelog.json +52 -0
  152. package/src/lib/evolution/evolution-file-witness.js +59 -13
  153. package/src/lib/evolution/evolution-ledger-file-backend.js +16 -1
  154. package/src/lib/remote-read-loop-guard.js +290 -0
  155. package/src/lib/task-progress-tracker.js +5 -1
  156. package/src/lib/text-file-search-worker.js +147 -0
  157. package/src/lib/text-file-search.js +116 -0
  158. package/src/lib/web-fetch-snapshots.js +211 -0
  159. package/src/lib/web-fetch.js +317 -26
  160. package/src/lib/web-search.js +118 -10
  161. package/src/runtime/agent-core.js +85 -1
  162. package/src/runtime/coding-agent-contract-shared.cjs +98 -4
  163. package/src/assets/web-panel/assets/ChatBubbleRenderer-D68YIuni.js +0 -1
  164. package/src/assets/web-panel/assets/MarkdownRenderer-CbOmzhyX.js +0 -1
  165. package/src/assets/web-panel/assets/MobileProjects-DqNyu4pO.js +0 -1
  166. package/src/assets/web-panel/assets/OrderTableRenderer-CEQJHoU9.js +0 -1
  167. package/src/assets/web-panel/assets/PdhVaultBrowser-CZDiOia9.js +0 -7
  168. package/src/assets/web-panel/assets/Tasks-ClYwVLRE.js +0 -1
  169. package/src/assets/web-panel/assets/devWarning-CahG3JuS.js +0 -1
  170. package/src/assets/web-panel/assets/index-BZ3kGfit.js +0 -1
  171. 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
+ });