dsh-workspace-api-key 0.3.0 → 0.3.1

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
@@ -34,7 +34,7 @@ session key → workspace key → system default
34
34
 
35
35
  The plugin switches on **host capability**, not on a parsed version: if the `web-deepseek-api` settings row contains `searchBilling` / `fetchBilling`, the host is the new one (DSH >= `0.2.2-rc.2`); otherwise the old behaviour applies.
36
36
 
37
- - **New host**: the main panel keeps a single "搜索与获取" card that explains the official settings page owns the search key, shows the current search/fetch billing mode, and offers an "打开官方搜索设置" button. The old "搜索默认 key" card, the "同步搜索 API" switch, the effective-search-key hint, and all three search warnings are hidden — `apiKeyEnv` no longer takes effect on this host, so showing them would only invite misconfiguration. With "DeepSeek API Key" billing, search resolves `DEEPSEEK_API_KEY` and therefore follows the session → workspace → default cascade automatically; with "已登录的 DeepSeek 账号" billing it uses the account token, which the plugin cannot override, and the card says so.
37
+ - **New host**: the main panel keeps a single "搜索与获取" card that explains the official settings page owns the search key, shows the current search/fetch billing mode, and offers an "打开官方搜索设置" button that switches to the 插件 → 网页搜索与获取 page. The old "搜索默认 key" card, the "同步搜索 API" switch, the effective-search-key hint, and all three search warnings are hidden — `apiKeyEnv` no longer takes effect on this host, so showing them would only invite misconfiguration. With "DeepSeek API Key" billing, search resolves `DEEPSEEK_API_KEY` and therefore follows the session → workspace → default cascade automatically; with "已登录的 DeepSeek 账号" billing it uses the account token, which the plugin cannot override, and the card says so.
38
38
  - **Old host**: the 0.2.x behaviour is unchanged (search default key, `apiKeyEnv` takeover, per-workspace sync switch, search warnings).
39
39
  - **Host version**: `hostVersion` in `/wsk-api/state` is best-effort — the plugin reads the `version` of a resolvable `@deepseek-ai/dsh` package.json; the desktop host is packed inside app.asar, so when it cannot be resolved the Settings card shows an unknown version. Capability detection, not the version string, drives behaviour.
40
40
  - **Experimental "unified search key"** (Settings → API Key 分配 only, never in the main panel): when enabled, the plugin points the official `searchApiKeyEnv` / `fetchApiKeyEnv` at `DEEPSEEK_API_KEY_SEARCH` and stores that one key, so every session and workspace uses it for `web_search` / `web_fetch` (ahead of session/workspace keys); disabling clears both fields and returns to the official billing mode. It requires the new host — an old host answers the request with 400.
@@ -92,7 +92,7 @@ The command forwards its arguments to pnpm inside the profile directory, so the
92
92
  pnpm add dsh-workspace-api-key # or: npm install dsh-workspace-api-key
