tianshu-harness 3.23.0

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 (255) hide show
  1. package/LICENSE +189 -0
  2. package/README.en.md +1066 -0
  3. package/README.ja.md +1214 -0
  4. package/README.ko.md +1216 -0
  5. package/README.md +309 -0
  6. package/completions/rivet.bash +118 -0
  7. package/completions/rivet.fish +110 -0
  8. package/completions/rivet.ps1 +230 -0
  9. package/completions/rivet.zsh +187 -0
  10. package/dist/account-OAEKORZE.js +47 -0
  11. package/dist/account-login-V6FOLHJR.js +65 -0
  12. package/dist/account-status-KHNPPAFL.js +23 -0
  13. package/dist/agent/worker-process/child.js +407 -0
  14. package/dist/browser-cli-FLNY5ZHI.js +19 -0
  15. package/dist/browser-readiness-LWVGSBD5.js +12 -0
  16. package/dist/build-backends-5KXKR2S4.js +13 -0
  17. package/dist/build-options-66Y4ZE7C.js +9 -0
  18. package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
  19. package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
  20. package/dist/chain-7MAMFXZ3.js +10 -0
  21. package/dist/checkpoint-MND4DUIX.js +33 -0
  22. package/dist/chunk-2E6CK2RC.js +392 -0
  23. package/dist/chunk-2EBWGAXD.js +51 -0
  24. package/dist/chunk-2HDP7RMR.js +119 -0
  25. package/dist/chunk-2HKD2UFM.js +1000 -0
  26. package/dist/chunk-2VYRLULP.js +51 -0
  27. package/dist/chunk-2XN7PM4N.js +125 -0
  28. package/dist/chunk-35H7GVNR.js +66 -0
  29. package/dist/chunk-3C3R7JXY.js +77 -0
  30. package/dist/chunk-3VGD2FXB.js +118 -0
  31. package/dist/chunk-424AP7FM.js +534 -0
  32. package/dist/chunk-4J5TZGOE.js +3707 -0
  33. package/dist/chunk-4QJ3Y5JM.js +898 -0
  34. package/dist/chunk-4U2ET7ZY.js +1326 -0
  35. package/dist/chunk-4XPFLICE.js +876 -0
  36. package/dist/chunk-5B2OCU5P.js +31707 -0
  37. package/dist/chunk-5GGOGLSN.js +73 -0
  38. package/dist/chunk-5JRZPLI6.js +251 -0
  39. package/dist/chunk-5TQ3JYBU.js +397 -0
  40. package/dist/chunk-66JPZSGX.js +55 -0
  41. package/dist/chunk-6HSVXHCZ.js +764 -0
  42. package/dist/chunk-6S5FPUS7.js +21 -0
  43. package/dist/chunk-722LQO64.js +131 -0
  44. package/dist/chunk-73NJ7IT7.js +354 -0
  45. package/dist/chunk-7FW3I7WC.js +95 -0
  46. package/dist/chunk-7KUCBNLB.js +173 -0
  47. package/dist/chunk-7RDOV3DF.js +201 -0
  48. package/dist/chunk-A5MNH4ER.js +79 -0
  49. package/dist/chunk-ADIVZ2D5.js +15 -0
  50. package/dist/chunk-AE3P3F6W.js +366 -0
  51. package/dist/chunk-AERM3VGP.js +280 -0
  52. package/dist/chunk-AEWHC4WM.js +246 -0
  53. package/dist/chunk-AOVQ7VIF.js +169 -0
  54. package/dist/chunk-AOYHP3WI.js +12411 -0
  55. package/dist/chunk-B3YEBABJ.js +7160 -0
  56. package/dist/chunk-BCQXEZPJ.js +154 -0
  57. package/dist/chunk-BKTQAYO2.js +418 -0
  58. package/dist/chunk-BSECIPAQ.js +80 -0
  59. package/dist/chunk-BUIMGNDD.js +52 -0
  60. package/dist/chunk-BXHV3GSB.js +10320 -0
  61. package/dist/chunk-C6ZVZCPP.js +139 -0
  62. package/dist/chunk-CEZNQVWM.js +82 -0
  63. package/dist/chunk-CGFZGR7G.js +1492 -0
  64. package/dist/chunk-CGOADM2X.js +337 -0
  65. package/dist/chunk-CI4R6P2B.js +24 -0
  66. package/dist/chunk-D2TCGRSX.js +73 -0
  67. package/dist/chunk-D4PTYFOG.js +154 -0
  68. package/dist/chunk-D54ZWG4T.js +717 -0
  69. package/dist/chunk-DEBI37QB.js +145 -0
  70. package/dist/chunk-DWROHPXP.js +1051 -0
  71. package/dist/chunk-DZ6UOJ5D.js +92 -0
  72. package/dist/chunk-EBJTVRQN.js +81 -0
  73. package/dist/chunk-EGEI2RQA.js +1167 -0
  74. package/dist/chunk-EHHUASO5.js +1305 -0
  75. package/dist/chunk-EPKTIOED.js +41 -0
  76. package/dist/chunk-ER5JW2QZ.js +5681 -0
  77. package/dist/chunk-ETHFACAB.js +315 -0
  78. package/dist/chunk-EXUE5W2F.js +112 -0
  79. package/dist/chunk-EXXICUIW.js +73 -0
  80. package/dist/chunk-FFQSZJOO.js +169 -0
  81. package/dist/chunk-FHFKPGDO.js +85 -0
  82. package/dist/chunk-FW3QKMFX.js +158 -0
  83. package/dist/chunk-GD4ZNZ7W.js +30 -0
  84. package/dist/chunk-GHVMWF2I.js +581 -0
  85. package/dist/chunk-GMKAXSYQ.js +297 -0
  86. package/dist/chunk-HBVF6BWO.js +325 -0
  87. package/dist/chunk-HV4YGVMS.js +379 -0
  88. package/dist/chunk-ILBCKAV6.js +89 -0
  89. package/dist/chunk-J4U4AW46.js +440 -0
  90. package/dist/chunk-J6ESSM47.js +1791 -0
  91. package/dist/chunk-JBZHIC6T.js +2309 -0
  92. package/dist/chunk-JCFWOULR.js +245 -0
  93. package/dist/chunk-JRF5L6OD.js +131 -0
  94. package/dist/chunk-JSS75KCG.js +2014 -0
  95. package/dist/chunk-JSXWIWWJ.js +225 -0
  96. package/dist/chunk-JYV7I2QH.js +98 -0
  97. package/dist/chunk-KIOXTQQL.js +1596 -0
  98. package/dist/chunk-KWVTXT5B.js +89 -0
  99. package/dist/chunk-KX22RD6R.js +33 -0
  100. package/dist/chunk-LA6IPTGH.js +709 -0
  101. package/dist/chunk-LH5VCGLB.js +1796 -0
  102. package/dist/chunk-LQQ6XJ2Y.js +269 -0
  103. package/dist/chunk-MJQ55WZZ.js +65 -0
  104. package/dist/chunk-ML3UKMH3.js +40 -0
  105. package/dist/chunk-MOZPLHSU.js +5526 -0
  106. package/dist/chunk-NGEWJ4DF.js +89 -0
  107. package/dist/chunk-NH6VIOAR.js +211 -0
  108. package/dist/chunk-O5VZQKCB.js +26106 -0
  109. package/dist/chunk-OHSV42QB.js +748 -0
  110. package/dist/chunk-OO3TAHO5.js +4070 -0
  111. package/dist/chunk-OTNHSAGN.js +38282 -0
  112. package/dist/chunk-OUWAC7AQ.js +442 -0
  113. package/dist/chunk-OXMIV4WE.js +270 -0
  114. package/dist/chunk-P5KJURUR.js +229 -0
  115. package/dist/chunk-P6GJOVO6.js +307 -0
  116. package/dist/chunk-QARAGUXX.js +23 -0
  117. package/dist/chunk-R43HJCDX.js +2711 -0
  118. package/dist/chunk-RAXBAWRN.js +210 -0
  119. package/dist/chunk-RK3JHWPT.js +39 -0
  120. package/dist/chunk-RWA23GMM.js +24 -0
  121. package/dist/chunk-S4K4L6IM.js +179 -0
  122. package/dist/chunk-SAXK6LVF.js +124 -0
  123. package/dist/chunk-SEPGEJNK.js +1607 -0
  124. package/dist/chunk-SUJERP5I.js +285 -0
  125. package/dist/chunk-T3ED6BY2.js +913 -0
  126. package/dist/chunk-THKDVSEC.js +90 -0
  127. package/dist/chunk-TI4GSEL5.js +52 -0
  128. package/dist/chunk-TPI73L57.js +164 -0
  129. package/dist/chunk-TVHW77C7.js +338 -0
  130. package/dist/chunk-TWNHOSDT.js +1073 -0
  131. package/dist/chunk-UFMN76QD.js +347 -0
  132. package/dist/chunk-UHGLFRZE.js +205 -0
  133. package/dist/chunk-UTZYPKBF.js +15231 -0
  134. package/dist/chunk-UXNLELW6.js +25 -0
  135. package/dist/chunk-UXXS53EA.js +166 -0
  136. package/dist/chunk-V5DHFGI7.js +171 -0
  137. package/dist/chunk-V5YIPMHV.js +2353 -0
  138. package/dist/chunk-WMUWEO2C.js +754 -0
  139. package/dist/chunk-WOE3D7JM.js +945 -0
  140. package/dist/chunk-X2IK5LVR.js +2592 -0
  141. package/dist/chunk-XU2YXCDL.js +459 -0
  142. package/dist/chunk-YJG5HIX5.js +203 -0
  143. package/dist/chunk-Z3QE7TZM.js +72 -0
  144. package/dist/chunk-Z5Z3JHJY.js +64 -0
  145. package/dist/chunk-ZDIFBJFT.js +168 -0
  146. package/dist/chunk-ZGGCEXDC.js +176 -0
  147. package/dist/chunk-ZMZCZNKU.js +263 -0
  148. package/dist/chunk-ZNC5T65N.js +17 -0
  149. package/dist/chunk-ZO3I3A7O.js +308 -0
  150. package/dist/chunk-ZQKXBUB7.js +217 -0
  151. package/dist/chunk-ZTRINFM2.js +78 -0
  152. package/dist/chunk-ZU7JZCVP.js +193 -0
  153. package/dist/chunk-ZZAWNALS.js +350 -0
  154. package/dist/cli/entry.js +54 -0
  155. package/dist/connector-L5QQTR6Y.js +26 -0
  156. package/dist/context-62DC6EC3.js +31 -0
  157. package/dist/council-convene-NJWPNBDU.js +46 -0
  158. package/dist/create-agent-config-ODSQWNVC.js +55 -0
  159. package/dist/default-registry-2PU3BTAU.js +68 -0
  160. package/dist/deliver-task-KONV4C4M.js +57 -0
  161. package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
  162. package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
  163. package/dist/fetch-core-ZPPYDSZH.js +14 -0
  164. package/dist/galaxy-K72BBDQN.js +91 -0
  165. package/dist/goal-criteria-SCALAU2D.js +141 -0
  166. package/dist/goal-persist-XWY2GDQX.js +19 -0
  167. package/dist/goal-tracker-FXMJ2GVK.js +11 -0
  168. package/dist/headless-LSB6I7PT.js +162 -0
  169. package/dist/headless-coordinator-VDPDMVUT.js +90 -0
  170. package/dist/inject-U55SMB6A.js +39 -0
  171. package/dist/login-flow-CD4ZGXOC.js +59 -0
  172. package/dist/logs-cli-SQVYFMMN.js +283 -0
  173. package/dist/loop-ULZYBDA3.js +76 -0
  174. package/dist/main.js +4064 -0
  175. package/dist/manager-ROZBCWPA.js +171 -0
  176. package/dist/manager-Z7XFFUOM.js +10670 -0
  177. package/dist/meridian-indexer-ZIW363L7.js +21 -0
  178. package/dist/native-resolver-PN34UIVV.js +13 -0
  179. package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
  180. package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
  181. package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  182. package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  183. package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  184. package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  185. package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  186. package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  187. package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  188. package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  189. package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  190. package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  191. package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
  192. package/dist/node_modules/better-sqlite3/package.json +59 -0
  193. package/dist/ownership-ledger-E3WIEKPL.js +9 -0
  194. package/dist/package.json +3 -0
  195. package/dist/path-grants-XRRXHXG7.js +40 -0
  196. package/dist/paths-LVV5IMQN.js +49 -0
  197. package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
  198. package/dist/plan-store-ODLTTPFB.js +17 -0
  199. package/dist/plan-templates-5FPFBOL6.js +109 -0
  200. package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
  201. package/dist/prefix-budget-62OV5HQC.js +13 -0
  202. package/dist/project-trust-5WS5MOMU.js +31 -0
  203. package/dist/provider-cli-ALDQXIT7.js +37 -0
  204. package/dist/providers-XQYTAVPK.js +11 -0
  205. package/dist/proxy-resolver-V2Y6534D.js +15 -0
  206. package/dist/recovery-cli-KPGY6FY3.js +106 -0
  207. package/dist/review-discipline-MNSYCST5.js +55 -0
  208. package/dist/review-router-BVQZW3MX.js +18 -0
  209. package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
  210. package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
  211. package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
  212. package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
  213. package/dist/seed-capsules/seed-capsule-fu.md +39 -0
  214. package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
  215. package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
  216. package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
  217. package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
  218. package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
  219. package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
  220. package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
  221. package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
  222. package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
  223. package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
  224. package/dist/serve-X2R2UTLF.js +170 -0
  225. package/dist/serve-agent-KHSU6RVV.js +965 -0
  226. package/dist/session-persist-HUUC772H.js +37 -0
  227. package/dist/session-registry-6XUD5C4Y.js +15 -0
  228. package/dist/skill-loader-CK3RMO47.js +48 -0
  229. package/dist/skills/cli-harness-sop.md +207 -0
  230. package/dist/spawn-git-4HFU65AH.js +29 -0
  231. package/dist/sse-EYUTBWUK.js +512 -0
  232. package/dist/starflow-NOFBMPFU.js +30 -0
  233. package/dist/streamableHttp-V67CNJMO.js +447 -0
  234. package/dist/task-contract-6XHBNJQK.js +32 -0
  235. package/dist/task-ledger-3K4TJIER.js +9 -0
  236. package/dist/team-orchestrate-S6NHZP7X.js +64 -0
  237. package/dist/title-extract-QX4XGCYN.js +50 -0
  238. package/dist/todo-QDXGRZZP.js +31 -0
  239. package/dist/todo-store-V3TTO5AH.js +12 -0
  240. package/dist/tool-preset-LU5BYEDZ.js +23 -0
  241. package/dist/tool-tiers-GD742YBN.js +23 -0
  242. package/dist/turndown.es-OI55K6C3.js +17373 -0
  243. package/dist/verification-attribution-EKG4CNUV.js +13 -0
  244. package/dist/volatile-git-MBTST7RA.js +15 -0
  245. package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
  246. package/dist/web-cli-DUG4I35O.js +233 -0
  247. package/dist/web-search-3IK542AA.js +34 -0
  248. package/dist/workers/cpu-worker.js +45 -0
  249. package/dist/workflow-runner-HX7UBAYG.js +297 -0
  250. package/dist/worktree-baseline-O5NCU6M2.js +9 -0
  251. package/package.json +122 -0
  252. package/scripts/check-mobile-env.mjs +76 -0
  253. package/scripts/fetch-native-sqlite.js +392 -0
  254. package/scripts/prune-installed-wasms.js +45 -0
  255. package/scripts/tree-sitter-wasm-keep.js +40 -0
