dsh-free-search 0.4.36 → 0.4.37

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 CHANGED
@@ -30,7 +30,7 @@ dsh 默认的搜索 provider 依赖 DeepSeek 官方 API key(`DEEPSEEK_API_KEY`
30
30
 
31
31
  - **零成本** —— 多个免费引擎,无需 key、无需注册
32
32
  - **多引擎可选**:DuckDuckGo(html/lite)、Bing、SearXNG(元搜索,支持自定义实例)、AnySearch、Exa、Tavily、Keenable、Firecrawl、Parallel、Perplexity、SerpBase、DeepSeek 官方
33
- - **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")+ 中英文切换;DSH 0.1.6-alpha.2+ 入口在左侧「插件」页的组件行配置,旧版在「设置 → 插件 → 可配置」
33
+ - **网页设置页** —— 引擎切换 + API key 配置(UI 中 key 脱敏显示"已配置")+ 中英文切换;入口在左侧「插件」页的组件行配置(`plugins.row.config`,DSH 0.1.7-rc.1+)
34
34
  - **弹出式切换命令** —— 聊天框输入 `/free-search-engine`,弹出引擎选择窗口,点选即切换(等效设置页 + 保存)
35
35
  - **引擎测试** —— `free_search_test` 工具让 agent 一键测试所有引擎;设置页也有"测试引擎"按钮(直测当前引擎,不走回退链,付费引擎无 key 会明确报错)
36
36
  - **统一引擎回退** —— 任何引擎失败(付费/免费,缺 key/401/限流/网络)自动轮流尝试下一个引擎:首选引擎 → 其他引擎(exa/tavily/keenable/firecrawl/parallel 无 key 也会尝试,因为它们自带 keyless 免费额度)→ 剩余免费引擎,搜索永不直接失败;结果顶部注明实际生效的引擎(如 `Note: perplexity unavailable or failed, using exa.`)
@@ -114,10 +114,9 @@ dsh web
114
114
 
115
115
  #### 网页设置(推荐)
116
116
 
117
- 安装后按 DSH 版本打开配置页:
117
+ 安装后打开配置页(DSH 0.1.7-rc.1+):
118
118
 
119
- - **DSH 0.1.6-alpha.2 及以上**:左侧 **插件** 页 → **已安装** 分组 → `free-search` → 点击组件行 `web-search-free`(行内"配置"入口)
120
- - **DSH 0.1.5 及更早**:**设置 → 插件 → 可配置** 标签页 → **Free Search** 卡片
119
+ - 左侧 **插件** 页 → **已安装** 分组 → `free-search` → 点击组件行 `web-search-free`(行内"配置"入口)
121
120
 
122
121
  配置页提供:
123
122
 
@@ -156,24 +155,24 @@ dsh web
156
155
 
157
156
  #### 配置文件
158
157
 
159
- 配置存在 `~/.dsh/settings.yaml`:
158
+ DSH 0.1.7-rc.1 起,配置跟随 profile 的插件条目保存:设置页与 `/free-search-engine` 都会写入当前 profile 的 `cordis.patch.yml` 中 `web-search-free`(`dsh-free-search`)条目的 `config`。旧版 `~/.dsh/settings.yaml` 的 `free-search:` 段只会在启动时自动导入一次,随后原文件被重命名为 `settings.yaml.imported`。
160
159
 
161
160
  ```yaml
162
- free-search:
163
- provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
164
- lang: zh # 设置页界面语言(zh / en)
165
- bingMarket: zh-CN # Bing 市场
166
- region: cn-zh # DuckDuckGo 区域(可选)
167
- searxngInstances: # 自定义 SearXNG 实例(可选)
168
- - https://your-instance.example
169
- exaApiKey: ... # 或通过设置页填写
170
- tavilyApiKey: ... # 或通过设置页填写
171
- keenableApiKey: ... # 或通过设置页填写
172
- firecrawlApiKey: ... # 或通过设置页填写
173
- parallelApiKey: ... # 或通过设置页填写
174
- perplexityApiKey: ...
175
- serpbaseApiKey: ... # 或通过设置页填写
176
- deepseekApiKey: ...
161
+ # profiles/<profile>/cordis.patch.yml 中该条目的 config:
162
+ provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
163
+ lang: zh # 设置页界面语言(zh / en)
164
+ bingMarket: zh-CN # Bing 市场
165
+ region: cn-zh # DuckDuckGo 区域(可选)
166
+ searxngInstances: # 自定义 SearXNG 实例(可选)
167
+ - https://your-instance.example
168
+ exaApiKey: ... # 或通过设置页填写
169
+ tavilyApiKey: ... # 或通过设置页填写
170
+ keenableApiKey: ... # 或通过设置页填写
171
+ firecrawlApiKey: ... # 或通过设置页填写
172
+ parallelApiKey: ... # 或通过设置页填写
173
+ perplexityApiKey: ...
174
+ serpbaseApiKey: ... # 或通过设置页填写
175
+ deepseekApiKey: ...
177
176
  ```
178
177
 
179
178
  #### 让 agent 测试所有引擎
@@ -254,7 +253,7 @@ Search engine test:
254
253
 
255
254
  切换后重启 `dsh web` 生效。
256
255
 
257
- > 配置卡片挂在官方设置页的 `settings.plugin.item` 插槽(dsh 自带),配置读写走插件自建 bridge,**不依赖 dsh-web-ui**,插件可独立使用。
256
+ > 配置卡片挂在左侧「插件」页的 `plugins.row.config` 行配置插槽(dsh 自带),配置读写走插件自建 bridge,**不依赖 dsh-web-ui**,插件可独立使用。
258
257
 
259
258
  ### 代理说明(国内用户)
260
259
 
@@ -270,8 +269,8 @@ Windows 用户:桌面快捷方式已内置此配置(`set NODE_USE_ENV_PROXY=
270
269
 
271
270
  ### 工作原理
272
271
 
273
- - `lib/index.js`:host 端。实现 `WebSearchProvider`(`id` / `available()` / `search()`),统一引擎路由 + 自动回退(付费引擎优先,免费兜底);解析 `timeRange`(固定档/相对值/绝对日期)并透传给各引擎;注册 `free-search` settings namespace;提供 `/api/dsh-free-search-settings` 读写桥 + `raw-search` 调试接口;注册 `free_search_test`、`platform_search`、`advanced_search` 工具;动态注入引擎清单到系统提示词(设置变更时自动刷新)。
274
- - `lib/client.js`:浏览器端。React 配置卡片(引擎选择 + key 输入 + 连通测试 + 中英切换),挂载到官方设置页的 `settings.plugin.item` 插槽;注册 `/free-search-engine` 弹出式切换命令(`commandUi` popupSelect,与 `/model` 同机制)。
272
+ - `lib/index.js`:host 端。实现 `WebSearchProvider`(`id` / `available()` / `search()`),统一引擎路由 + 自动回退(付费引擎优先,免费兜底);解析 `timeRange`(固定档/相对值/绝对日期)并透传给各引擎;在 `web-search-free` 条目上声明可编辑配置(`.volatile()`)并自带设置页(`plugins.row.config`);提供 `/api/dsh-free-search-settings` 读写桥 + `raw-search` 调试接口;注册 `free_search_test`、`platform_search`、`advanced_search` 工具;动态注入引擎清单到系统提示词(设置变更时自动刷新)。
273
+ - `lib/client.js`:浏览器端。React 配置卡片(引擎选择 + key 输入 + 连通测试 + 中英切换),挂载到左侧「插件」页的 `plugins.row.config` 行配置插槽;注册 `/free-search-engine` 弹出式切换命令(`commandUi` popupSelect,与 `/model` 同机制)。
275
274
  - `cordis.patch.yml`:插件 loader 配置。
276
275
 
277
276
  ---
@@ -300,7 +299,7 @@ This plugin provides multiple free search engines with automatic fallback, compl
300
299
 
301
300
  - **Zero Cost** — Multiple free engines with no API key or registration required
302
301
  - **Multi-Engine Support** — DuckDuckGo (HTML / Lite), Bing, AnySearch AI, SearXNG (meta-search with custom instances), Exa, Tavily, Keenable, Firecrawl, Parallel, Perplexity, SerpBase, and DeepSeek Official
303
- - **Web Settings UI** — Engine switching, API key configuration (keys masked as "configured" in the UI), and a Chinese/English toggle; on DSH 0.1.6-alpha.2+ the entry is the component-row config on the sidebar Plugins page, on older versions Settings → Plugins → Configurable
302
+ - **Web Settings UI** — Engine switching, API key configuration (keys masked as "configured" in the UI), and a Chinese/English toggle; the entry is the component-row config (`plugins.row.config`) on the sidebar Plugins page (DSH 0.1.7-rc.1+)
304
303
  - **Popup Switch Command** — Type `/free-search-engine` in the chat: a picker opens with all engines; click one to switch (equivalent to the settings page + save)
305
304
  - **Engine Testing** — `free_search_test` for the agent to check all engines in one call; the settings UI also has a "Test engine" button that tests the selected engine directly (no fallback chain; paid engines without a key report an explicit error)
306
305
  - **Unified Engine Fallback** — Any engine failure (paid or free, missing key, 401, rate limit, network error) automatically tries the next engine: the configured engine first, then other engines (exa/tavily/keenable/firecrawl/parallel are tried even without a key because they have built-in keyless quota), then the remaining free engines (Bing/AnySearch etc.) — with a note attached to the results naming the engine that actually served them (e.g. `Note: perplexity unavailable or failed, using exa.`). Search never fails outright.
@@ -384,10 +383,9 @@ This plugin intentionally specifies `@deepseek-ai/dsh-settings` and `@deepseek-a
384
383
 
385
384
  #### Web Settings (Recommended)
386
385
 
387
- After installation, open the config page according to your DSH version:
386
+ After installation, open the config page (DSH 0.1.7-rc.1+):
388
387
 
389
- - **DSH 0.1.6-alpha.2 and later**: sidebar **Plugins** page → **Installed** group → `free-search` → click the `web-search-free` component row (the row's "configure" entry)
390
- - **DSH 0.1.5 and earlier**: **Settings → Plugins → Configurable** tab → **Free Search** card
388
+ - Sidebar **Plugins** page → **Installed** group → `free-search` → click the `web-search-free` component row (the row's "configure" entry)
391
389
 
392
390
  The config page provides:
393
391
 
@@ -426,24 +424,24 @@ The command only changes the preferred engine; search still goes through `web_se
426
424
 
427
425
  #### Configuration File
428
426
 
429
- Configuration is stored in `~/.dsh/settings.yaml`:
427
+ Since DSH 0.1.7-rc.1 the configuration is stored with the profile's plugin entry: the settings page and `/free-search-engine` both write the `config` of the `web-search-free` (`dsh-free-search`) entry in the active profile's `cordis.patch.yml`. The old `free-search:` section of `~/.dsh/settings.yaml` is imported once at startup; the file is then renamed to `settings.yaml.imported`.
430
428
 
431
429
  ```yaml
432
- free-search:
433
- provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
434
- lang: zh # settings UI language (zh / en)
435
- bingMarket: zh-CN # Bing market
436
- region: cn-zh # DuckDuckGo region (optional)
437
- searxngInstances: # Custom SearXNG instances (optional)
438
- - https://your-instance.example
439
- exaApiKey: ... # Or configure via the web settings UI
440
- tavilyApiKey: ... # Or configure via the web settings UI
441
- keenableApiKey: ... # Or configure via the web settings UI
442
- firecrawlApiKey: ... # Or configure via the web settings UI
443
- parallelApiKey: ... # Or configure via the web settings UI
444
- perplexityApiKey: ...
445
- serpbaseApiKey: ... # Or configure via the web settings UI
446
- deepseekApiKey: ...
430
+ # config of that entry in profiles/<profile>/cordis.patch.yml:
431
+ provider: bing # ddg / ddg-lite / bing / searxng / anysearch / exa / tavily / keenable / firecrawl / parallel / perplexity / serpbase / deepseek-official
432
+ lang: zh # settings UI language (zh / en)
433
+ bingMarket: zh-CN # Bing market
434
+ region: cn-zh # DuckDuckGo region (optional)
435
+ searxngInstances: # Custom SearXNG instances (optional)
436
+ - https://your-instance.example
437
+ exaApiKey: ... # Or configure via the web settings UI
438
+ tavilyApiKey: ... # Or configure via the web settings UI
439
+ keenableApiKey: ... # Or configure via the web settings UI
440
+ firecrawlApiKey: ... # Or configure via the web settings UI
441
+ parallelApiKey: ... # Or configure via the web settings UI
442
+ perplexityApiKey: ...
443
+ serpbaseApiKey: ... # Or configure via the web settings UI
444
+ deepseekApiKey: ...
447
445
  ```
448
446
 
449
447
  #### Asking the Agent to Test All Engines
@@ -524,7 +522,7 @@ The `tools/` directory includes a lightweight, zero-dependency switcher:
524
522
 
525
523
  Restart `dsh web` after switching to apply changes.
526
524
 
527
- > The settings card mounts into the official `settings.plugin.item` slot (built into DSH), and configuration reads/writes go through the plugin's own bridge. **No `dsh-web-ui` dependency — the plugin can be used standalone.**
525
+ > The settings card mounts into the official `plugins.row.config` component-row slot on the sidebar Plugins page (built into DSH), and configuration reads/writes go through the plugin's own bridge. **No `dsh-web-ui` dependency — the plugin can be used standalone.**
528
526
 
529
527
  ### Proxy Note (for Users in Mainland China)
530
528
 
@@ -540,8 +538,8 @@ Windows users: The desktop shortcut already includes this configuration (`set NO
540
538
 
541
539
  ### How It Works
542
540
 
543
- - `lib/index.js`: Host side. Implements `WebSearchProvider` (`id` / `available()` / `search()`), unified engine routing + auto-fallback (paid engines first, free as fallback); parses `timeRange` (fixed tiers / relative values / absolute dates) and forwards it to each engine; registers the `free-search` settings namespace; provides the `/api/dsh-free-search-settings` read/write bridge + `raw-search` debug endpoint; registers the `free_search_test`, `platform_search`, and `advanced_search` tools; dynamically injects the engine list into system prompts (auto-refreshes on settings change).
544
- - `lib/client.js`: Browser side. React configuration card (engine select, key inputs, connectivity test, and Chinese/English toggle), mounted into the official `settings.plugin.item` slot; registers the `/free-search-engine` popup switch command (`commandUi` popupSelect, the same mechanism as `/model`).
541
+ - `lib/index.js`: Host side. Implements `WebSearchProvider` (`id` / `available()` / `search()`), unified engine routing + auto-fallback (paid engines first, free as fallback); parses `timeRange` (fixed tiers / relative values / absolute dates) and forwards it to each engine; declares its editable config as volatile fields on the `web-search-free` composition entry and ships its own settings page (`plugins.row.config`); provides the `/api/dsh-free-search-settings` read/write bridge + `raw-search` debug endpoint; registers the `free_search_test`, `platform_search`, and `advanced_search` tools; dynamically injects the engine list into system prompts (auto-refreshes on settings change).
542
+ - `lib/client.js`: Browser side. React configuration card (engine select, key inputs, connectivity test, and Chinese/English toggle), mounted into the official `plugins.row.config` component-row slot on the sidebar Plugins page; registers the `/free-search-engine` popup switch command (`commandUi` popupSelect, the same mechanism as `/model`).
545
543
  - `cordis.patch.yml`: Plugin loader configuration.
546
544
 
547
545
  ### License
package/lib/client.js CHANGED
@@ -83,7 +83,10 @@ window.__ModuleLoader__.load({
83
83
  //#endregion
84
84
 
85
85
  const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
86
- const NS = "free-search";
86
+ // rc.1: the settings namespace is the profile composition entry id (the
87
+ // `web-search-free` row declared by cordis.patch.yml), not the old `free-search`
88
+ // section name. Keep in sync with FREE_SEARCH_NS in lib/index.js.
89
+ const NS = "web-search-free";
87
90
  const I18N = {
88
91
  zh: {
89
92
  description: "免费搜索 —— 无需 API key(Bing / DuckDuckGo / AnySearch / Exa / Tavily / Keenable / Firecrawl / Parallel)",
@@ -106,7 +109,7 @@ window.__ModuleLoader__.load({
106
109
  keyStorageCred: "凭据中心(推荐)",
107
110
  keyStorageSettings: "设置页(兼容)",
108
111
  keyStorageCredHint: (c) => `保存后写入 ~/.dsh/.credentials.yaml(最高优先级)。当前已配置:${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "无"}`,
109
- keyStorageSettingsHint: "保存后写入 settings.yaml(向后兼容路径,优先级低于凭据中心)。",
112
+ keyStorageSettingsHint: "保存后写入当前 profile 的插件条目 config(cordis.patch.yml,优先级低于凭据中心)。",
110
113
  platformSearch: "平台搜索(platform_search 工具)",
111
114
  platformHint: "为 agent 的 platform_search 工具启用平台。禁用的平台会被跳过。",
112
115
  cacheTtl: "结果缓存时长(分钟)",
@@ -173,7 +176,7 @@ window.__ModuleLoader__.load({
173
176
  keyStorageCred: "Credential center (recommended)",
174
177
  keyStorageSettings: "Settings page (legacy)",
175
178
  keyStorageCredHint: (c) => `Saved to ~/.dsh/.credentials.yaml (highest priority). Currently configured: ${["exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek"].filter((k) => c[k]).map((k) => k.toUpperCase()).join(", ") || "none"}`,
176
- keyStorageSettingsHint: "Saved to settings.yaml (backward-compatible path; lower priority than the credential center).",
179
+ keyStorageSettingsHint: "Saved to the active profile plugin entry config (cordis.patch.yml; lower priority than the credential center).",
177
180
  platformSearch: "Platform search (platform_search tool)",
178
181
  platformHint: "Enable platforms for the agent's platform_search tool. Disabled platforms are skipped.",
179
182
  cacheTtl: "Result cache TTL (minutes)",
@@ -222,7 +225,7 @@ window.__ModuleLoader__.load({
222
225
  };
223
226
  const tt = (lang) => I18N[lang === "en" ? "en" : "zh"];
224
227
  // 当前插件版本(与 lib/index.js 的 PLUGIN_VERSION 保持一致)
225
- const PLUGIN_VERSION = "0.4.36";
228
+ const PLUGIN_VERSION = "0.4.37";
226
229
  const ENGINES = [
227
230
  { id: "ddg", label: "DuckDuckGo · HTML", badge: "FREE", link: "https://duckduckgo.com" },
228
231
  { id: "ddg-lite", label: "DuckDuckGo · Lite", badge: "FREE", link: "https://duckduckgo.com" },
@@ -466,7 +469,7 @@ window.__ModuleLoader__.load({
466
469
  ops.push({ op: "set", path: ["safeSearch"], value: safeSearch });
467
470
  ops.push({ op: "set", path: ["bingMarket"], value: bingMarket });
468
471
  if (keyStorage !== "credentials") {
469
- // settings 模式:key 仍写 settings.yaml(旧行为)
472
+ // settings 模式:key 写入当前 profile 的插件条目 config(cordis.patch.yml)
470
473
  for (const [field, value] of keyFields) {
471
474
  if (value.trim()) ops.push({ op: "set", path: [field], value: value.trim() });
472
475
  }
@@ -1070,22 +1073,10 @@ react_jsx_runtime.jsx("div", {
1070
1073
  const inject = ["slots", "commandUi"];
1071
1074
 
1072
1075
  function apply(ctx) {
1073
- // 挂官方插槽 settings.plugin.item(设置 → 插件 → 可配置标签页)。
1074
- // 不依赖 dsh-web-ui:配置读写走自建 bridge(/api/dsh-free-search-settings)。
1075
- ctx.slots.inject("settings.plugin.item", () =>
1076
- ctx.slots.register(
1077
- {
1078
- name: "settings.plugin.item",
1079
- key: "free-search",
1080
- id: "dsh-free-search",
1081
- order: 120,
1082
- inject: () => ({}),
1083
- },
1084
- FreeSearchCard
1085
- )
1086
- );
1087
- // 挂 0.1.6-alpha.2+ 的插件页 slot:侧栏 插件 → 已安装 → free-search → 行配置页。
1076
+ // 挂插件页 slot:侧栏 插件 → 已安装 → free-search → 行配置页。
1077
+ // rc.1 废弃了旧版设置页的插件配置 slot,只保留 plugins.row.config;
1088
1078
  // key 规则见 ui-plugin-manager 的 rowConfigKey:<包名>#<patch 里声明的行 id>。
1079
+ // 配置读写走自建 bridge(/api/dsh-free-search-settings),不依赖 dsh-web-ui。
1089
1080
  ctx.slots.inject("plugins.row.config", () =>
1090
1081
  ctx.slots.register(
1091
1082
  {
package/lib/index.js CHANGED
@@ -46,13 +46,16 @@ const MARKET_TO_LANG = {
46
46
  "ko-KR": "ko-KR,ko;q=0.9,en;q=0.8",
47
47
  };
48
48
 
49
- const FREE_SEARCH_NS = "free-search";
49
+ // rc.1: settings moved into the profile-owned plugin Config. The namespace is the
50
+ // composition entry id (the `web-search-free` row the bundle's cordis.patch.yml
51
+ // declares), not the old `$DSH_HOME/settings.yaml` section name.
52
+ const FREE_SEARCH_NS = "web-search-free";
50
53
  const BRIDGE_PREFIX = "/api/dsh-free-search-settings";
51
54
  const FREE_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch"];
52
55
  const ALL_ENGINES = ["ddg", "ddg-lite", "bing", "searxng", "anysearch", "exa", "tavily", "keenable", "firecrawl", "parallel", "perplexity", "serpbase", "deepseek-official"];
53
56
 
54
57
  // 当前插件版本(发布时与 package.json 同步)
55
- const PLUGIN_VERSION = "0.4.36";
58
+ const PLUGIN_VERSION = "0.4.37";
56
59
  // 检查更新的 npm registry 元数据地址(dsh-free-search 是 npmjs 上的公开包)
57
60
  const NPM_REGISTRY_URL = "https://registry.npmjs.org/dsh-free-search/latest";
58
61
  const PLUGIN_NPM_URL = "https://www.npmjs.com/package/dsh-free-search";
@@ -1785,29 +1788,44 @@ const KEY_REF_MAP = {
1785
1788
  deepseekApiKey: "DEEPSEEK_API_KEY",
1786
1789
  };
1787
1790
 
1791
+ // rc.1 declares editable fields `.volatile()`: the Loader hands `apply` live
1792
+ // references and commits profile edits in place without remounting. `resolveConfig`
1793
+ // unwraps those references so every read sees the latest accepted value.
1794
+ // `.volatile()` requires the scoped `@deepseek-ai/schemastery` (>= 3.18.2).
1788
1795
  const Config = z.object({
1789
- provider: z.string().default("bing"),
1790
- cache: z.boolean().default(true), // 单 query 结果缓存开关(防限流/省额度)
1791
- cacheTtl: z.number().default(5), // 缓存时长(分钟),0-5 可配置(使用处再 clamp)
1792
- keyStorage: z.string().default("credentials"), // key 存储位置:credentials(凭据中心)| settings(设置页)
1793
- lang: z.string().default("zh"),
1794
- region: z.string(),
1795
- bingMarket: z.string().default("zh-CN"),
1796
- safeSearch: z.string().default("off"),
1797
- searxngInstances: z.array(z.string()),
1798
- platforms: z.array(z.string()).default(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]),
1799
- exaApiKey: z.string().role("secret"),
1800
- tavilyApiKey: z.string().role("secret"),
1801
- keenableApiKey: z.string().role("secret"),
1802
- firecrawlApiKey: z.string().role("secret"),
1803
- parallelApiKey: z.string().role("secret"),
1804
- perplexityApiKey: z.string().role("secret"),
1805
- serpbaseApiKey: z.string().role("secret"),
1806
- deepseekApiKey: z.string().role("secret"),
1796
+ provider: z.string().default("bing").volatile(),
1797
+ cache: z.boolean().default(true).volatile(), // 单 query 结果缓存开关(防限流/省额度)
1798
+ cacheTtl: z.number().default(5).volatile(), // 缓存时长(分钟),0-5 可配置(使用处再 clamp)
1799
+ keyStorage: z.string().default("credentials").volatile(), // key 存储位置:credentials(凭据中心)| settings(设置页)
1800
+ lang: z.string().default("zh").volatile(),
1801
+ region: z.string().volatile(),
1802
+ bingMarket: z.string().default("zh-CN").volatile(),
1803
+ safeSearch: z.string().default("off").volatile(),
1804
+ searxngInstances: z.array(z.string()).volatile(),
1805
+ platforms: z.array(z.string()).default(["github", "v2ex", "bilibili", "reddit", "hn", "stackoverflow", "wikipedia", "npm"]).volatile(),
1806
+ exaApiKey: z.string().role("secret").volatile(),
1807
+ tavilyApiKey: z.string().role("secret").volatile(),
1808
+ keenableApiKey: z.string().role("secret").volatile(),
1809
+ firecrawlApiKey: z.string().role("secret").volatile(),
1810
+ parallelApiKey: z.string().role("secret").volatile(),
1811
+ perplexityApiKey: z.string().role("secret").volatile(),
1812
+ serpbaseApiKey: z.string().role("secret").volatile(),
1813
+ deepseekApiKey: z.string().role("secret").volatile(),
1807
1814
  });
1808
1815
 
1816
+ // Unwrap the live `Volatile<T>` references the Loader passes for `.volatile()`
1817
+ // fields into a plain object, once per read.
1818
+ function resolveConfig(config) {
1819
+ if (config === null || typeof config !== "object") return {};
1820
+ const out = {};
1821
+ for (const [key, value] of Object.entries(config)) {
1822
+ out[key] = value !== null && typeof value === "object" && typeof value.get === "function" ? value.get() : value;
1823
+ }
1824
+ return out;
1825
+ }
1826
+
1809
1827
  function apply(ctx, config) {
1810
- let current = () => config ?? {};
1828
+ const current = () => resolveConfig(config);
1811
1829
  const logger = ctx.logger;
1812
1830
  // credentials 服务可能晚于本插件挂载(跨 bundle 顺序),运行期动态获取而不是 apply 时缓存
1813
1831
  const getCredentials = () => ctx.get("credentials");
@@ -2035,37 +2053,16 @@ function apply(ctx, config) {
2035
2053
  };
2036
2054
 
2037
2055
  ctx.inject(["settings"], (sctx) => {
2038
- // alpha.2+(SettingsProvider 有 installSection 方法)与 rc.2(模块级 installSettingsSection)双兼容:
2039
- // 特性检测优先用 alpha 方法;缺失时动态 import 旧 API 注册(rc.2 环境,不会触发 alpha 的缺失导出报错)。
2040
- if (typeof sctx.settings.installSection === "function") {
2041
- sctx.settings.installSection(ctx, FREE_SEARCH_NS, Config, config ?? {}, {
2042
- setSource: (source) => {
2043
- current = source;
2044
- },
2045
- onChange: () => {
2046
- // settings 变更时刷新系统提示词(显示最新引擎)
2047
- if (typeof refreshPrompt === "function") refreshPrompt();
2048
- },
2049
- });
2050
- } else {
2051
- void (async () => {
2052
- const legacy = await import("@deepseek-ai/dsh-settings");
2053
- if (typeof legacy.installSettingsSection === "function" && legacy.settingsNamespace) {
2054
- const legacyNs = legacy.settingsNamespace(FREE_SEARCH_NS);
2055
- legacy.installSettingsSection(ctx, legacyNs, Config, config ?? {}, {
2056
- setSource: (source) => {
2057
- current = source;
2058
- },
2059
- onChange: () => {
2060
- if (typeof refreshPrompt === "function") refreshPrompt();
2061
- },
2062
- });
2063
- } else {
2064
- sctx.logger?.warn?.("free-search: dsh-settings 无可用注册 API(installSection/installSettingsSection 均缺失)");
2065
- return;
2066
- }
2067
- })();
2068
- }
2056
+ // rc.1 moved plugin configuration into the profile-owned Config, so the old
2057
+ // namespace-registration API is gone. Editable fields are declared `.volatile()`
2058
+ // on `Config` (above); this only records that the plugin ships its own settings
2059
+ // page (`auto: false`) so the host does not also generate a schema form. Values
2060
+ // persist per profile in `cordis.patch.yml` under this entry (`FREE_SEARCH_NS`).
2061
+ sctx.effect(() => sctx.settings.configure({ auto: false }, ctx.fiber));
2062
+ // Re-render the dynamic system-prompt engine list whenever this entry's config changes.
2063
+ sctx.on("settings/document-updated", (ns) => {
2064
+ if (String(ns) === FREE_SEARCH_NS && typeof refreshPrompt === "function") refreshPrompt();
2065
+ });
2069
2066
  });
2070
2067
 
2071
2068
  ctx.inject(["webServer", "settings"], (sctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-free-search",
3
- "version": "0.4.36",
3
+ "version": "0.4.37",
4
4
  "description": "Free web search for DeepSeek Harness: 13 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Perplexity/SerpBase/DeepSeek with key) + time filtering + platform search + web_fetch, with web settings UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -43,15 +43,47 @@
43
43
  "node": ">=20"
44
44
  },
45
45
  "dependencies": {
46
- "@deepseek-ai/schemastery": "^3.18.1"
46
+ "@deepseek-ai/schemastery": "^3.18.4"
47
47
  },
48
48
  "peerDependencies": {
49
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1",
50
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.7 || ^0.1.1-rc.2 || ^0.1.2-alpha.2 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1"
49
+ "@deepseek-ai/cordis": "^4.0.1",
50
+ "@deepseek-ai/dsh-client-ui-commands": "^0.1.7-rc.1",
51
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-rc.1",
52
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.1",
53
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
54
+ "@deepseek-ai/dsh-tools": "^0.1.7-rc.1",
55
+ "@deepseek-ai/schemastery": "^3.18.4",
56
+ "react": "^18.2.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@deepseek-ai/cordis": {
60
+ "optional": true
61
+ },
62
+ "@deepseek-ai/dsh-client-ui-commands": {
63
+ "optional": true
64
+ },
65
+ "@deepseek-ai/dsh-client-ui-plugin-manager": {
66
+ "optional": true
67
+ },
68
+ "@deepseek-ai/dsh-client-ui-renderer": {
69
+ "optional": true
70
+ },
71
+ "@deepseek-ai/dsh-settings": {
72
+ "optional": true
73
+ },
74
+ "@deepseek-ai/dsh-tools": {
75
+ "optional": true
76
+ },
77
+ "@deepseek-ai/schemastery": {
78
+ "optional": true
79
+ },
80
+ "react": {
81
+ "optional": true
82
+ }
51
83
  },
52
84
  "devDependencies": {
53
- "@deepseek-ai/dsh-settings": "0.1.0-rc.7",
54
- "@deepseek-ai/dsh-tools": "0.1.0-rc.7"
85
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
86
+ "@deepseek-ai/dsh-tools": "^0.1.7-rc.1"
55
87
  },
56
88
  "dsh": {
57
89
  "bundle": {
@@ -59,27 +91,18 @@
59
91
  },
60
92
  "client": {
61
93
  "inject": [
62
- "@deepseek-ai/dsh-client-runtime"
94
+ "@deepseek-ai/dsh-client-ui-commands",
95
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
96
+ "@deepseek-ai/dsh-client-ui-renderer"
63
97
  ],
64
98
  "platform": "web"
65
99
  },
66
100
  "engines": {
67
- "dsh": ">=0.1.1-rc.1"
101
+ "dsh": ">=0.1.7-rc.1"
68
102
  },
69
103
  "compatibility": {
70
104
  "dshReleases": {
71
- "rc.7": "unknown",
72
- "rc.8": "unknown",
73
- "0.1.1-rc.1": "compatible",
74
- "0.1.1-rc.2": "compatible",
75
- "0.1.2-alpha.1": "unknown",
76
- "0.1.2-alpha.2": "compatible",
77
- "0.1.2-alpha.3": "compatible",
78
- "0.1.2-alpha.4": "compatible",
79
- "0.1.2-alpha.5": "unknown",
80
- "0.1.2-rc.1": "compatible",
81
- "0.1.5-rc.1": "compatible",
82
- "0.1.5-rc.2": "compatible"
105
+ "0.1.7-rc.1": "compatible"
83
106
  }
84
107
  }
85
108
  }