dsh-free-search 0.6.5 → 0.6.7
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 +30 -20
- package/cordis.patch.yml +14 -11
- package/lib/client.js +2 -2
- package/lib/index.js +32 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,28 +108,33 @@ dsh web
|
|
|
108
108
|
|
|
109
109
|
#### 接管行为与验证
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
插件加载后**自动接管搜索**(运行时接管,不改配置层):
|
|
112
112
|
|
|
113
|
-
- `web.searchProvider` 未设置,或仍是 DSH 出厂默认的官方搜索 `deepseek-official`
|
|
113
|
+
- `web.searchProvider` 未设置,或仍是 DSH 出厂默认的官方搜索 `deepseek-official` 时,自动切换为本插件的 provider(id 固定为 `ddg`);
|
|
114
114
|
- 如果(你或别的插件)已显式选择其他 provider,本插件不抢占,只在启动日志输出 WARN 与切换用的 YAML。
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
**不再往配置里写 `searchProvider: ddg`**:静态覆盖曾导致"悬空引用"——patch 是整段覆盖 config,一旦插件那一行被**停用或跳过**(比如在插件管理器里关掉),配置仍指向 `ddg` 而没有任何东西注册它,于是每次搜索都失败:
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
```
|
|
119
|
+
Error: configured web provider "ddg" is not registered
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
现在没有这层覆盖:插件被停用时 `web.searchProvider` 保持官方默认(`deepseek-official`),搜索至少还能走官方通道;插件启用时由上面的运行时接管切到 `ddg`。
|
|
123
|
+
|
|
124
|
+
> 如果你曾按旧文档手动在 `profiles/<profile>/cordis.patch.yml` 里加过下面这段,而插件没有启用,就会看到上面那条错误——**删掉这段即可**(或重新启用插件):
|
|
119
125
|
|
|
120
126
|
```yaml
|
|
121
|
-
#
|
|
122
|
-
# DSH 0.1.2+ 的 patch 是"整段覆盖 config"
|
|
123
|
-
# 已有 web 条目里的字段(如 fetchProvider)必须在这里一并重述,否则会被抹掉。
|
|
127
|
+
# 可选,通常不需要:只有你想强制指定时才加。
|
|
128
|
+
# DSH 0.1.2+ 的 patch 是"整段覆盖 config",必须保留 fetchProvider。
|
|
124
129
|
- id: web
|
|
125
130
|
config:
|
|
126
131
|
searchProvider: ddg
|
|
127
132
|
fetchProvider: http
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
- `searchProvider: ddg` ——
|
|
131
|
-
- `fetchProvider: http` —— 官方网页抓取(web-fetch-http
|
|
132
|
-
-
|
|
135
|
+
- `searchProvider: ddg` —— 本插件的 **provider id(固定值)**,不是"使用 DuckDuckGo 引擎";用哪个引擎由设置页的 `provider` 字段决定(`bing`/`baidu`/`auto`/…)。
|
|
136
|
+
- `fetchProvider: http` —— 官方网页抓取(web-fetch-http),手动声明时必须保留。
|
|
137
|
+
- 想改用官方搜索:在插件管理器里停用本插件条目即可,不需要改配置。
|
|
133
138
|
|
|
134
139
|
#### 姊妹插件:dsh-preset-workbench(预设工作台)
|
|
135
140
|
|
|
@@ -431,28 +436,33 @@ dsh web
|
|
|
431
436
|
|
|
432
437
|
#### Takeover behavior and verification
|
|
433
438
|
|
|
434
|
-
The plugin **takes over search
|
|
439
|
+
The plugin **takes over search at runtime** once loaded (it does not touch the config layer):
|
|
435
440
|
|
|
436
|
-
- When `web.searchProvider` is unset, or still the shipped default `deepseek-official`, it switches to this plugin;
|
|
441
|
+
- When `web.searchProvider` is unset, or still the shipped default `deepseek-official`, it switches to this plugin's provider (id: `ddg`);
|
|
437
442
|
- If you (or another plugin) explicitly selected a different provider, it does not steal it — it only logs a WARN with a copy-pasteable YAML snippet.
|
|
438
443
|
|
|
439
|
-
|
|
444
|
+
**It no longer writes `searchProvider: ddg` into the config layer.** A static override used to create a dangling reference: a patch replaces the whole row config, so when the plugin's row was **disabled or skipped** (e.g. switched off in the plugin manager) the config still said `ddg` while nothing registered it, and every search failed with:
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
Error: configured web provider "ddg" is not registered
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
Without that override, a disabled plugin leaves `web.searchProvider` at the shipped default (`deepseek-official`) so search still works through the official provider; when the plugin is enabled the runtime takeover above points it at `ddg`.
|
|
440
451
|
|
|
441
|
-
|
|
452
|
+
> If you previously added the snippet below by hand to `profiles/<profile>/cordis.patch.yml` *and* the plugin is not enabled, you will see that error — **just remove the snippet** (or re-enable the plugin):
|
|
442
453
|
|
|
443
454
|
```yaml
|
|
444
|
-
#
|
|
445
|
-
# Since DSH 0.1.2 a patch REPLACES the whole entry config
|
|
446
|
-
# restate any existing web.* fields (e.g. fetchProvider) or they are dropped.
|
|
455
|
+
# Optional; normally unnecessary — only to force a specific provider.
|
|
456
|
+
# Since DSH 0.1.2 a patch REPLACES the whole entry config: keep fetchProvider.
|
|
447
457
|
- id: web
|
|
448
458
|
config:
|
|
449
459
|
searchProvider: ddg
|
|
450
460
|
fetchProvider: http
|
|
451
461
|
```
|
|
452
462
|
|
|
453
|
-
- `searchProvider: ddg` —
|
|
454
|
-
- `fetchProvider: http` — the official web-fetch provider; keep it
|
|
455
|
-
- To go back to official search: disable this plugin's entry in the plugin manager.
|
|
463
|
+
- `searchProvider: ddg` — this plugin's **fixed provider id**, not "use the DuckDuckGo engine"; the engine is chosen by the `provider` field in settings (`bing` / `baidu` / `auto` / …).
|
|
464
|
+
- `fetchProvider: http` — the official web-fetch provider; keep it when declaring the row by hand.
|
|
465
|
+
- To go back to official search: disable this plugin's entry in the plugin manager — no config edit needed.
|
|
456
466
|
|
|
457
467
|
#### Sister Plugin: dsh-preset-workbench
|
|
458
468
|
|
package/cordis.patch.yml
CHANGED
|
@@ -10,10 +10,20 @@
|
|
|
10
10
|
# A legacy ~/.dsh/settings.yaml "free-search:" section is migrated into this
|
|
11
11
|
# entry's config once at startup (see README).
|
|
12
12
|
# Failed/empty engines automatically fall back to the next free engine.
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
13
|
+
#
|
|
14
|
+
# NOTE (2026-10, issue #51): this bundle does NOT touch `web.searchProvider`.
|
|
15
|
+
# The plugin takes the search provider over at runtime instead — when the field
|
|
16
|
+
# is unset or still the shipped default (`deepseek-official`) it points it at its
|
|
17
|
+
# own provider id (`ddg`); when another provider was chosen explicitly it leaves
|
|
18
|
+
# it alone and only warns.
|
|
19
|
+
#
|
|
20
|
+
# A static `- id: web config: searchProvider: ddg` used to live here. It was
|
|
21
|
+
# removed because it created a dangling reference: a patch replaces the whole
|
|
22
|
+
# row config, so rows that came from a *disabled* or skipped plugin left
|
|
23
|
+
# `searchProvider: ddg` behind while nothing registered `ddg` — every web search
|
|
24
|
+
# then failed with `configured web provider "ddg" is not registered`. Leaving the
|
|
25
|
+
# base default (`deepseek-official`) in place means a disabled plugin degrades to
|
|
26
|
+
# the official provider instead of breaking search outright.
|
|
17
27
|
- insert:
|
|
18
28
|
- id: web-search-free
|
|
19
29
|
name: dsh-free-search
|
|
@@ -21,10 +31,3 @@
|
|
|
21
31
|
provider: bing
|
|
22
32
|
disabledEngines: []
|
|
23
33
|
bingMarket: zh-CN
|
|
24
|
-
# 自动接管默认搜索:让 harness 的 web_search 直接用本插件的 provider(内部含全部引擎)
|
|
25
|
-
# 注意 patch 语义是"整行替换 config"(DSH 0.1.2+):必须保留 fetchProvider,否则
|
|
26
|
-
# web-fetch-http 会被另一层重新注册,与 base bundle 的注册造成 duplicate loader entry。
|
|
27
|
-
- id: web
|
|
28
|
-
config:
|
|
29
|
-
searchProvider: ddg
|
|
30
|
-
fetchProvider: http
|
package/lib/client.js
CHANGED
|
@@ -290,7 +290,7 @@ window.__ModuleLoader__.load({
|
|
|
290
290
|
};
|
|
291
291
|
const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
|
|
292
292
|
// 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
|
|
293
|
-
const PLUGIN_VERSION = "0.6.
|
|
293
|
+
const PLUGIN_VERSION = "0.6.7";
|
|
294
294
|
const ENGINES = [
|
|
295
295
|
{ id: "auto", label: "Auto · 智能路由", badge: "AUTO", link: null },
|
|
296
296
|
{ id: "multi", label: "Multi Search · 多引擎", badge: "MULTI", link: null },
|
|
@@ -647,7 +647,7 @@ window.__ModuleLoader__.load({
|
|
|
647
647
|
setTestResult({
|
|
648
648
|
ok: true,
|
|
649
649
|
count: sources.length,
|
|
650
|
-
engine: result.value.provider ?? provider,
|
|
650
|
+
engine: result.value.provider === "multi_search" ? "multi" : (result.value.provider ?? provider),
|
|
651
651
|
content: result.value.content ?? "",
|
|
652
652
|
sample: sources[0]?.title ?? "",
|
|
653
653
|
});
|
package/lib/index.js
CHANGED
|
@@ -61,6 +61,9 @@ const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "
|
|
|
61
61
|
// 支持 time_range 过滤的引擎
|
|
62
62
|
const TIME_ENGINES = ["tavily", "exa", "keenable", "firecrawl", "parallel", "searxng", "ddg", "ddg-lite", "baidu", "doubao"];
|
|
63
63
|
|
|
64
|
+
// 虚拟搜索模式:不是单个引擎,而是 provider.search 内部的路由/并发策略
|
|
65
|
+
const SEARCH_MODES = ["auto", "multi"];
|
|
66
|
+
|
|
64
67
|
const DEFAULT_FALLBACK_ORDER = ["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official", "you", "baidu", "kimi", "aliyun", "doubao", "bing", "anysearch", "ddg", "ddg-lite", "searxng"];
|
|
65
68
|
|
|
66
69
|
function normalizeDisabledEngines(value) {
|
|
@@ -118,7 +121,7 @@ function routeEngines(query, { timeRange, disabledEngines = [], fallbackOrder =
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
// 当前插件版本(发布时与 package.json 同步)
|
|
121
|
-
const PLUGIN_VERSION = "0.6.
|
|
124
|
+
const PLUGIN_VERSION = "0.6.7";
|
|
122
125
|
// 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
|
|
123
126
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
|
|
124
127
|
const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
|
|
@@ -2036,8 +2039,11 @@ function makeBridgeRoutes(settings, search, testEngine, getCredentials) {
|
|
|
2036
2039
|
}
|
|
2037
2040
|
const maxResults = Math.min(Math.max(Number(request.maxResults) || 5, 1), 10);
|
|
2038
2041
|
const timeRange = parseTimeRange(request.timeRange);
|
|
2039
|
-
// 指定 engine
|
|
2040
|
-
|
|
2042
|
+
// 指定 engine:直测该引擎本身(不走回退链),报告它自己的可用性。
|
|
2043
|
+
// auto / multi 是虚拟模式,没有"单引擎本身"可测 → 交给下面的 provider.search,
|
|
2044
|
+
// 真实跑一遍路由/并发链路(否则设置页「测试引擎」会对它们报 unknown engine)。
|
|
2045
|
+
const engineIsMode = typeof request.engine === "string" && SEARCH_MODES.includes(request.engine);
|
|
2046
|
+
if (typeof request.engine === "string" && request.engine.length > 0 && !engineIsMode) {
|
|
2041
2047
|
if (typeof testEngine !== "function") {
|
|
2042
2048
|
return { ok: false, code: "search-unavailable", message: "engine test is not wired" };
|
|
2043
2049
|
}
|
|
@@ -3056,7 +3062,7 @@ function apply(ctx, config) {
|
|
|
3056
3062
|
parameters: {
|
|
3057
3063
|
engines: {
|
|
3058
3064
|
type: "array",
|
|
3059
|
-
description: "Which engines to test (default: all). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao.",
|
|
3065
|
+
description: "Which engines to test (default: all enabled engines). Options: ddg, ddg-lite, bing, searxng, anysearch, exa, tavily, keenable, firecrawl, parallel, perplexity, serpbase, deepseek-official, you, baidu, kimi, aliyun, doubao; the virtual modes auto and multi are also accepted and run the real routing/concurrent path.",
|
|
3060
3066
|
items: { type: "string" },
|
|
3061
3067
|
},
|
|
3062
3068
|
query: {
|
|
@@ -3089,7 +3095,8 @@ function apply(ctx, config) {
|
|
|
3089
3095
|
render(args, value) {
|
|
3090
3096
|
const lines = value.results.map((r) => {
|
|
3091
3097
|
if (r.status === "ok") {
|
|
3092
|
-
|
|
3098
|
+
const via = r.engineUsed && r.engineUsed !== r.engine ? ` via ${r.engineUsed}` : "";
|
|
3099
|
+
return `- ${r.engine}: OK (${r.results} results${via}${r.sampleTitle ? `, e.g. "${r.sampleTitle.slice(0, 40)}"` : ""})`;
|
|
3093
3100
|
}
|
|
3094
3101
|
if (r.status === "disabled") return `- ${r.engine}: DISABLED`;
|
|
3095
3102
|
return `- ${r.engine}: FAIL - ${r.error}`;
|
|
@@ -3103,7 +3110,7 @@ function apply(ctx, config) {
|
|
|
3103
3110
|
const disabled = new Set(normalizeDisabledEngines(current().disabledEngines));
|
|
3104
3111
|
const explicit = Array.isArray(args.engines) && args.engines.length > 0;
|
|
3105
3112
|
const engines = explicit
|
|
3106
|
-
? args.engines.filter((engine) => ALL_ENGINES.includes(engine))
|
|
3113
|
+
? args.engines.filter((engine) => ALL_ENGINES.includes(engine) || SEARCH_MODES.includes(engine))
|
|
3107
3114
|
: enabledEngines(current().disabledEngines, current().fallbackOrder);
|
|
3108
3115
|
const results = [];
|
|
3109
3116
|
for (const engine of engines) {
|
|
@@ -3111,6 +3118,25 @@ function apply(ctx, config) {
|
|
|
3111
3118
|
results.push({ engine, status: "disabled" });
|
|
3112
3119
|
continue;
|
|
3113
3120
|
}
|
|
3121
|
+
// 虚拟模式(auto/multi):走 provider.search 真实路由/并发,报告实际生效的引擎
|
|
3122
|
+
if (SEARCH_MODES.includes(engine)) {
|
|
3123
|
+
try {
|
|
3124
|
+
const r = await provider.search({ query: args.query, maxResults: 2, engine });
|
|
3125
|
+
const sources = r.sources ?? [];
|
|
3126
|
+
if (sources.length === 0) {
|
|
3127
|
+
results.push({ engine, status: "fail", error: "0 results" });
|
|
3128
|
+
} else {
|
|
3129
|
+
const item = { engine, status: "ok", results: sources.length, engineUsed: r.provider ?? r.engine ?? engine };
|
|
3130
|
+
if (sources[0]?.title) item.sampleTitle = String(sources[0].title);
|
|
3131
|
+
if (sources[0]?.url) item.sampleUrl = String(sources[0].url);
|
|
3132
|
+
if (r.content) item.note = String(r.content);
|
|
3133
|
+
results.push(item);
|
|
3134
|
+
}
|
|
3135
|
+
} catch (error) {
|
|
3136
|
+
results.push({ engine, status: "fail", error: error instanceof Error ? error.message : String(error) });
|
|
3137
|
+
}
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3114
3140
|
const r = await runEngineTest(engine, args.query);
|
|
3115
3141
|
if (r.ok) {
|
|
3116
3142
|
const item = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-free-search",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Free web search for DeepSeek Harness: 18 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key) + auto smart routing + global engine enable/disable & fallback priority + multi mode (parallel top-3 merge) + multi_search cross-source tool + time filtering + platform search + web_fetch, with web settings UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|