dsh-free-search 0.9.12 → 0.9.13
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 +8 -6
- package/lib/client.js +1 -1
- package/lib/fetch_provider.js +7 -3
- package/lib/index.js +37 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -307,7 +307,8 @@ provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa
|
|
|
307
307
|
fallbackOn: [quota, auth, bot-wall, transient, invalid-response, unknown] # 允许触发回退的失败类别;[] = 任何失败都立即中止(默认全部)
|
|
308
308
|
lang: zh # 搜索语言池/本地化(zh / en / ru …,不再被设置页强行改写)
|
|
309
309
|
# lang 决定:Bing 的 mkt/Accept-Language、Wikipedia 子域(ja → ja.wikipedia.org)、地理编码语言;
|
|
310
|
-
# 我们自己渲染的文案只有中文/英文两套,其它语言(ja/ru/de…)一律走英文,避免回答前先塞一段中文(#92)。
|
|
310
|
+
# 我们自己渲染的文案只有中文/英文两套,其它语言(ja/ru/de…)一律走英文,避免回答前先塞一段中文(#92)。
|
|
311
|
+
# web_fetch(fs-quality-fetch)的 Accept-Language 同样跟随 lang,不再固定中文(#92)。
|
|
311
312
|
uiLang: zh # 设置页界面语言(zh / en);留空则跟随 lang
|
|
312
313
|
bingMarket: zh-CN # Bing 市场;留空 = 自动(不发送 mkt)
|
|
313
314
|
fetchProviderTakeover: true # false = 保留你显式 pin 的 fetchProvider: http
|
|
@@ -489,7 +490,7 @@ Search engine test:
|
|
|
489
490
|
|
|
490
491
|
#### 离线自检断言(tools/)
|
|
491
492
|
|
|
492
|
-
仓库自带 **24 个**零依赖断言脚本(合计 **
|
|
493
|
+
仓库自带 **24 个**零依赖断言脚本(合计 **558 项**),**不需要 API key、不联网、不需要测试框架**,Node ≥ 20 即可跑:
|
|
493
494
|
|
|
494
495
|
| 脚本 | 覆盖 | 运行(项数) |
|
|
495
496
|
|---|---|---|
|
|
@@ -509,7 +510,7 @@ Search engine test:
|
|
|
509
510
|
| `assert-killtree-win32.mjs` | Windows 子进程树终止(#74):不再依赖 `taskkill` | `node tools/assert-killtree-win32.mjs`(8) |
|
|
510
511
|
| `assert-killtree-portable.mjs` | 跨平台 `killTree` 便携版契约 | `node tools/assert-killtree-portable.mjs`(5) |
|
|
511
512
|
| `assert-parallel-mcp.mjs` | Parallel MCP 引擎:25 s 超时/调用方取消覆盖**响应体读取**、共享 SSE 解析器(通知/多行帧)、6 类错误路径、REST 精确时间过滤 | `node tools/assert-parallel-mcp.mjs`(15) |
|
|
512
|
-
| `assert-output-language.mjs` | 语言分支(#92):`searchLanguage` 支持 zh/en/ja/ru/de/es/fr/ko 其余回退 en、`outputLanguage` 只输出 zh/en、天气文案 ja→英文、置信度说明随语言、失败备注随语言 | `node tools/assert-output-language.mjs`(
|
|
513
|
+
| `assert-output-language.mjs` | 语言分支(#92):`searchLanguage` 支持 zh/en/ja/ru/de/es/fr/ko 其余回退 en、`outputLanguage` 只输出 zh/en、天气文案 ja→英文、置信度说明随语言、失败备注随语言 | `node tools/assert-output-language.mjs`(8) |
|
|
513
514
|
| `assert-uncertainty.mjs` | 输出契约:0/1 源为 low、2 源与同站为 medium、3 个不同域为 high、时间过滤无日期、失败备注、聚合站提示 | `node tools/assert-uncertainty.mjs`(8) |
|
|
514
515
|
| `assert-multikey.mjs` | 多 key 字段:单 key 不变、逗号/分号/换行分隔、空白项丢弃、空输入为空数组 | `node tools/assert-multikey.mjs`(3) |
|
|
515
516
|
| `assert-firecrawl-keyless.mjs` | Firecrawl keyless 出口判定:403 + VPN/proxy 文案才算拒绝、普通 403 与 401/429/500 不算、冷却初始未触发 | `node tools/assert-firecrawl-keyless.mjs`(5) |
|
|
@@ -844,7 +845,8 @@ provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa
|
|
|
844
845
|
fallbackOn: [quota, auth, bot-wall, transient, invalid-response, unknown] # failure classes allowed to trigger fallback; [] = abort on any failure (default: all)
|
|
845
846
|
lang: zh # search language pools / localization (zh / en / ru …; no longer rewritten by the settings page)
|
|
846
847
|
# lang drives Bing's mkt/Accept-Language, the Wikipedia subdomain (ja → ja.wikipedia.org) and the geocoding language;
|
|
847
|
-
# text this plugin renders itself only exists in Chinese and English, so other languages (ja/ru/de…) get English rather than a wall of Chinese (#92).
|
|
848
|
+
# text this plugin renders itself only exists in Chinese and English, so other languages (ja/ru/de…) get English rather than a wall of Chinese (#92).
|
|
849
|
+
# web_fetch (fs-quality-fetch) sends the same Accept-Language instead of a fixed Chinese one (#92).
|
|
848
850
|
uiLang: zh # settings UI language (zh / en); empty = follow lang
|
|
849
851
|
bingMarket: zh-CN # Bing market; empty = automatic (no mkt sent)
|
|
850
852
|
fetchProviderTakeover: true # false = keep the fetchProvider: http you pinned yourself
|
|
@@ -1026,7 +1028,7 @@ Restart `dsh web` after switching to apply changes.
|
|
|
1026
1028
|
|
|
1027
1029
|
#### Offline self-check assertions (`tools/`)
|
|
1028
1030
|
|
|
1029
|
-
**Twenty-four** zero-dependency assertion scripts ship with the repo (**
|
|
1031
|
+
**Twenty-four** zero-dependency assertion scripts ship with the repo (**558 assertions** total): **no API keys, no network, no test framework**, Node ≥ 20:
|
|
1030
1032
|
|
|
1031
1033
|
| Script | Covers | Run (count) |
|
|
1032
1034
|
|---|---|---|
|
|
@@ -1046,7 +1048,7 @@ Restart `dsh web` after switching to apply changes.
|
|
|
1046
1048
|
| `assert-killtree-win32.mjs` | Windows process-tree termination (#74): no longer relies on `taskkill` | `node tools/assert-killtree-win32.mjs` (8) |
|
|
1047
1049
|
| `assert-killtree-portable.mjs` | portable `killTree` contract | `node tools/assert-killtree-portable.mjs` (5) |
|
|
1048
1050
|
| `assert-parallel-mcp.mjs` | Parallel MCP engine: the 25 s timeout / caller abort covers the **body read**, shared SSE parser (notifications, multiline frames), six error paths, REST precise time filtering | `node tools/assert-parallel-mcp.mjs` (15) |
|
|
1049
|
-
| `assert-output-language.mjs` | language branches (#92): `searchLanguage` passes zh/en/ja/ru/de/es/fr/ko through and falls back to en, `outputLanguage` only returns zh/en, weather text is English for ja, and both confidence and failure notes follow the language | `node tools/assert-output-language.mjs` (
|
|
1051
|
+
| `assert-output-language.mjs` | language branches (#92): `searchLanguage` passes zh/en/ja/ru/de/es/fr/ko through and falls back to en, `outputLanguage` only returns zh/en, weather text is English for ja, and both confidence and failure notes follow the language | `node tools/assert-output-language.mjs` (8) |
|
|
1050
1052
|
| `assert-uncertainty.mjs` | output contract: 0/1 source is low, two sources or a single site is medium, three distinct domains is high, time filter without dates, failure notes, aggregator caveat | `node tools/assert-uncertainty.mjs` (8) |
|
|
1051
1053
|
| `assert-multikey.mjs` | multi-key fields: a single key is unchanged, comma/semicolon/newline separate keys, blank entries are dropped, empty input yields none | `node tools/assert-multikey.mjs` (3) |
|
|
1052
1054
|
| `assert-firecrawl-keyless.mjs` | Firecrawl keyless egress refusal: only a 403 with the VPN/proxy wording counts, plain 403 and 401/429/500 do not, and the cooldown starts inactive | `node tools/assert-firecrawl-keyless.mjs` (5) |
|
package/lib/client.js
CHANGED
|
@@ -426,7 +426,7 @@ window.__ModuleLoader__.load({
|
|
|
426
426
|
};
|
|
427
427
|
const tt = (lang) => I18N[String(lang ?? "").trim().toLowerCase() === "zh" ? "zh" : "en"];
|
|
428
428
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致;跟随上游 v0.7.5)
|
|
429
|
-
const PLUGIN_VERSION = "0.9.
|
|
429
|
+
const PLUGIN_VERSION = "0.9.13";
|
|
430
430
|
const PLUGIN_REPO_URL = "https://github.com/DDDMUC/dsh-free-search";
|
|
431
431
|
// 右侧浮动目录(#85):条目顺序即页面顺序,anchor 是页面里对应 field 的 id。
|
|
432
432
|
const NAV_ITEMS = [
|
package/lib/fetch_provider.js
CHANGED
|
@@ -274,11 +274,11 @@ export function httpsGetFollow(url, { headers = {}, timeoutMs = 20000, signal }
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
// 完整浏览器请求头(Chrome 128 形态):部分反爬站(如百度系)对精简头直接 403,完整头可通过。
|
|
277
|
+
// Accept-Language 不在这里:它跟随 lang(#92),由调用方逐次传入,否则日语环境抓到的仍是中文版页面。
|
|
277
278
|
// 注意:声明了 Accept-Encoding 就必须解压(httpsGetFollow 已支持 gzip/br/deflate)。
|
|
278
279
|
const BROWSERISH = {
|
|
279
280
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
|
280
281
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
281
|
-
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
|
282
282
|
"Accept-Encoding": "gzip, deflate, br",
|
|
283
283
|
"sec-ch-ua": '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"',
|
|
284
284
|
"sec-ch-ua-mobile": "?0",
|
|
@@ -290,7 +290,11 @@ const BROWSERISH = {
|
|
|
290
290
|
"Upgrade-Insecure-Requests": "1",
|
|
291
291
|
};
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
/** 请求头:Accept-Language 跟随调用方(#92),其余固定为浏览器形态。 */
|
|
294
|
+
export function browserishHeaders(acceptLanguage = "en-US,en;q=0.9") {
|
|
295
|
+
return { ...BROWSERISH, "Accept-Language": String(acceptLanguage ?? "").trim() || "en-US,en;q=0.9" };
|
|
296
|
+
}
|
|
297
|
+
export function createFetchProvider({ resolveApiKey, logger, acceptLanguage = () => "en-US,en;q=0.9" }) {
|
|
294
298
|
return {
|
|
295
299
|
id: "fs-quality-fetch",
|
|
296
300
|
/** 廉价本地可用性检查(seam 契约要求,禁止网络调用)。 */
|
|
@@ -302,7 +306,7 @@ export function createFetchProvider({ resolveApiKey, logger }) {
|
|
|
302
306
|
|
|
303
307
|
// T1:直连(文本类内容;二进制交给后层——exa 取正文,PDF 等仍由 firecrawl 处理)
|
|
304
308
|
try {
|
|
305
|
-
const r = await httpsGetFollow(url, { headers:
|
|
309
|
+
const r = await httpsGetFollow(url, { headers: browserishHeaders(acceptLanguage()), signal });
|
|
306
310
|
if (r.status < 400 && TEXTUAL.test(r.contentType) && r.text.length >= T1_MIN_CHARS) {
|
|
307
311
|
return { url: r.url, statusCode: r.status, body: { kind: "html", content: r.text }, truncated: false };
|
|
308
312
|
}
|
package/lib/index.js
CHANGED
|
@@ -164,6 +164,13 @@ const USER_AGENT =
|
|
|
164
164
|
const ACCEPT_LANG = "zh-CN,zh;q=0.9,en;q=0.8";
|
|
165
165
|
// 当前请求生效的 Accept-Language:由 lang(或显式 bingMarket)决定,搜索前设置(#92)。
|
|
166
166
|
let activeAcceptLanguage = ACCEPT_LANG;
|
|
167
|
+
/** 由配置推导 Accept-Language:显式 bingMarket 优先,否则跟随 lang(未知回退 en)。 */
|
|
168
|
+
export function acceptLanguageFor(cfg) {
|
|
169
|
+
const market = cfg?.bingMarket;
|
|
170
|
+
if (market && MARKET_TO_LANG[market]) return MARKET_TO_LANG[market];
|
|
171
|
+
return LANG_PROFILES[searchLanguage(configLanguage(cfg))].acceptLang;
|
|
172
|
+
}
|
|
173
|
+
|
|
167
174
|
export function setActiveAcceptLanguage(value) {
|
|
168
175
|
if (typeof value === "string" && value.trim() !== "") activeAcceptLanguage = value.trim();
|
|
169
176
|
}
|
|
@@ -180,6 +187,18 @@ export function searchLanguage(lang) {
|
|
|
180
187
|
return Object.prototype.hasOwnProperty.call(LANG_PROFILES, tag) ? tag : "en";
|
|
181
188
|
}
|
|
182
189
|
|
|
190
|
+
/**
|
|
191
|
+
* 配置里生效的语言标签:未设置时按 schema 默认 zh(#92)。
|
|
192
|
+
* searchLanguage() 只负责「未知值回退 en」,两者不要混用。
|
|
193
|
+
*/
|
|
194
|
+
export function configLanguage(cfg) {
|
|
195
|
+
const value = cfg?.lang;
|
|
196
|
+
if (value === undefined || value === null) return "zh";
|
|
197
|
+
const tag = String(value).trim().toLowerCase();
|
|
198
|
+
return tag === "" ? "zh" : tag;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
183
202
|
export function outputLanguage(lang) {
|
|
184
203
|
return searchLanguage(lang) === "zh" ? "zh" : "en";
|
|
185
204
|
}
|
|
@@ -380,7 +399,7 @@ function routeEngines(query, { timeRange, disabledEngines = [], fallbackOrder =
|
|
|
380
399
|
}
|
|
381
400
|
|
|
382
401
|
// 当前插件版本(发布时与 package.json 同步;跟随上游 v0.7.5,不在 PR 里 bump)
|
|
383
|
-
const PLUGIN_VERSION = "0.9.
|
|
402
|
+
const PLUGIN_VERSION = "0.9.13";
|
|
384
403
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
385
404
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
386
405
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -742,13 +761,13 @@ function extractBingUrl(href) {
|
|
|
742
761
|
async function searchBing(query, maxResults, options, signal) {
|
|
743
762
|
// mkt:仅显式 bingMarket 才发。默认不发——mkt=zh-CN 在部分地区/线路会返回空 SERP 容器
|
|
744
763
|
// (#71:海外 IP + mkt=zh-CN → b_algo=0),Bing 会按 Accept-Language 自行本地化。
|
|
745
|
-
const profile = LANG_PROFILES[options
|
|
764
|
+
const profile = LANG_PROFILES[searchLanguage(configLanguage(options))];
|
|
746
765
|
const params = new URLSearchParams({ q: query });
|
|
747
766
|
if (options?.bingMarket) params.set("mkt", options.bingMarket);
|
|
748
767
|
// Accept-Language:显式 bingMarket 时按 market 反推(避免中文 header 污染),否则用 lang 档案
|
|
749
768
|
const acceptLang = options?.bingMarket
|
|
750
|
-
? (MARKET_TO_LANG[options.bingMarket] ??
|
|
751
|
-
: (profile.acceptLang ??
|
|
769
|
+
? (MARKET_TO_LANG[options.bingMarket] ?? activeAcceptLanguage)
|
|
770
|
+
: (profile.acceptLang ?? activeAcceptLanguage);
|
|
752
771
|
const adlt = options?.safeSearch ?? "off";
|
|
753
772
|
if (adlt === "off") params.set("adlt", "off");
|
|
754
773
|
else if (adlt === "moderate") params.set("adlt", "moderate");
|
|
@@ -1383,7 +1402,7 @@ function sliceBalancedObject(text, start) {
|
|
|
1383
1402
|
// json 字段 murl(真实播放地址) / vt(标题) / du(时长) / purl(详情页)(字段语义以实测为准)。
|
|
1384
1403
|
async function searchBingVideos(query, maxResults, options, signal) {
|
|
1385
1404
|
const params = new URLSearchParams({ q: query, count: String(Math.max(Number(maxResults) || 10, 1)), first: "1", mmasync: "1" });
|
|
1386
|
-
const market = options?.bingMarket ??
|
|
1405
|
+
const market = options?.bingMarket ?? LANG_PROFILES[searchLanguage(configLanguage(options))].market;
|
|
1387
1406
|
if (market) params.set("mkt", market);
|
|
1388
1407
|
const html = await fetchVideoHtml(`${BING_VIDEOS_ASYNC_URL}?${params}`, signal);
|
|
1389
1408
|
if (/captcha|unusual traffic|robot check/i.test(html.slice(0, 5000))) {
|
|
@@ -3822,10 +3841,8 @@ function apply(ctx, config) {
|
|
|
3822
3841
|
// #72:本次请求生效的域名黑名单(模块级活动值;各结果咽喉点过滤读取它)
|
|
3823
3842
|
setActiveBlockedDomains(cfg.blockedDomains);
|
|
3824
3843
|
// #92:Accept-Language 跟随 lang(显式 bingMarket 优先),不再固定中文
|
|
3825
|
-
setActiveAcceptLanguage(
|
|
3826
|
-
|
|
3827
|
-
);
|
|
3828
|
-
setActiveOutputLanguage(outputLanguage(cfg.lang));
|
|
3844
|
+
setActiveAcceptLanguage(acceptLanguageFor(cfg));
|
|
3845
|
+
setActiveOutputLanguage(outputLanguage(configLanguage(cfg)));
|
|
3829
3846
|
const disabledEngines = normalizeDisabledEngines(cfg.disabledEngines);
|
|
3830
3847
|
const fallbackOnSet = new Set(normalizeFallbackOn(cfg.fallbackOn));
|
|
3831
3848
|
const disabledSet = new Set(disabledEngines);
|
|
@@ -4371,7 +4388,12 @@ function apply(ctx, config) {
|
|
|
4371
4388
|
// web_fetch 质量链接管:注册 fs-quality-fetch(直连 → exa contents → firecrawl → crawl4ai),
|
|
4372
4389
|
// 未设置 fetchProviderId、或仍是出厂默认 http 时接管;已被显式指向其他 provider 时不抢占。
|
|
4373
4390
|
// 与 web_search 的接管规则同构:运行时兜底,不依赖 bundle patch 是否落地。
|
|
4374
|
-
const fetchProvider = createFetchProvider({
|
|
4391
|
+
const fetchProvider = createFetchProvider({
|
|
4392
|
+
resolveApiKey,
|
|
4393
|
+
logger,
|
|
4394
|
+
// #92:web_fetch 不经过 searchOnce,所以这里按当前配置现算,而不是复用上次搜索留下的值。
|
|
4395
|
+
acceptLanguage: () => acceptLanguageFor(current()),
|
|
4396
|
+
});
|
|
4375
4397
|
registerOnce(() => ctx.web.registerFetchProvider(fetchProvider), fetchProvider, "fetch provider");
|
|
4376
4398
|
|
|
4377
4399
|
// 接管必须先等 settings 服务就绪:rc.2 起 provider 选择是 config 的 volatile ref,
|
|
@@ -5049,7 +5071,7 @@ function apply(ctx, config) {
|
|
|
5049
5071
|
);
|
|
5050
5072
|
}
|
|
5051
5073
|
const limit = Math.min(args.maxResults ?? 5, 10);
|
|
5052
|
-
let result = await searchPlatform(platform, args.query, limit, undefined, current()
|
|
5074
|
+
let result = await searchPlatform(platform, args.query, limit, undefined, configLanguage(current()));
|
|
5053
5075
|
if (platform === "vimeo" && (result.sources ?? []).length === 0) {
|
|
5054
5076
|
// Vimeo 搜索页有时不返回结果:退回网页搜索(site: 过滤)
|
|
5055
5077
|
try {
|
|
@@ -5194,7 +5216,7 @@ function apply(ctx, config) {
|
|
|
5194
5216
|
output: {
|
|
5195
5217
|
schema: WEATHER_OUTPUT_SCHEMA,
|
|
5196
5218
|
render(args, value) {
|
|
5197
|
-
const lang = outputLanguage(current()
|
|
5219
|
+
const lang = outputLanguage(configLanguage(current()));
|
|
5198
5220
|
const c = value.current ?? {};
|
|
5199
5221
|
const lines = [];
|
|
5200
5222
|
lines.push(lang === "en" ? `Weather for ${value.location}:` : `${value.location} 天气:`);
|
|
@@ -5215,7 +5237,7 @@ function apply(ctx, config) {
|
|
|
5215
5237
|
},
|
|
5216
5238
|
async execute(args) {
|
|
5217
5239
|
// 天气正文只提供 zh/en(回退 en),地理编码用真实搜索语言(ja 就查日文地名)。
|
|
5218
|
-
const lang = searchLanguage(current()
|
|
5240
|
+
const lang = searchLanguage(configLanguage(current()));
|
|
5219
5241
|
const location = String(args.location ?? "").trim();
|
|
5220
5242
|
if (!location) throw new Error("weather: location is required");
|
|
5221
5243
|
return sanitizeWeatherResult(await fetchWeather(location, args.days, lang, undefined));
|
|
@@ -5324,7 +5346,7 @@ function apply(ctx, config) {
|
|
|
5324
5346
|
sources: result.sources ?? [],
|
|
5325
5347
|
timeRange: Boolean(request.timeRange),
|
|
5326
5348
|
note: typeof result.content === "string" ? result.content : "",
|
|
5327
|
-
lang: current()
|
|
5349
|
+
lang: configLanguage(current()),
|
|
5328
5350
|
}),
|
|
5329
5351
|
},
|
|
5330
5352
|
};
|
|
@@ -5424,7 +5446,7 @@ function apply(ctx, config) {
|
|
|
5424
5446
|
uncertainty: assessUncertainty({
|
|
5425
5447
|
sources: toolResult.sources ?? [],
|
|
5426
5448
|
note: typeof toolResult.content === "string" ? toolResult.content : "",
|
|
5427
|
-
lang: current()
|
|
5449
|
+
lang: configLanguage(current()),
|
|
5428
5450
|
}),
|
|
5429
5451
|
},
|
|
5430
5452
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 27 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Zhihu global/site via MCP and Perplexity/SerpBase/DeepSeek (official + native tools/search)/You.com/Baidu/Kimi/Aliyun/Doubao with key; OpenAI/Gemini/Claude model built-in search, explicit-only) + auto smart routing + global engine enable/disable & fallback priority + editable zh/en language pools + multi mode (parallel full language-pool merge) + multi_search cross-source tool + failure-aware fallback (quota/auth cooldown, transient retry, fallbackOn) + time filtering + platform search + domain blocklist (blockedDomains) + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|