93
93
  ```
94
94
 
95
- Pin a version with `dsh-workspace-api-key@0.3.0`, or install straight from the repository with `dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key` (a git-hosted plugin is built from source on install).
95
+ Pin a version with `dsh-workspace-api-key@0.3.1`, or install straight from the repository with `dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key` (a git-hosted plugin is built from source on install).
96
96
 
97
97
  Then add `"dsh-workspace-api-key"` to `dsh.profile.bundles` in that profile's `package.json` and restart DSH. For local development, use a link instead:
98
98
 
package/README.zh.md CHANGED
@@ -34,7 +34,7 @@ DSH 只从一个全局凭据 ref(默认 `DEEPSEEK_API_KEY`)解析 DeepSeek k
34
34
 
35
35
  插件按**宿主能力**切换界面,不猜版本号:`web-deepseek-api` 的设置里出现 `searchBilling` / `fetchBilling` 字段,就按新版宿主(DSH >= `0.2.2-rc.2`)处理,否则按旧版处理。
36
36
 
37
- - **新版宿主**:主面板只保留一张「搜索与获取」卡片,说明官方设置页已经接管搜索 key,显示当前的搜索/获取计费方式,并提供「打开官方搜索设置」按钮。旧版的「搜索默认 key」卡片、「同步搜索 API」开关、「搜索生效 key」提示和三类搜索告警全部隐藏——`apiKeyEnv` 在新版里已不再生效,继续展示只会让人误配。计费方式为「DeepSeek API Key」时,搜索解析的是 `DEEPSEEK_API_KEY`,因此会自动跟随上面讲的会话 → 工作区 → 系统默认级联;计费方式为「已登录的 DeepSeek 账号」时走账号令牌,插件无法覆盖,卡片会如实提示。
37
+ - **新版宿主**:主面板只保留一张「搜索与获取」卡片,说明官方设置页已经接管搜索 key,显示当前的搜索/获取计费方式,并提供「打开官方搜索设置」按钮(点击后切到插件面板的「网页搜索与获取」页)。旧版的「搜索默认 key」卡片、「同步搜索 API」开关、「搜索生效 key」提示和三类搜索告警全部隐藏——`apiKeyEnv` 在新版里已不再生效,继续展示只会让人误配。计费方式为「DeepSeek API Key」时,搜索解析的是 `DEEPSEEK_API_KEY`,因此会自动跟随上面讲的会话 → 工作区 → 系统默认级联;计费方式为「已登录的 DeepSeek 账号」时走账号令牌,插件无法覆盖,卡片会如实提示。
38
38
  - **旧版宿主**:0.2.x 的行为原样保留(搜索默认 key、接管 `apiKeyEnv`、工作区同步开关、搜索告警)。
39
39
  - **宿主版本**:`/wsk-api/state` 里的 `hostVersion` 是尽力而为读出来的——从能解析到的 `@deepseek-ai/dsh` 的 `package.json` 取 `version`;桌面版宿主打在 asar 里读不到时返回空,设置页显示「未知」。所以兼容性判断始终以设置字段为准,版本号只用于展示。
40
40
  - **实验性「统一搜索 key」**(只在「设置 → API Key 分配」,不放进主面板):开启后插件把官方 `searchApiKeyEnv` / `fetchApiKeyEnv` 指向 `DEEPSEEK_API_KEY_SEARCH`,并把这把 key 存进凭据服务,于是所有会话、工作区的 `web_search` / `web_fetch` 都用它(优先于会话/工作区 key);关闭时清空这两个字段,回到官方计费方式。它需要新版宿主,旧版宿主调用会返回 400。
@@ -92,7 +92,7 @@ dsh plugin --profile web add dsh-workspace-api-key
92
92
  pnpm add dsh-workspace-api-key # 或者:npm install dsh-workspace-api-key
93
93
  ```
94
94
 
95
- 锁版本用 `dsh-workspace-api-key@0.3.0`;直接从仓库装则用 `dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key`(git 来源的插件会在安装时现场构建)。
95
+ 锁版本用 `dsh-workspace-api-key@0.3.1`;直接从仓库装则用 `dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key`(git 来源的插件会在安装时现场构建)。
96
96
 
97
97
  然后把 `"dsh-workspace-api-key"` 加进该 profile `package.json` 的 `dsh.profile.bundles`,重启 DSH。本地开发用 link 代替:
98
98
 
@@ -113,7 +113,7 @@ node test/client-billing.test.mjs # 8 条断言(新版计费模型)
113
113
  ## 要求与限制
114
114
 
115
115
  - 已在 DSH Desktop(`@deepseek-ai/dsh*` 0.2.2-rc.2、`@deepseek-ai/cordis` 4.0.4)上验证。插件没有运行时依赖,也不 import 任何 `@deepseek-ai/*` 包,只用宿主服务(`credentials`、`workspaceRegistry`、`agents`、`sessionController`、`webServer`、`llm`、`settings`)与 Node 内置模块。
116
- - 搜索功能依赖自带官方 Search/Fetch API 的 DSH(`web-deepseek-api`,即「设置 → 插件 → 网页搜索」里那个「启用新版 Web 搜索 API」)。没有这个能力的版本上,插件根本收不到搜索请求:开关照旧显示,面板会如实报告当前是旧版 Messages 通道,而不是瞎猜。新版宿主改用「计费方式」后,`account` 计费走账号令牌,插件无法覆盖,只能提示;需要按会话/工作区 key 计费时请把计费方式设为「DeepSeek API Key」,或使用设置页里的实验性「统一搜索 key」。
116
+ - 搜索功能依赖自带官方 Search/Fetch API 的 DSH(`web-deepseek-api`,即「插件 → 网页搜索与获取」里的计费方式设置)。没有这个能力的版本上,插件根本收不到搜索请求:开关照旧显示,面板会如实报告当前是旧版 Messages 通道,而不是瞎猜。新版宿主改用「计费方式」后,`account` 计费走账号令牌,插件无法覆盖,只能提示;需要按会话/工作区 key 计费时请把计费方式设为「DeepSeek API Key」,或使用设置页里的实验性「统一搜索 key」。
117
117
  - 每一层只有一把 key:一个会话无论被哪个 provider 路由读取,用的都是同一把 key;key 记下的服务商与会话当前服务商不一致时会被报为「不匹配」,但同一服务商内换模型不算不匹配,因为 DeepSeek key 是账号级的。