@@ -0,0 +1,1000 @@
1
+ #!/usr/bin/env -S node --expose-gc --max-old-space-size=4096
2
+ import { createRequire as __rivetCreateRequire } from 'node:module'; const require = __rivetCreateRequire(import.meta.url);
3
+ import {
4
+ SSRFError,
5
+ httpFetchGuarded,
6
+ resolveAndAssertPublic
7
+ } from "./chunk-5JRZPLI6.js";
8
+ import {
9
+ resolveProxyForUrl
10
+ } from "./chunk-2HDP7RMR.js";
11
+ import {
12
+ launchHeadlessChromium
13
+ } from "./chunk-Z3QE7TZM.js";
14
+ import {
15
+ fetchCauseDetail
16
+ } from "./chunk-BKTQAYO2.js";
17
+ import {
18
+ init_esm_shims
19
+ } from "./chunk-5GGOGLSN.js";
20
+
21
+ // src/tools/web-fetch/fetch-core.ts
22
+ init_esm_shims();
23
+
24
+ // src/tools/web-fetch/extract.ts
25
+ init_esm_shims();
26
+ var _TurndownCtor = null;
27
+ async function loadTurndownCtor() {
28
+ if (!_TurndownCtor) {
29
+ const { default: TurndownService } = await import("./turndown.es-OI55K6C3.js");
30
+ _TurndownCtor = TurndownService;
31
+ }
32
+ return _TurndownCtor;
33
+ }
34
+ var MIN_SUBSTANTIAL_LENGTH = 200;
35
+ var EXCLUDE_NON_MAIN_SELECTORS = [
36
+ "header",
37
+ "footer",
38
+ "nav",
39
+ "aside",
40
+ ".header",
41
+ ".top",
42
+ ".navbar",
43
+ "#header",
44
+ ".footer",
45
+ ".bottom",
46
+ "#footer",
47
+ ".sidebar",
48
+ ".side",
49
+ ".aside",
50
+ "#sidebar",
51
+ ".modal",
52
+ ".popup",
53
+ "#modal",
54
+ ".overlay",
55
+ ".ad",
56
+ ".ads",
57
+ ".advert",
58
+ "#ad",
59
+ ".lang-selector",
60
+ ".language",
61
+ "#language-selector",
62
+ ".social",
63
+ ".social-media",
64
+ ".social-links",
65
+ "#social",
66
+ ".menu",
67
+ ".navigation",
68
+ "#nav",
69
+ ".breadcrumbs",
70
+ "#breadcrumbs",
71
+ ".share",
72
+ "#share",
73
+ ".widget",
74
+ "#widget",
75
+ ".cookie",
76
+ "#cookie"
77
+ ];
78
+ var FORCE_INCLUDE_SELECTORS = ["#main", "#content"];
79
+ function safeMatches(node, selector) {
80
+ try {
81
+ return node.matches(selector);
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+ function safeClosest(node, selector) {
87
+ try {
88
+ return node.closest(selector) !== null;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ function pickBestSrcsetCandidate(srcset, src) {
94
+ const candidates = [];
95
+ let hasLowRes = false;
96
+ for (const part of srcset.split(",")) {
97
+ const bits = part.trim().split(/\s+/);
98
+ const url = bits[0];
99
+ if (!url) continue;
100
+ const desc = bits[1] ?? "";
101
+ let score = 1;
102
+ if (desc.endsWith("w")) score = parseFloat(desc) || 1;
103
+ else if (desc.endsWith("x")) score = (parseFloat(desc) || 1) * 1e3;
104
+ else hasLowRes = true;
105
+ if (score <= 1e3) hasLowRes = true;
106
+ candidates.push({ url, score });
107
+ }
108
+ if (src && hasLowRes) candidates.push({ url: src, score: 1e3 });
109
+ candidates.sort((a, b) => b.score - a.score);
110
+ return candidates[0]?.url;
111
+ }
112
+ function pickImageSource(el) {
113
+ const srcset = el.getAttribute("srcset") || el.getAttribute("data-srcset");
114
+ if (srcset) {
115
+ const best = pickBestSrcsetCandidate(srcset, el.getAttribute("src"));
116
+ if (best) return best;
117
+ }
118
+ return el.getAttribute("data-src") || el.getAttribute("src") || void 0;
119
+ }
120
+ function absolutizeUrl(url, base) {
121
+ if (!base || /^(data|blob|about):/i.test(url)) return void 0;
122
+ try {
123
+ return new URL(url, base).href;
124
+ } catch {
125
+ return void 0;
126
+ }
127
+ }
128
+ function extractLinks(html, pageUrl) {
129
+ const base = resolveBaseUrl(html, pageUrl);
130
+ const links = /* @__PURE__ */ new Set();
131
+ const re = /<a\b[^>]*?href=["']([^"']+)["']/gi;
132
+ let m;
133
+ while ((m = re.exec(html)) !== null) {
134
+ const href = m[1].trim();
135
+ if (!href || href.startsWith("#") || /^(mailto|tel|javascript|data):/i.test(href)) continue;
136
+ const abs = absolutizeUrl(href, base);
137
+ if (abs && (abs.startsWith("http:") || abs.startsWith("https:"))) links.add(abs);
138
+ }
139
+ return [...links];
140
+ }
141
+ function extractLinksFromMarkdown(markdown) {
142
+ const links = /* @__PURE__ */ new Set();
143
+ const re = /\]\((https?:\/\/[^)\s]+?)(?:\s+"[^"]*")?\)/g;
144
+ let m;
145
+ while ((m = re.exec(markdown)) !== null) {
146
+ links.add(m[1]);
147
+ }
148
+ return [...links];
149
+ }
150
+ function resolveBaseUrl(html, pageUrl) {
151
+ const headWindow = html.slice(0, 16384);
152
+ const baseHref = headWindow.match(/<base\b[^>]*?href=["']([^"']+)["']/i)?.[1];
153
+ if (baseHref) {
154
+ try {
155
+ return pageUrl ? new URL(baseHref, pageUrl).href : baseHref;
156
+ } catch {
157
+ return baseHref;
158
+ }
159
+ }
160
+ return pageUrl;
161
+ }
162
+ function postProcessMarkdown(md) {
163
+ let out = "";
164
+ let depth = 0;
165
+ let inFence = false;
166
+ for (let i = 0; i < md.length; i++) {
167
+ const ch = md[i];
168
+ if (ch === "`" && md.startsWith("```", i)) {
169
+ inFence = !inFence;
170
+ out += "```";
171
+ i += 2;
172
+ continue;
173
+ }
174
+ if (!inFence) {
175
+ const prev = i > 0 ? md[i - 1] : "";
176
+ if (ch === "[" && prev !== "\\") depth++;
177
+ else if (ch === "]" && prev !== "\\" && depth > 0) depth--;
178
+ else if (ch === "\n" && depth > 0) {
179
+ out += "\\\n";
180
+ continue;
181
+ }
182
+ }
183
+ out += ch;
184
+ }
185
+ out = out.replace(/\[ *skip to (?:main )?content *\]\([^)]*\) *\n?/gi, "");
186
+ out = out.replace(/!\[[^\]]*\]\(data:[^)]+\)/g, "<Base64-Image-Removed>");
187
+ out = out.replace(/\n{3,}/g, "\n\n");
188
+ return out;
189
+ }
190
+ async function createTurndown(baseUrl) {
191
+ const TurndownService = await loadTurndownCtor();
192
+ const td = new TurndownService({
193
+ headingStyle: "atx",
194
+ codeBlockStyle: "fenced"
195
+ });
196
+ td.remove(["script", "style", "noscript", "svg", "iframe", "template", "head"]);
197
+ td.addRule("excludeNonMain", {
198
+ filter: (node) => node.nodeType === 1 && EXCLUDE_NON_MAIN_SELECTORS.some((sel) => safeMatches(node, sel)) && !FORCE_INCLUDE_SELECTORS.some((sel) => safeClosest(node, sel)),
199
+ replacement: () => ""
200
+ });
201
+ td.addRule("imageFix", {
202
+ filter: "img",
203
+ replacement: (_content, node) => {
204
+ const src = pickImageSource(node);
205
+ if (!src) return "";
206
+ if (src.startsWith("data:")) return "<Base64-Image-Removed>";
207
+ const abs = absolutizeUrl(src, baseUrl) ?? src;
208
+ const alt = (node.getAttribute("alt") || "").replace(/[[\]]/g, "").trim();
209
+ return `![${alt}](${abs})`;
210
+ }
211
+ });
212
+ td.addRule("absolutizeLink", {
213
+ filter: "a",
214
+ replacement: (content, node) => {
215
+ const href = node.getAttribute("href");
216
+ const text = content.trim();
217
+ if (!href || href.startsWith("#") || /^(mailto|tel|javascript):/i.test(href)) {
218
+ if (href?.startsWith("#") && /skip to/i.test(text)) return "";
219
+ return text || "";
220
+ }
221
+ const final = absolutizeUrl(href, baseUrl) ?? href;
222
+ if (!text) return final;
223
+ const title = node.getAttribute("title");
224
+ return title ? `[${text}](${final} "${title}")` : `[${text}](${final})`;
225
+ }
226
+ });
227
+ return td;
228
+ }
229
+ async function htmlToMarkdown(html, opts = {}) {
230
+ const td = await createTurndown(resolveBaseUrl(html, opts.pageUrl));
231
+ return postProcessMarkdown(td.turndown(html));
232
+ }
233
+ async function htmlToMarkdownSmart(html, opts = {}) {
234
+ if (opts.onlyMainContent !== false) {
235
+ const md = await htmlToMarkdown(extractMainContent(html), opts);
236
+ if (md.trim().length >= MIN_SUBSTANTIAL_LENGTH) return md;
237
+ }
238
+ return htmlToMarkdown(html, opts);
239
+ }
240
+ function decodeBody(bytes, contentType) {
241
+ const charset = detectCharset(bytes, contentType);
242
+ try {
243
+ return new TextDecoder(charset, { fatal: false }).decode(bytes);
244
+ } catch {
245
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
246
+ }
247
+ }
248
+ function detectCharset(bytes, contentType) {
249
+ const header = contentType.match(/charset=([^;]+)/i)?.[1]?.trim().replace(/^["']|["']$/g, "");
250
+ if (header) return header.toLowerCase();
251
+ if (contentType.includes("text/html")) {
252
+ const head = new TextDecoder("utf-8", { fatal: false }).decode(bytes.slice(0, 1024));
253
+ const meta = head.match(/<meta[^>]+charset=["']?([^"';>\s]+)/i)?.[1];
254
+ if (meta) return meta.toLowerCase();
255
+ }
256
+ return "utf-8";
257
+ }
258
+ var NOISE_TAGS = ["script", "style", "noscript", "svg", "nav", "header", "footer", "aside"];
259
+ function extractMainContent(html) {
260
+ const region = extractRegion(html, "main") ?? extractRegion(html, "article");
261
+ const source = region ?? html;
262
+ const noisePattern = new RegExp(`<(${NOISE_TAGS.join("|")})\\b[^>]*>[\\s\\S]*?</\\1>`, "gi");
263
+ const stripped = source.replace(noisePattern, "");
264
+ return stripped.trim();
265
+ }
266
+ function extractRegion(html, tag) {
267
+ const openRegex = new RegExp(`<${tag}\\b[^>]*>`, "i");
268
+ const openMatch = html.match(openRegex);
269
+ if (!openMatch) return void 0;
270
+ const start = openMatch.index + openMatch[0].length;
271
+ const openScanner = new RegExp(`<${tag}\\b[^>]*>`, "gi");
272
+ const closeScanner = new RegExp(`</${tag}>`, "gi");
273
+ let depth = 1;
274
+ let idx = start;
275
+ while (depth > 0) {
276
+ openScanner.lastIndex = idx;
277
+ closeScanner.lastIndex = idx;
278
+ const nextOpen = openScanner.exec(html);
279
+ const nextClose = closeScanner.exec(html);
280
+ if (!nextClose) return void 0;
281
+ if (nextOpen && nextOpen.index < nextClose.index) {
282
+ depth++;
283
+ idx = nextOpen.index + nextOpen[0].length;
284
+ } else {
285
+ depth--;
286
+ if (depth === 0) return html.slice(start, nextClose.index);
287
+ idx = nextClose.index + nextClose[0].length;
288
+ }
289
+ }
290
+ return void 0;
291
+ }
292
+
293
+ // src/tools/web-fetch/jina-fetch.ts
294
+ init_esm_shims();
295
+ var DEFAULT_JINA_BASE = "https://r.jina.ai";
296
+ function isJinaQualityHeuristic(md) {
297
+ const trimmed = md.trim();
298
+ if (trimmed.length < 200) return true;
299
+ const jsPageSignals = [
300
+ "Please enable JavaScript",
301
+ "Enable JavaScript",
302
+ "This page requires JavaScript",
303
+ "noscript",
304
+ "Checking your browser",
305
+ "Just a moment",
306
+ "DDOS protection",
307
+ 'id="challenge-form"',
308
+ // GitHub and similar SPA error shells — the page "loaded" but the app
309
+ // failed to hydrate, leaving a stub error banner as the only content.
310
+ "error while loading",
311
+ "please reload this page",
312
+ "uh oh"
313
+ ];
314
+ const lower = trimmed.toLowerCase();
315
+ return jsPageSignals.some((s) => lower.includes(s.toLowerCase()));
316
+ }
317
+ async function fetchViaJina(url, deps = {}, options = {}) {
318
+ const base = (options.jinaBaseUrl ?? DEFAULT_JINA_BASE).replace(/\/+$/, "");
319
+ const jinaUrl = `${base}/${url}`;
320
+ const { status, contentType, bytes } = await httpFetchGuarded(jinaUrl, deps, {
321
+ ...options
322
+ // Jina's text/plain responses are usually much smaller than raw HTML.
323
+ // Keep the same cap for consistency; if Jina returns oversized content
324
+ // the caller can re-fetch the raw URL with a higher cap.
325
+ });
326
+ const body = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
327
+ const sourceStatus = status;
328
+ const ok = status >= 200 && status < 400 && body.trim().length > 0;
329
+ return { sourceStatus, markdown: body, ok };
330
+ }
331
+
332
+ // src/tools/web-fetch/render-fetch.ts
333
+ init_esm_shims();
334
+ import { lookup as dnsLookup } from "dns/promises";
335
+
336
+ // src/tools/web-fetch/render-actions.ts
337
+ init_esm_shims();
338
+ var MAX_ACTIONS = 50;
339
+ var MAX_TOTAL_WAIT_MS = 6e4;
340
+ var ACTION_STEP_TIMEOUT_MS = 1e4;
341
+ var EXECUTE_JS_RESULT_LIMIT = 2048;
342
+ function isNonEmptyString(v) {
343
+ return typeof v === "string" && v.trim().length > 0;
344
+ }
345
+ function parseRenderActions(raw) {
346
+ if (!Array.isArray(raw)) return { error: "actions \u5FC5\u987B\u662F\u6570\u7EC4" };
347
+ if (raw.length === 0) return { error: "actions \u4E0D\u80FD\u4E3A\u7A7A\u6570\u7EC4" };
348
+ if (raw.length > MAX_ACTIONS) return { error: `actions \u6B65\u6570\u8D85\u9650\uFF08${raw.length} > ${MAX_ACTIONS}\uFF09` };
349
+ let totalWaitMs = 0;
350
+ const actions = [];
351
+ for (let i = 0; i < raw.length; i++) {
352
+ const a = raw[i];
353
+ if (!a || typeof a !== "object") return { error: `actions[${i}] \u4E0D\u662F\u5BF9\u8C61` };
354
+ switch (a.type) {
355
+ case "wait":
356
+ if (typeof a.ms === "number") {
357
+ if (!Number.isFinite(a.ms) || a.ms <= 0 || a.ms > MAX_TOTAL_WAIT_MS) {
358
+ return { error: `actions[${i}].ms \u975E\u6CD5\uFF08\u987B\u4E3A 1..${MAX_TOTAL_WAIT_MS}\uFF09` };
359
+ }
360
+ totalWaitMs += a.ms;
361
+ actions.push({ type: "wait", ms: a.ms });
362
+ } else if (isNonEmptyString(a.selector)) {
363
+ actions.push({ type: "wait", selector: a.selector.trim() });
364
+ } else {
365
+ return { error: `actions[${i}] wait \u9700\u8981 ms \u6216 selector` };
366
+ }
367
+ break;
368
+ case "click":
369
+ if (!isNonEmptyString(a.selector)) return { error: `actions[${i}].selector \u4E0D\u80FD\u4E3A\u7A7A` };
370
+ actions.push({ type: "click", selector: a.selector.trim(), all: a.all === true });
371
+ break;
372
+ case "write":
373
+ if (!isNonEmptyString(a.selector)) return { error: `actions[${i}].selector \u4E0D\u80FD\u4E3A\u7A7A` };
374
+ if (typeof a.text !== "string") return { error: `actions[${i}].text \u5FC5\u987B\u662F\u5B57\u7B26\u4E32` };
375
+ actions.push({ type: "write", selector: a.selector.trim(), text: a.text });
376
+ break;
377
+ case "press":
378
+ if (!isNonEmptyString(a.key)) return { error: `actions[${i}].key \u4E0D\u80FD\u4E3A\u7A7A` };
379
+ actions.push({
380
+ type: "press",
381
+ key: a.key.trim(),
382
+ ...isNonEmptyString(a.selector) ? { selector: a.selector.trim() } : {}
383
+ });
384
+ break;
385
+ case "scroll": {
386
+ const dir = a.direction;
387
+ actions.push({
388
+ type: "scroll",
389
+ direction: dir === "up" || dir === "top" || dir === "bottom" ? dir : "down",
390
+ ...isNonEmptyString(a.selector) ? { selector: a.selector.trim() } : {}
391
+ });
392
+ break;
393
+ }
394
+ case "execute_js":
395
+ if (!isNonEmptyString(a.script)) return { error: `actions[${i}].script \u4E0D\u80FD\u4E3A\u7A7A` };
396
+ actions.push({ type: "execute_js", script: a.script });
397
+ break;
398
+ default:
399
+ return { error: `actions[${i}] \u672A\u77E5\u7C7B\u578B\uFF1A${String(a.type)}\uFF08\u652F\u6301 wait/click/write/press/scroll/execute_js\uFF09` };
400
+ }
401
+ }
402
+ if (totalWaitMs > MAX_TOTAL_WAIT_MS) {
403
+ return { error: `wait \u603B\u65F6\u957F\u8D85\u9650\uFF08${totalWaitMs}ms > ${MAX_TOTAL_WAIT_MS}ms\uFF09` };
404
+ }
405
+ return { actions };
406
+ }
407
+ async function scrollPage(page, action) {
408
+ if (action.selector) {
409
+ await page.evaluate(
410
+ `document.querySelector(${JSON.stringify(action.selector)})?.scrollIntoView({ block: 'center', inline: 'nearest' })`
411
+ );
412
+ return;
413
+ }
414
+ switch (action.direction) {
415
+ case "top":
416
+ await page.evaluate("window.scrollTo(0, 0)");
417
+ break;
418
+ case "bottom":
419
+ await page.evaluate("window.scrollTo(0, document.body.scrollHeight)");
420
+ break;
421
+ case "up":
422
+ await page.evaluate("window.scrollBy(0, -window.innerHeight * 0.8)");
423
+ break;
424
+ default:
425
+ await page.evaluate("window.scrollBy(0, window.innerHeight * 0.8)");
426
+ }
427
+ }
428
+ async function executeRenderActions(page, actions) {
429
+ const results = [];
430
+ for (const action of actions) {
431
+ try {
432
+ switch (action.type) {
433
+ case "wait":
434
+ if ("ms" in action) await new Promise((r) => setTimeout(r, action.ms));
435
+ else await page.waitForSelector(action.selector, { timeout: ACTION_STEP_TIMEOUT_MS });
436
+ results.push({ type: "wait", ok: true });
437
+ break;
438
+ case "click":
439
+ if (action.all) {
440
+ await page.evaluate(
441
+ `document.querySelectorAll(${JSON.stringify(action.selector)}).forEach((el) => el.click())`
442
+ );
443
+ } else {
444
+ await page.click(action.selector, { timeout: ACTION_STEP_TIMEOUT_MS });
445
+ }
446
+ results.push({ type: "click", ok: true });
447
+ break;
448
+ case "write":
449
+ await page.fill(action.selector, action.text, { timeout: ACTION_STEP_TIMEOUT_MS });
450
+ results.push({ type: "write", ok: true });
451
+ break;
452
+ case "press":
453
+ if (action.selector) await page.press(action.selector, action.key, { timeout: ACTION_STEP_TIMEOUT_MS });
454
+ else if (page.keyboard) await page.keyboard.press(action.key);
455
+ results.push({ type: "press", ok: true });
456
+ break;
457
+ case "scroll":
458
+ await scrollPage(page, action);
459
+ results.push({ type: "scroll", ok: true });
460
+ break;
461
+ case "execute_js": {
462
+ const value = await page.evaluate(action.script);
463
+ let detail;
464
+ try {
465
+ detail = JSON.stringify(value) ?? String(value);
466
+ } catch {
467
+ detail = String(value);
468
+ }
469
+ if (detail.length > EXECUTE_JS_RESULT_LIMIT) {
470
+ detail = `${detail.slice(0, EXECUTE_JS_RESULT_LIMIT)}\u2026\uFF08\u622A\u65AD\uFF09`;
471
+ }
472
+ results.push({ type: "execute_js", ok: true, detail });
473
+ break;
474
+ }
475
+ }
476
+ } catch (err) {
477
+ const msg = err instanceof Error ? err.message.split("\n")[0] ?? err.message : String(err);
478
+ results.push({ type: action.type, ok: false, detail: msg });
479
+ break;
480
+ }
481
+ }
482
+ return results;
483
+ }
484
+
485
+ // src/tools/web-fetch/render-pool.ts
486
+ init_esm_shims();
487
+ var DEFAULT_IDLE_TIMEOUT_MS = 10 * 6e4;
488
+ var DEFAULT_MAX_PAGES = 8;
489
+ var RENDER_USER_AGENTS = [
490
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
491
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
492
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"
493
+ ];
494
+ function pickUserAgent() {
495
+ return RENDER_USER_AGENTS[Math.floor(Math.random() * RENDER_USER_AGENTS.length)];
496
+ }
497
+ var RenderPool = class {
498
+ browser = null;
499
+ launching = null;
500
+ activePages = 0;
501
+ idleTimer = null;
502
+ /** page → 其所属 context(每页独立 context:serviceWorkers 阻断 + UA/viewport 隔离) */
503
+ pageContexts = /* @__PURE__ */ new WeakMap();
504
+ idleTimeoutMs;
505
+ maxPages;
506
+ launch;
507
+ constructor(opts = {}) {
508
+ this.idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
509
+ this.maxPages = opts.maxPages ?? DEFAULT_MAX_PAGES;
510
+ this.launch = opts.launchBrowser ?? (() => launchHeadlessChromium({ proxy: opts.proxy }));
511
+ }
512
+ /** 当前活跃 Page 数(诊断与测试用)。 */
513
+ get pageCount() {
514
+ return this.activePages;
515
+ }
516
+ /** Browser 是否在跑(诊断与测试用)。 */
517
+ get isRunning() {
518
+ return this.browser !== null;
519
+ }
520
+ async acquirePage() {
521
+ if (this.activePages >= this.maxPages) {
522
+ throw new Error(`\u6E32\u67D3\u6C60 Page \u6570\u8D85\u9650\uFF08${this.maxPages}\uFF09`);
523
+ }
524
+ const browser = await this.ensureBrowser();
525
+ this.activePages += 1;
526
+ try {
527
+ const context = await browser.newContext({
528
+ serviceWorkers: "block",
529
+ viewport: { width: 1280, height: 800 },
530
+ userAgent: pickUserAgent()
531
+ });
532
+ const page = await context.newPage();
533
+ this.pageContexts.set(page, context);
534
+ this.disarmIdleTimer();
535
+ return page;
536
+ } catch (err) {
537
+ this.activePages -= 1;
538
+ throw err;
539
+ }
540
+ }
541
+ async releasePage(page) {
542
+ this.activePages = Math.max(0, this.activePages - 1);
543
+ const context = this.pageContexts.get(page);
544
+ try {
545
+ await (context ?? page).close();
546
+ } catch {
547
+ }
548
+ this.pageContexts.delete(page);
549
+ if (this.activePages === 0) this.armIdleTimer();
550
+ }
551
+ /** 立即关闭 Browser(空闲回收与进程退出共用)。 */
552
+ async closeBrowser() {
553
+ this.disarmIdleTimer();
554
+ const browser = this.browser;
555
+ this.browser = null;
556
+ if (browser) {
557
+ try {
558
+ await browser.close();
559
+ } catch {
560
+ }
561
+ }
562
+ }
563
+ async ensureBrowser() {
564
+ if (this.browser) {
565
+ if (this.browser.isConnected && !this.browser.isConnected()) {
566
+ this.browser = null;
567
+ } else {
568
+ return this.browser;
569
+ }
570
+ }
571
+ if (this.launching) return this.launching;
572
+ this.launching = this.launch().then((browser) => {
573
+ this.browser = browser;
574
+ this.launching = null;
575
+ browser.on("disconnected", (() => {
576
+ this.browser = null;
577
+ }));
578
+ return browser;
579
+ }).catch((err) => {
580
+ this.launching = null;
581
+ throw err;
582
+ });
583
+ return this.launching;
584
+ }
585
+ armIdleTimer() {
586
+ this.disarmIdleTimer();
587
+ this.idleTimer = setTimeout(() => {
588
+ void this.closeBrowser();
589
+ }, this.idleTimeoutMs);
590
+ this.idleTimer.unref?.();
591
+ }
592
+ disarmIdleTimer() {
593
+ if (this.idleTimer) {
594
+ clearTimeout(this.idleTimer);
595
+ this.idleTimer = null;
596
+ }
597
+ }
598
+ };
599
+ var defaultPool = null;
600
+ var exitHookInstalled = false;
601
+ function installExitHook() {
602
+ if (exitHookInstalled) return;
603
+ exitHookInstalled = true;
604
+ const cleanup = () => {
605
+ if (defaultPool) void defaultPool.closeBrowser().catch(() => {
606
+ });
607
+ };
608
+ process.once("exit", cleanup);
609
+ process.once("SIGINT", cleanup);
610
+ process.once("SIGTERM", cleanup);
611
+ }
612
+ function getDefaultRenderPool(opts = {}) {
613
+ if (!defaultPool) {
614
+ defaultPool = new RenderPool(opts);
615
+ installExitHook();
616
+ }
617
+ return defaultPool;
618
+ }
619
+
620
+ // src/tools/web-fetch/render-fetch.ts
621
+ var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
622
+ var AD_SERVING_DOMAINS = [
623
+ "doubleclick.net",
624
+ "adservice.google.com",
625
+ "googlesyndication.com",
626
+ "googletagservices.com",
627
+ "googletagmanager.com",
628
+ "google-analytics.com",
629
+ "adsystem.com",
630
+ "adservice.com",
631
+ "adnxs.com",
632
+ "ads-twitter.com",
633
+ "facebook.net",
634
+ "fbcdn.net",
635
+ "amazon-adsystem.com"
636
+ ];
637
+ function isAdHost(hostname) {
638
+ return AD_SERVING_DOMAINS.some((d) => hostname === d || hostname.endsWith("." + d));
639
+ }
640
+ function isLocalScheme(protocol) {
641
+ return protocol === "data:" || protocol === "blob:" || protocol === "about:";
642
+ }
643
+ async function fetchViaPlaywright(rawUrl, opts = {}) {
644
+ let target;
645
+ try {
646
+ target = new URL(rawUrl);
647
+ } catch {
648
+ return void 0;
649
+ }
650
+ const lookup = opts.lookup ?? ((hostname) => dnsLookup(hostname));
651
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
652
+ try {
653
+ await resolveAndAssertPublic(target.hostname, lookup);
654
+ } catch (err) {
655
+ if (err instanceof SSRFError) throw err;
656
+ return void 0;
657
+ }
658
+ const proxyServer = resolveProxyForUrl(rawUrl, opts.proxy);
659
+ const pool = opts.pool ?? getDefaultRenderPool(proxyServer ? { proxy: { server: proxyServer } } : {});
660
+ let page;
661
+ try {
662
+ page = await pool.acquirePage();
663
+ } catch {
664
+ return void 0;
665
+ }
666
+ let blockedRequests = 0;
667
+ let blockedAds = 0;
668
+ try {
669
+ await page.route("**/*", async (route, request) => {
670
+ const reqUrl = request.url();
671
+ let parsed;
672
+ try {
673
+ parsed = new URL(reqUrl);
674
+ } catch {
675
+ blockedRequests += 1;
676
+ await route.abort();
677
+ return;
678
+ }
679
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
680
+ if (isLocalScheme(parsed.protocol)) {
681
+ await route.continue();
682
+ return;
683
+ }
684
+ blockedRequests += 1;
685
+ await route.abort();
686
+ return;
687
+ }
688
+ if (isAdHost(parsed.hostname)) {
689
+ blockedAds += 1;
690
+ await route.abort();
691
+ return;
692
+ }
693
+ try {
694
+ await resolveAndAssertPublic(parsed.hostname, lookup);
695
+ await route.continue();
696
+ } catch {
697
+ blockedRequests += 1;
698
+ await route.abort();
699
+ }
700
+ });
701
+ await page.goto(rawUrl, { waitUntil: "domcontentloaded", timeout: timeoutMs });
702
+ if (opts.waitMs && opts.waitMs > 0) {
703
+ await new Promise((r) => setTimeout(r, opts.waitMs));
704
+ }
705
+ let actionResults;
706
+ if (opts.actions && opts.actions.length > 0) {
707
+ actionResults = await executeRenderActions(page, opts.actions);
708
+ }
709
+ const finalUrl = page.url();
710
+ if (finalUrl.startsWith("http:") || finalUrl.startsWith("https:")) {
711
+ const finalHost = new URL(finalUrl).hostname;
712
+ if (finalHost !== target.hostname) {
713
+ await resolveAndAssertPublic(finalHost, lookup);
714
+ }
715
+ }
716
+ const html = await page.content();
717
+ const pageUrl = finalUrl.startsWith("http") ? finalUrl : rawUrl;
718
+ const markdown = await htmlToMarkdownSmart(html, {
719
+ pageUrl,
720
+ onlyMainContent: opts.extractMainContent !== false
721
+ });
722
+ return {
723
+ markdown,
724
+ blockedRequests,
725
+ blockedAds,
726
+ ...actionResults ? { actionResults } : {},
727
+ links: extractLinks(html, pageUrl)
728
+ };
729
+ } catch (err) {
730
+ if (err instanceof SSRFError) throw err;
731
+ return void 0;
732
+ } finally {
733
+ await pool.releasePage(page);
734
+ }
735
+ }
736
+
737
+ // src/tools/web-fetch/fetch-cache.ts
738
+ init_esm_shims();
739
+ import { createHash } from "crypto";
740
+ import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
741
+ import { join } from "path";
742
+ var DEFAULT_CACHE_MAX_AGE_MS = 2 * 24 * 60 * 6e4;
743
+ var SWEEP_EVERY_N_WRITES = 20;
744
+ function normalizeCacheUrl(rawUrl) {
745
+ try {
746
+ const u = new URL(rawUrl);
747
+ u.hostname = u.hostname.toLowerCase();
748
+ if (u.protocol === "https:" && u.port === "443" || u.protocol === "http:" && u.port === "80") {
749
+ u.port = "";
750
+ }
751
+ if (u.hash && !u.hash.startsWith("#/") && !u.hash.startsWith("#!/")) u.hash = "";
752
+ return u.toString();
753
+ } catch {
754
+ return rawUrl;
755
+ }
756
+ }
757
+ var FetchCache = class {
758
+ constructor(dir, opts = {}) {
759
+ this.dir = dir;
760
+ this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_CACHE_MAX_AGE_MS;
761
+ this.now = opts.now ?? Date.now;
762
+ }
763
+ dir;
764
+ writesSinceSweep = 0;
765
+ maxAgeMs;
766
+ now;
767
+ key(url, variant) {
768
+ return createHash("sha256").update(`${normalizeCacheUrl(url)}
769
+ ${variant}`).digest("hex");
770
+ }
771
+ async read(url, variant) {
772
+ if (this.maxAgeMs === 0) return void 0;
773
+ try {
774
+ const raw = await readFile(join(this.dir, `${this.key(url, variant)}.json`), "utf8");
775
+ const entry = JSON.parse(raw);
776
+ if (typeof entry.fetchedAt !== "number" || typeof entry.markdown !== "string") return void 0;
777
+ if (entry.fetchedAt + this.maxAgeMs < this.now()) return void 0;
778
+ return entry;
779
+ } catch {
780
+ return void 0;
781
+ }
782
+ }
783
+ async write(url, variant, entry) {
784
+ try {
785
+ await mkdir(this.dir, { recursive: true });
786
+ const full = { ...entry, fetchedAt: this.now() };
787
+ await writeFile(join(this.dir, `${this.key(url, variant)}.json`), JSON.stringify(full));
788
+ this.writesSinceSweep += 1;
789
+ if (this.writesSinceSweep >= SWEEP_EVERY_N_WRITES) {
790
+ this.writesSinceSweep = 0;
791
+ void this.sweep().catch(() => {
792
+ });
793
+ }
794
+ } catch {
795
+ }
796
+ }
797
+ /** 清理过期条目(按 entry.fetchedAt 判定,与 read 的 TTL 语义严格一致;损坏条目一并清理)。 */
798
+ async sweep() {
799
+ const now = this.now();
800
+ let names;
801
+ try {
802
+ names = await readdir(this.dir);
803
+ } catch {
804
+ return;
805
+ }
806
+ for (const name of names) {
807
+ if (!name.endsWith(".json")) continue;
808
+ const path = join(this.dir, name);
809
+ try {
810
+ const raw = await readFile(path, "utf8");
811
+ const entry = JSON.parse(raw);
812
+ if (typeof entry.fetchedAt !== "number" || entry.fetchedAt + this.maxAgeMs < now) {
813
+ await rm(path, { force: true });
814
+ }
815
+ } catch {
816
+ await rm(path, { force: true }).catch(() => {
817
+ });
818
+ }
819
+ }
820
+ }
821
+ };
822
+ function fetchCacheDir(cwd) {
823
+ return join(cwd, ".rivet", "cache", "web-fetch");
824
+ }
825
+ var caches = /* @__PURE__ */ new Map();
826
+ function getFetchCache(cwd, opts = {}) {
827
+ const dir = fetchCacheDir(cwd);
828
+ let cache = caches.get(dir);
829
+ if (!cache) {
830
+ cache = new FetchCache(dir, opts);
831
+ caches.set(dir, cache);
832
+ }
833
+ return cache;
834
+ }
835
+ function formatCacheAge(fetchedAt, now = Date.now()) {
836
+ const minutes = Math.max(1, Math.round((now - fetchedAt) / 6e4));
837
+ if (minutes < 60) return `${minutes} \u5206\u949F`;
838
+ const hours = Math.round(minutes / 60);
839
+ if (hours < 24) return `${hours} \u5C0F\u65F6`;
840
+ return `${Math.round(hours / 24)} \u5929`;
841
+ }
842
+
843
+ // src/tools/web-fetch/fetch-core.ts
844
+ var DEFAULT_TIMEOUT_MS = 15e3;
845
+ var DEFAULT_MAX_BYTES = 10485760;
846
+ var DEFAULT_MAX_REDIRECTS = 5;
847
+ var DEFAULT_USER_AGENT = "Tianshu/1.0 (terminal coding agent)";
848
+ var BINARY_CONTENT_TYPE_PREFIXES = [
849
+ "image/",
850
+ "application/pdf",
851
+ "application/octet-stream",
852
+ "video/",
853
+ "audio/",
854
+ "font/"
855
+ ];
856
+ function httpApiErrorKind(status) {
857
+ if (status === 429 || status === 500 || status === 502 || status === 503) return "api_error";
858
+ return void 0;
859
+ }
860
+ async function fetchMarkdown(rawUrl, deps, opts) {
861
+ let url;
862
+ try {
863
+ url = new URL(rawUrl);
864
+ } catch {
865
+ return { ok: false, error: `\u65E0\u6548 URL\uFF1A${rawUrl}` };
866
+ }
867
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
868
+ return { ok: false, error: `\u4E0D\u652F\u6301\u7684\u534F\u8BAE\uFF1A${url.protocol}\u3002\u4EC5\u5141\u8BB8 http \u548C https\u3002` };
869
+ }
870
+ const options = {
871
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
872
+ maxResponseBytes: opts.maxResponseBytes ?? DEFAULT_MAX_BYTES,
873
+ maxRedirects: opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS,
874
+ userAgent: opts.userAgent ?? DEFAULT_USER_AGENT
875
+ };
876
+ const extractMainContentEnabled = opts.extractMainContent ?? true;
877
+ const renderFetch = deps.renderFetch ?? (opts.enablePlaywright ? (u) => fetchViaPlaywright(u, {
878
+ timeoutMs: opts.renderTimeoutMs,
879
+ waitMs: opts.renderWaitMs,
880
+ lookup: deps.lookup,
881
+ extractMainContent: extractMainContentEnabled
882
+ }) : void 0);
883
+ const cache = deps.cache ?? getFetchCache(opts.cwd, { maxAgeMs: opts.cacheMaxAgeMs });
884
+ const cacheVariant = `e${extractMainContentEnabled ? 1 : 0}`;
885
+ const cached = await cache.read(rawUrl, cacheVariant);
886
+ if (cached) {
887
+ return {
888
+ ok: true,
889
+ status: cached.status,
890
+ markdown: cached.markdown,
891
+ via: cached.via,
892
+ links: extractLinksFromMarkdown(cached.markdown),
893
+ rawBytes: cached.markdown.length,
894
+ fromCache: true,
895
+ fetchedAt: cached.fetchedAt
896
+ };
897
+ }
898
+ const writeCache = async (status, markdown, via) => {
899
+ if (markdown.trim().length < MIN_SUBSTANTIAL_LENGTH) return;
900
+ await cache.write(rawUrl, cacheVariant, { url: rawUrl, markdown, via, status });
901
+ };
902
+ try {
903
+ const { status, contentType, bytes } = await httpFetchGuarded(rawUrl, deps, options);
904
+ const contentTypeLower = contentType.toLowerCase();
905
+ if (status >= 400) {
906
+ if (contentTypeLower.includes("text/html")) {
907
+ const body2 = decodeBody(bytes, contentType);
908
+ const md = await htmlToMarkdownSmart(body2, {
909
+ pageUrl: rawUrl,
910
+ onlyMainContent: extractMainContentEnabled
911
+ });
912
+ if (md.trim().length >= MIN_SUBSTANTIAL_LENGTH) {
913
+ await writeCache(status, md, "");
914
+ return {
915
+ ok: true,
916
+ status,
917
+ markdown: md,
918
+ via: "",
919
+ links: extractLinks(body2, rawUrl),
920
+ rawBytes: bytes.length,
921
+ fromCache: false
922
+ };
923
+ }
924
+ }
925
+ return {
926
+ ok: false,
927
+ error: `HTTP ${status}\uFF1A${rawUrl}`,
928
+ ...httpApiErrorKind(status) ? { errorKind: "api_error" } : {}
929
+ };
930
+ }
931
+ if (BINARY_CONTENT_TYPE_PREFIXES.some((prefix) => contentTypeLower.includes(prefix))) {
932
+ return {
933
+ ok: false,
934
+ error: `\u4E8C\u8FDB\u5236\u5185\u5BB9\uFF08${contentType}\uFF09\u4E0D\u4F1A\u4EE5\u6587\u672C\u8FD4\u56DE\u3002\u8BF7\u4F7F\u7528 import_resource \u4E0B\u8F7D\u6B64 URL\u3002`
935
+ };
936
+ }
937
+ const body = decodeBody(bytes, contentType);
938
+ let content;
939
+ let via = "";
940
+ let links = [];
941
+ if (contentTypeLower.includes("text/html")) {
942
+ links = extractLinks(body, rawUrl);
943
+ content = await htmlToMarkdownSmart(body, {
944
+ pageUrl: rawUrl,
945
+ onlyMainContent: extractMainContentEnabled
946
+ });
947
+ if (isJinaQualityHeuristic(content)) {
948
+ if (renderFetch) {
949
+ const rendered = await renderFetch(rawUrl);
950
+ if (rendered && rendered.markdown.trim().length >= MIN_SUBSTANTIAL_LENGTH) {
951
+ content = rendered.markdown;
952
+ via = "\uFF08\u7ECF Playwright \u6E32\u67D3\uFF09";
953
+ links = rendered.links ?? extractLinksFromMarkdown(content);
954
+ }
955
+ }
956
+ if (!via) {
957
+ const jinaResult = await fetchViaJina(rawUrl, deps, {
958
+ ...options,
959
+ ...opts.jinaBaseUrl ? { jinaBaseUrl: opts.jinaBaseUrl } : {}
960
+ });
961
+ if (jinaResult) {
962
+ content = jinaResult.markdown;
963
+ via = "\uFF08\u7ECF Jina Reader\uFF09";
964
+ links = extractLinksFromMarkdown(content);
965
+ }
966
+ }
967
+ }
968
+ } else {
969
+ content = body;
970
+ }
971
+ await writeCache(status, content, via);
972
+ return {
973
+ ok: true,
974
+ status,
975
+ markdown: content,
976
+ via,
977
+ links,
978
+ rawBytes: bytes.length,
979
+ fromCache: false
980
+ };
981
+ } catch (err) {
982
+ if (err instanceof SSRFError) {
983
+ return { ok: false, error: err.message };
984
+ }
985
+ const message = err instanceof Error ? err.message : String(err);
986
+ const detail = fetchCauseDetail(err);
987
+ const full = detail ? `${message}: ${detail}` : message;
988
+ return { ok: false, error: `\u6293\u53D6\u5931\u8D25 ${rawUrl}\uFF1A${full}` };
989
+ }
990
+ }
991
+
992
+ export {
993
+ MIN_SUBSTANTIAL_LENGTH,
994
+ decodeBody,
995
+ parseRenderActions,
996
+ fetchViaPlaywright,
997
+ normalizeCacheUrl,
998
+ formatCacheAge,
999
+ fetchMarkdown
1000
+ };