feat(sites): 出口统一与 Cloudflare 过挑战级联(CF-2)

- 出口:站点请求不再隐式继承 HTTP(S)_PROXY,没开代理明确直连(创建客户端时临时拿掉代理环境变量,Deno 2.9 语义见 egress.ts),
  开了走站点代理;TMDB / Brave / 图片改为显式按环境变量(行为不变)
- CloudflareGate:浏览器代理期直接代抓 → HTTP(可复用时带 cf_clearance)→ 挑战 → 浏览器过挑战(同站 30 分钟一次、单飞)
  → cf_clearance 仅在 UA 跟随浏览器且两端同出口时给 HTTP,重试 1 次记 cfReuse → 仍被挑战则浏览器代抓并进入 6 小时代理期
- 浏览器:solveSiteChallenge(自动放行 ≤15s / 系统发起视觉子流程最多 2 次 / 真验证码转人工待处理)、fetchSiteHtml(profile 锁 + 站点令牌)、
  交还后读回 cf_clearance 继续;site.config.cloudflare 状态契约

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 21:26:54 +08:00
1 parent 6b14b0da45
commit 16083942c0
14 files changed
+1711 -43

No files matched your search

+4 -2
View File
@@ -17,6 +17,7 @@ import {
type WebSearchTestResult,
} from "@umt/contracts";
import { cachedCall, pgCacheFor, pgTokenBucketFor } from "@umt/db";
import { externalFetch } from "../../egress.ts";
import { storageSettings } from "../../shared-cache.ts";
import type { ServerContext } from "../types.ts";
@@ -94,7 +95,8 @@ export class WebSearchService {
return createBraveClient({
apiKey,
...(this.opts.braveBaseUrl ? { baseUrl: this.opts.braveBaseUrl } : {}),
...(this.opts.fetch ? { fetch: this.opts.fetch } : {}),
// 外网请求:显式按环境变量走代理(CF-2 出口表)
fetch: this.opts.fetch ?? externalFetch,
});
}
@@ -184,7 +186,7 @@ export class WebSearchService {
allow ? "只能读取最近一次 web.search 结果里的链接" : "先用 web.search 搜索,再读取结果里的链接",
);
const page = await fetchPage(want, {
...(this.opts.fetch ? { fetch: this.opts.fetch } : {}),
fetch: this.opts.fetch ?? externalFetch,
...(this.opts.resolveHost ? { resolve: this.opts.resolveHost } : {}),
...(o.signal ? { signal: o.signal } : {}),
});