118
118
  - 会话按目录(`cwd`)归入工作区。宿主自己的 `list()` 会跳过从未存过 `cwd` 的历史会话,而 live 兜底只认识当前装载中的会话,所以面板列出的会话可能比侧栏少;只在浏览器端存储里可见的会话,除了侧栏知道的那个 `cwd` 之外没有别的路径信息。
119
119
  - 「测试连接」说的是 Anthropic Messages 协议(`POST <baseURL>/messages` 带 `x-api-key` 与 `anthropic-version: 2023-06-01`),也就是 DeepSeek 路由用的协议;换了别的协议的 provider 一样可以按层绑 key,只是测试可能把有效的 key 报成失败。
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@ window.__ModuleLoader__.load({
16
16
  // Messages produced by the host half (which has no locale of its own) are
17
17
  // covered by EN_HOST and the pattern rules below.
18
18
  const EN = {
19
- "勾选后:该目录下会话的搜索/抓取请求改用聊天用的那把 key(会话单独配置仍然优先)。不勾则用「设置 → 插件 → 网页搜索」里配置的 key。": "When checked: search/fetch calls from sessions in this directory use the chat key (session-level keys still win). Unchecked, they use the key from Settings → Plugins → Web Search.",
19
+ "勾选后:该目录下会话的搜索/抓取请求改用聊天用的那把 key(会话单独配置仍然优先)。不勾则用「插件 → 网页搜索与获取」里配置的 key。": "When checked: search/fetch calls from sessions in this directory use the chat key (session-level keys still win). Unchecked, they use the key from Plugins → Web search and fetch.",
20
20
  "连接正常": "Connected",
21
21
  "key 无效": "Invalid key",
22
22
  "余额不足": "Out of balance",
@@ -38,7 +38,7 @@ window.__ModuleLoader__.load({
38
38
  "官方「网页搜索」页仍指向原来的 ref,两处暂时不是同一把。": "The official \"Web Search\" page still points at the previous ref, so the two are temporarily different keys.",
39
39
  "官方「网页搜索」页的 API Key 输入框也指向这个 ref:以后两边改的是同一把 key。": "The API Key field on the official \"Web Search\" page now points at this ref too: both places edit the same key.",
40
40
  "已同步:该目录下会话的搜索请求改用聊天用的那把 key(会话单独配置仍然优先)。": "Synced: searches from sessions in this directory use the chat key (a session-level key still wins).",
41
- "已取消同步:该目录的搜索请求改用「设置 → 插件 → 网页搜索」里配置的 key。": "Sync off: searches in this directory use the key from Settings → Plugins → Web Search.",
41
+ "已取消同步:该目录的搜索请求改用「插件 → 网页搜索与获取」里配置的 key。": "Sync off: searches in this directory use the key from Plugins → Web search and fetch.",
42
42
  "旧版(未重启应用)": "older version (app not restarted)",
43
43
  "不可用": "Unavailable",
44
44
  "(live 兜底)": "(live fallback)",
@@ -66,7 +66,7 @@ window.__ModuleLoader__.load({
66
66
  "未分组目录": "Ungrouped directory",
67
67
  "目录已不存在": "Directory no longer exists",
68
68
  "勾选后:该目录下会话的搜索/抓取请求改用聊天用的那把 key": "When checked: search/fetch calls from sessions in this directory use the chat key",
69
- "(会话单独配置仍然优先)。不勾则用「设置 → 插件 → 网页搜索」里配置的 key。": "(session-level keys still win). Unchecked, they use the key from Settings → Plugins → Web Search.",
69
+ "(会话单独配置仍然优先)。不勾则用「插件 → 网页搜索与获取」里配置的 key。": "(session-level keys still win). Unchecked, they use the key from Plugins → Web search and fetch.",
70
70
  "同步搜索 API": "Sync search API",
71
71
  "搜索用这里的 key": "Searches use this key",
72
72
  "搜索用搜索默认 key": "Searches use the search default key",
@@ -78,7 +78,7 @@ window.__ModuleLoader__.load({
78
78
  "。没有单独配置的地方都用它聊天。": ". Everywhere without its own key uses it for chat.",
79
79
  "再回到本页的「搜索默认 key」,放一把搜索专用的 key,例如 ": "Then, back on this page, put a search-only key in \"Search default key\", e.g. ",
80
80
  "。搜索请求默认用它,和聊天那把分开计费。": ". Searches use it by default, billed separately from the chat key.",
81
- "保存时插件会把官方「设置 → 插件 → 网页搜索」里的 API Key 指向同一个 ref,": "On save the plugin points the official Settings → Plugins → Web Search API Key at the same ref,",
81
+ "保存时插件会把官方「插件 → 网页搜索与获取」里的 API Key 指向同一个 ref,": "On save the plugin points the official Plugins → Web search and fetch API Key at the same ref,",
82
82
  "所以那个页面显示、修改的都是这把 key,两边不会各存一份。": "so that page shows and edits this key; there is no second copy.",
83
83
  "平时不用再管:聊天按工作区/会话的 key 走,搜索走搜索默认 key,两边账单互不影响。": "Nothing else to do: chat uses workspace/session keys, search uses the search default key, and the bills don't affect each other.",
84
84
  "想按工作区核算搜索花费": "Track search spend per workspace",
@@ -92,7 +92,7 @@ window.__ModuleLoader__.load({
92
92
  "官方「网页搜索」页里配的那把 —— 面板没配搜索默认 key 时的兜底": "The key on the official \"Web Search\" page — fallback when this panel has no search default key",
93
93
  "(它默认就是系统默认 key ": "(by default that is the system default key ",
94
94
  ",与聊天共用)": ", shared with chat)",
95
- "官方「设置 → 插件 → 网页搜索」那个 Key 框怎么办": "What about the Key field in Settings → Plugins → Web Search?",
95
+ "官方「插件 → 网页搜索与获取」那个 Key 框怎么办": "What about the Key field in Plugins → Web search and fetch?",
96
96
  "官方那个输入框写的永远是「当前 API 段的 ": "That field always writes the ref named by the current API section's ",
97
97
  " 指向的 ref」,默认就是聊天那把(": ", which defaults to the chat key (",
98
98
  "本插件启动后会把它指到插件自己的搜索 ref(": "On startup this plugin points it at its own search ref (",
@@ -169,7 +169,7 @@ window.__ModuleLoader__.load({
169
169
  "已切到该面板:如果它还是没出现,说明面板正在加载,稍等一下。": "Switched to that panel: if it has not appeared yet, it is still loading — give it a moment.",
170
170
  "这里同时管着每个工作区 / 会话的 key 与搜索通道;面板右上角的「使用说明」有完整的优先级说明。": "This panel manages the key and search channel of every workspace and session; the \"How to use\" button in its header explains the full precedence order.",
171
171
  "搜索与获取": "Search & fetch",
172
- "新版宿主用「计费方式」管理网页搜索 / 网页获取:key 由官方设置页决定,本面板不再接管,也不再显示旧的搜索 key。": "The newer host manages web search and fetch by billing mode: the key is decided on the official settings page, so this panel no longer takes over search keys or shows the old search key.",
172
+ "新版宿主用「计费方式」管理网页搜索 / 网页获取:key 由官方页面决定,本面板不再接管,也不再显示旧的搜索 key。": "The newer host manages web search and fetch by billing mode: the key is decided on the official page, so this panel no longer takes over search keys or shows the old search key.",
173
173
  "实验性「统一搜索 key」已开启:所有会话 / 工作区搜索都使用同一把 key。可在「设置 → API Key 分配」里更换或关闭。": "The experimental unified search key is on: every session and workspace searches with the same key. Change or disable it in Settings → API Key Allocation.",
174
174
  "计费方式为「DeepSeek API Key」时,搜索会跟随当前会话 / 工作区的聊天 key;为「已登录的 DeepSeek 账号」时,搜索走账号令牌,插件无法覆盖。": "With billing set to \"DeepSeek API Key\", searches follow the current session/workspace chat key; with \"Signed-in DeepSeek account\", searches use the account token, which this plugin cannot override.",
175
175
  "自动": "Automatic",
@@ -177,7 +177,7 @@ window.__ModuleLoader__.load({
177
177
  "打开官方搜索设置": "Open official search settings",
178
178
  "检测到的宿主版本:": "Detected host version: ",
179
179
  "搜索 API(实验性)": "Search API (experimental)",
180
- "新版宿主由官方「设置 → 插件 → 网页搜索」统一管理搜索/获取的计费方式。这里只保留一个实验开关:把所有会话的搜索强制指向插件持有的同一把 key,适合“一把搜索 key 走天下”。": "The newer host manages search/fetch billing on the official Settings → Plugins → Web Search page. Only one experimental switch remains here: force every session's searches to use a single key held by this plugin — handy for a single search key everywhere.",
180
+ "新版宿主由官方「插件 → 网页搜索与获取」统一管理搜索/获取的计费方式。这里只保留一个实验开关:把所有会话的搜索强制指向插件持有的同一把 key,适合“一把搜索 key 走天下”。": "The newer host manages search/fetch billing on the official Plugins → Web search and fetch page. Only one experimental switch remains here: force every session's searches to use a single key held by this plugin — handy for a single search key everywhere.",
181
181
  "当前已启用:所有会话 / 工作区搜索都使用 DEEPSEEK_API_KEY_SEARCH 这一把 key,不再跟随各自的聊天 key。": "Currently on: every session and workspace searches with DEEPSEEK_API_KEY_SEARCH instead of its chat key.",
182
182
  "粘贴新的搜索 key(sk-…)": "Paste a new search key (sk-…)",
183
183
  "保存并继续使用": "Save and keep using",
@@ -192,7 +192,7 @@ window.__ModuleLoader__.load({
192
192
  "计费方式为「DeepSeek API Key」:搜索跟随当前会话 / 工作区的聊天 key,账单一起算。": "Billing \"DeepSeek API Key\": searches follow the current session/workspace chat key and share its bill.",
193
193
  "计费方式为「已登录的 DeepSeek 账号」:搜索走账号令牌,插件无法覆盖。": "Billing \"Signed-in DeepSeek account\": searches use the account token, which this plugin cannot override.",
194
194
  "「自动」:看当前会话走的是哪个模型路由,规则由宿主决定。": "\"Automatic\": follows the model route of the current session; the host decides the rules.",
195
- "要改计费方式,点面板里的「打开官方搜索设置」,或手动到「设置 → 插件 → 网页搜索」。": "To change billing, press \"Open official search settings\" in the panel, or go to Settings → Plugins → Web Search manually.",
195
+ "要改计费方式,点面板里的「打开官方搜索设置」,或手动到「插件 → 网页搜索与获取」。": "To change billing, press \"Open official search settings\" in the panel, or go to Plugins → Web search and fetch manually.",
196
196
  "实验性的「统一搜索 key」在「设置 → API Key 分配」页里,开启后所有会话搜索共用插件持有的一把 key。": "The experimental unified search key lives on the Settings → API Key Allocation page; when on, every session searches with one key held by the plugin.",
197
197
  "聊天 key 的优先级": "Chat key precedence",
198
198
  "会话单独配置的 key": "a key configured for the session",
@@ -206,7 +206,7 @@ window.__ModuleLoader__.load({
206
206
  "搜索会用空 key 发出去。给这些工作区各配一把,或先配好系统默认 key。": "searches would go out with an empty key. Give each of them a key, or configure the system default first.",
207
207
  "当前搜索走旧的 Messages 通道,它本来就用聊天通道的 key(会话 → 工作区 → 系统默认),": "Searches use the legacy Messages channel, which already uses the chat key (session → workspace → system default),",
208
208
  "所以这个开关只影响「启用新版 Web 搜索 API」之后的行为。": "so this switch only affects behavior after \"New Web Search API\" is enabled.",
209
- "要让它生效,请到「设置 → 插件 → 网页搜索」打开那个开关;若暂时不想用新版 API,可以忽略本条。": "To enable it, turn on the switch in Settings → Plugins → Web Search; ignore this notice if you do not want the new API for now.",
209
+ "要让它生效,请到「插件 → 网页搜索与获取」打开那个开关;若暂时不想用新版 API,可以忽略本条。": "To enable it, turn on the switch in Plugins → Web search and fetch; ignore this notice if you do not want the new API for now.",
210
210
  "点面板上的「同步到官方设置」,官方页的 API Key 输入框就会改到同一个 ref,两处以后改的是同一把 key。": "Click \"Sync to official settings\" in the panel and the official API Key field moves to the same ref; both then edit the same key.",
211
211
  "在本面板的「搜索默认 key」里保存一把后,插件会自动把官方配置指过去,两边就分开了。": "Save one under \"Search default key\" in this panel and the plugin points the official setting at it, separating the two.",
212
212
  "连接正常,key 可用": "Connected; key works",
@@ -811,52 +811,84 @@ window.__ModuleLoader__.load({
811
811
  }
812
812
 
813
813
  /**
814
- * 跳到官方「设置 → 插件 → 网页搜索」。
814
+ * 跳到官方「插件 → 内置插件配置 → 网页搜索与获取」。
815
815
  *
816
- * 宿主没有给普通插件开放「把设置打开到某个 section」的 API(settings store 的
817
- * openSection 只发给 settings.launcher),所以这里做尽力而为的点击导航:
818
- * 打开设置 → 点「插件」→ 点「网页搜索」。任何一步失败,用户至少停在设置里。
816
+ * 这个页面不是设置弹窗里的一栏,而是插件面板里的一张内置插件卡片
817
+ * (`[data-plugin-item="web-search"]`)。所以先切到插件面板,再等「已安装」
818
+ * 标签页把卡片渲染出来点开。面板的列表是异步加载的,标签页要点一次才会拉取,
819
+ * 因此这里重试几轮、每轮都补点一次「已安装」。
819
820
  */
820
821
  function openOfficialSearchSettings() {
821
- const findClickable = (root, pattern) => {
822
- const nodes = root.querySelectorAll('button, a, [role="tab"], [role="button"], [role="menuitem"]')
823
- for (const node of nodes) {
824
- const text = (node.textContent ?? '').trim()
825
- if (text !== '' && pattern.test(text)) {
826
- node.click()
827
- return true
822
+ const waitFor = (probe, tries = 60, interval = 50) => new Promise((resolve) => {
823
+ let left = tries
824
+ const tick = () => {
825
+ let hit = null
826
+ try {
827
+ hit = probe()
828
+ } catch {
829
+ hit = null
830
+ }
831
+ if (hit !== null && hit !== undefined && hit !== false) {
832
+ resolve(hit)
833
+ return
828
834
  }
835
+ left -= 1
836
+ if (left <= 0) {
837
+ resolve(null)
838
+ return
839
+ }
840
+ setTimeout(tick, interval)
829
841
  }
830
- return false
842
+ tick()
843
+ })
844
+
845
+ // 官方搜索设置页是「插件」面板里的内置插件卡片(data-plugin-item="web-search"),
846
+ // 不是设置弹窗里的某一栏;切到插件面板再点开这张卡片最稳。
847
+ const openCard = () => {
848
+ const item = document.querySelector('[data-plugin-item="web-search"]')
849
+ if (item === null) return null
850
+ const target = item.querySelector('button, a, [role="button"]') ?? item
851
+ target.click()
852
+ return target
831
853
  }
832
- try {
833
- if (document.querySelector('[data-shortcut-modal="settings"]') === null) {
834
- const trigger = document.querySelector('button[aria-haspopup="dialog"]')
835
- if (trigger !== null) trigger.click()
836
- else window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', code: 'Comma', ctrlKey: true, bubbles: true }))
837
- }
838
- let tries = 0
839
- const timer = setInterval(() => {
840
- tries += 1
841
- const modal = document.querySelector('[data-shortcut-modal="settings"]')
842
- const text = modal === null ? '' : (modal.textContent ?? '')
843
- if (modal !== null && /插件|Plugins?/i.test(text)) {
844
- if (findClickable(modal, /^插件$|^Plugins?$/i)) {
845
- clearInterval(timer)
846
- setTimeout(() => {
847
- const again = document.querySelector('[data-shortcut-modal="settings"]')
848
- if (again !== null) findClickable(again, /网页搜索|Web search/i)
849
- }, 150)
850
- return
851
- }
852
- }
853
- if (tries > 40) clearInterval(timer)
854
- }, 50)
855
- } catch {
856
- /* 尽力而为:至少把设置打开 */
854
+
855
+ const findInstalledTab = () => {
856
+ const tab = [...document.querySelectorAll('button, [role="tab"], [role="button"]')]
857
+ .find((node) => /^(已安装|Installed)$/.test((node.textContent ?? '').trim()))
858
+ return tab === undefined ? null : tab
857
859
  }
860
+
861
+ Promise.resolve()
862
+ .then(async () => {
863
+ try {
864
+ const layout = host.ctx === null ? undefined : host.ctx.get('layout')
865
+ if (layout !== undefined && layout !== null && typeof layout.selectPanel === 'function') layout.selectPanel('plugins')
866
+ } catch {
867
+ /* 面板服务不可用时不抛错 */
868
+ }
869
+ for (let attempt = 0; attempt < 3; attempt += 1) {
870
+ await waitFor(findInstalledTab, 40)
871
+ const tab = findInstalledTab()
872
+ if (tab !== null) tab.click()
873
+ if ((await waitFor(openCard, 100)) !== null) return true
874
+ await new Promise((resolve) => setTimeout(resolve, 500))
875
+ }
876
+ // 最后兜底:任何写着「网页搜索与获取」的可点元素。
877
+ const label = await waitFor(() => {
878
+ const nodes = [...document.querySelectorAll('button, a, [role="button"]')]
879
+ const hit = nodes.find((node) => /网页搜索与获取|Web search and fetch/i.test(node.textContent ?? ''))
880
+ return hit === undefined ? null : hit
881
+ }, 20)
882
+ if (label !== null) {
883
+ label.click()
884
+ return true
885
+ }
886
+ return false
887
+ })
888
+ .catch(() => { /* 尽力而为 */ })
858
889
  }
859
890
 
891
+
860
892
  /**
861
893
  * 「设置 → API Key 分配」这一栏:侧栏图标没了也能从这里回到面板。
862
894
  *
@@ -935,7 +967,7 @@ window.__ModuleLoader__.load({
935
967
  ? h('div', { className: 'wsk-card' },
936
968
  h('h4', null, '搜索 API(实验性)'),
937
969
  h('div', { className: 'wsk-hint' },
938
- '新版宿主由官方「设置 → 插件 → 网页搜索」统一管理搜索/获取的计费方式。这里只保留一个实验开关:把所有会话的搜索强制指向插件持有的同一把 key,适合“一把搜索 key 走天下”。'),
970
+ '新版宿主由官方「插件 → 网页搜索与获取」统一管理搜索/获取的计费方式。这里只保留一个实验开关:把所有会话的搜索强制指向插件持有的同一把 key,适合“一把搜索 key 走天下”。'),
939
971
  unified
940
972
  ? h('div', null,
941
973
  h('div', { className: 'wsk-hint' },
@@ -983,7 +1015,9 @@ window.__ModuleLoader__.load({
983
1015
  h('button', {
984
1016
  type: 'button', className: 'wsk-btn', style: { marginTop: 8 },
985
1017
  onClick: openOfficialSearchSettings,
986
- }, '打开官方搜索设置'))
1018
+ }, '打开官方搜索设置'),
1019
+ h('div', { className: 'wsk-hint', style: { marginTop: 6 } },
1020
+ tf('打开后:插件 → 内置插件配置 → 网页搜索与获取', 'Then: Plugins → Built-in plugins → Web search and fetch')))
987
1021
  : null,
988
1022
  billing && typeof state?.hostVersion === 'string' && state.hostVersion !== ''
989
1023
  ? h('div', { className: 'wsk-hint' }, tf(`检测到的宿主版本:${state.hostVersion}`, `Detected host version: ${state.hostVersion}`))
@@ -1236,7 +1270,7 @@ window.__ModuleLoader__.load({
1236
1270
  kind: 'ok',
1237
1271
  text: enabled
1238
1272
  ? '已同步:该目录下会话的搜索请求改用聊天用的那把 key(会话单独配置仍然优先)。'
1239
- : '已取消同步:该目录的搜索请求改用「设置 → 插件 → 网页搜索」里配置的 key。',
1273
+ : '已取消同步:该目录的搜索请求改用「插件 → 网页搜索与获取」里配置的 key。',
1240
1274
  })
1241
1275
  }
1242
1276
  } catch (err) {
@@ -1503,7 +1537,7 @@ window.__ModuleLoader__.load({
1503
1537
  key: 'sync',
1504
1538
  className: `wsk-check${ws.searchSync === true ? ' on' : ''}`,
1505
1539
  title: '勾选后:该目录下会话的搜索/抓取请求改用聊天用的那把 key'
1506
- + '(会话单独配置仍然优先)。不勾则用「设置 → 插件 → 网页搜索」里配置的 key。',
1540
+ + '(会话单独配置仍然优先)。不勾则用「插件 → 网页搜索与获取」里配置的 key。',
1507
1541
  },
1508
1542
  h('input', {
1509
1543
  type: 'checkbox',
@@ -1574,7 +1608,7 @@ window.__ModuleLoader__.load({
1574
1608
  h('li', null, '计费方式为「已登录的 DeepSeek 账号」:搜索走账号令牌,插件无法覆盖。'),
1575
1609
  h('li', null, '「自动」:看当前会话走的是哪个模型路由,规则由宿主决定。')),
1576
1610
  h('div', { className: 'wsk-hint' },
1577
- '要改计费方式,点面板里的「打开官方搜索设置」,或手动到「设置 → 插件 → 网页搜索」。'),
1611
+ '要改计费方式,点面板里的「打开官方搜索设置」,或手动到「插件 → 网页搜索与获取」。'),
1578
1612
  h('div', { className: 'wsk-hint' },
1579
1613
  '实验性的「统一搜索 key」在「设置 → API Key 分配」页里,开启后所有会话搜索共用插件持有的一把 key。'),
1580
1614
  h('h4', null, '聊天 key 的优先级'),
@@ -1594,7 +1628,7 @@ window.__ModuleLoader__.load({
1594
1628
  '。没有单独配置的地方都用它聊天。'),
1595
1629
  h('li', null, '再回到本页的「搜索默认 key」,放一把搜索专用的 key,例如 ', h('code', null, 'search-default'),
1596
1630
  '。搜索请求默认用它,和聊天那把分开计费。',
1597
- '保存时插件会把官方「设置 → 插件 → 网页搜索」里的 API Key 指向同一个 ref,',
1631
+ '保存时插件会把官方「插件 → 网页搜索与获取」里的 API Key 指向同一个 ref,',
1598
1632
  '所以那个页面显示、修改的都是这把 key,两边不会各存一份。'),
1599
1633
  h('li', null, '平时不用再管:聊天按工作区/会话的 key 走,搜索走搜索默认 key,两边账单互不影响。')),
1600
1634
  h('h4', null, '想按工作区核算搜索花费'),
@@ -1609,7 +1643,7 @@ window.__ModuleLoader__.load({
1609
1643
  h('li', null, '搜索默认 key —— 没勾同步时,搜索用它'),
1610
1644
  h('li', null, '官方「网页搜索」页里配的那把 —— 面板没配搜索默认 key 时的兜底',
1611
1645
  '(它默认就是系统默认 key ', h('code', null, 'DEEPSEEK_API_KEY'), ',与聊天共用)')),
1612
- h('h4', null, '官方「设置 → 插件 → 网页搜索」那个 Key 框怎么办'),
1646
+ h('h4', null, '官方「插件 → 网页搜索与获取」那个 Key 框怎么办'),
1613
1647
  h('div', { className: 'wsk-hint' },
1614
1648
  '官方那个输入框写的永远是「当前 API 段的 ', h('code', null, 'apiKeyEnv'),
1615
1649
  ' 指向的 ref」,默认就是聊天那把(', h('code', null, 'DEEPSEEK_API_KEY'), ')。',
@@ -1775,7 +1809,7 @@ window.__ModuleLoader__.load({
1775
1809
  ? h('div', { className: 'wsk-card' },
1776
1810
  h('h4', null, '搜索与获取'),
1777
1811
  h('div', { className: 'wsk-hint' },
1778
- '新版宿主用「计费方式」管理网页搜索 / 网页获取:key 由官方设置页决定,本面板不再接管,也不再显示旧的搜索 key。'),
1812
+ '新版宿主用「计费方式」管理网页搜索 / 网页获取:key 由官方页面决定,本面板不再接管,也不再显示旧的搜索 key。'),
1779
1813
  h('div', { className: 'wsk-hint' },
1780
1814
  tf(
1781
1815
  `当前计费方式:搜索「${billingText(searchInfo.billing?.search)}」,获取「${billingText(searchInfo.billing?.fetch)}」`,
@@ -1789,7 +1823,9 @@ window.__ModuleLoader__.load({
1789
1823
  h('button', {
1790
1824
  type: 'button', className: 'wsk-btn primary',
1791
1825
  onClick: openOfficialSearchSettings,
1792
- }, '打开官方搜索设置')))
1826
+ }, '打开官方搜索设置')),
1827
+ h('div', { className: 'wsk-hint' },
1828
+ tf('打开后:插件 → 内置插件配置 → 网页搜索与获取', 'Then: Plugins → Built-in plugins → Web search and fetch')))
1793
1829
  : null,
1794
1830
 
1795
1831
  searchInfo?.mode !== 'billing' && searchInfo?.apiChannel === true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-workspace-api-key",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A DeepSeek Harness plugin that binds a separate DeepSeek API key to each workspace, or to a single session, so the spend of one key stays with the sessions you choose.",
5
5
  "type": "module",
6
6
  "license": "MIT",