DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Workspace Api Key — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins

dsh-workspace-api-key

Workspace Api Key

一个 DeepSeek Harness 插件,可为每个工作区或单个会话绑定独立的 DeepSeek API 密钥,从而让某个密钥的支出仅归属于你选择的会话。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add dsh-workspace-api-key@0.2.7
README兼容性版本

兼容性与来源证明

Workspace Api Key 以 dsh-workspace-api-key 发布,当前版本为 0.2.7。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
npm
Registry 更新时间
2026/10/16

版本

0.2.7stable
2026/10/12
0.2.6stable
2026/10/8
0.2.5stable
2026/10/8
查看其余 5 个版本收起版本
0.2.4stable
2026/10/8
0.2.3stable
2026/10/8
0.2.2stable
2026/10/8
0.2.1stable
2026/10/8
0.2.0stable
2026/10/8

相关插件

正在加载相关插件…

最新版
0.2.7
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
143.6 kB
文件数
7
Surface
web
许可证
MIT
发布源
npm
GitHub
★ 3
周下载
1,925
安全扫描
✓ v0.2.7 扫描通过
最近提交
2026/10/12
查看源码 ↗项目主页 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 security-access 分类下经过校验的插件。

Experimental Auto Review@deepseek-ai/dsh-experimental-auto-review针对每个工具的 LLM 授权审查,适用于 DeepSeek Harness Auto 权限预设Cc Safety Netcc-safety-net编码代理 CLI 钩子——阻止破坏性命令和访问机密文件Doctor@linxin666/dsh-doctorDSH 配置档案的事务性救援模式,配备受监督的启动器、隔离的恢复容器、确定性修复、健康监控以及本地 Web 恢复控制台Pocketdsh-pocket把 DeepSeek Harness 装进你的口袋:一个包、一个设置页,手机扫码即同步访问电脑上的 DSH(局域网 + 公网,实时同屏)。

README

dsh-workspace-api-key

English | 中文

A DeepSeek Harness plugin that binds a separate DeepSeek API key to each workspace — or to a single session.

DSH resolves the DeepSeek key from one global credential ref (default DEEPSEEK_API_KEY), so every session in every workspace spends the same key. This plugin adds a panel that gives individual workspaces — and, when you need it, individual sessions — their own key, so the spend of a key stays with the sessions that ran there. Anything without its own key keeps using the level above it:

session key  →  workspace key  →  system default

What you get

  • A sidebar row labelled API Key 分配, placed directly under the host's own Plugins row, opening a full-area page.
  • Per-workspace keys — every workspace registered with the workspaceRegistry service is listed, with its own key field.
  • Per-session keys — each workspace row expands (collapsed by default) to the sessions that live in that directory, each with its own key. Sessions that inherit a key from the level above say so instead of pretending to have one.
  • A system-default card — which ref is in effect, where its value comes from (file, env, …), and its masked value.
  • Connection test — one real POST <baseURL>/messages request (max_tokens: 1) with that key, reported as ok, invalid, quota, rate-limit or unreachable. A successful test also clears a stale flag.
  • Stale-key warning — 401/403 marks a bound key invalid, 402 marks it quota, 429 marks it rate-limit. When you enter a session whose effective key is flagged, an overlay tells you what happened and offers go configure, fall back to the level above, or dismiss.
  • Mismatch warning — if the key you bound was recorded for one provider (say deepseek-official) and the session now runs on another, entering that session reports it, because that request would be sent with a key the provider does not accept.
  • No silent substitution — neither warning ever changes which key is used. The decision stays with you, which is the whole point: an automatically swapped key would defeat the billing separation.
  • One-click revert — clearing a session entry falls back to the workspace key, clearing a workspace entry falls back to the system default.

How it works

Host half (lib/index.js):

  • wraps credentials.resolve once per process (restored on dispose through ctx.effect) instead of registering a second service or provider route, neither of which cordis allows for a name that is already taken;
  • reads the active session's directory and id from agents.currentInitiator()?.session — the agent loop runs the whole turn inside that async scope, so both are available when the credential is resolved, and the resolver picks the most specific level that has a key;
  • matches that directory against workspaceRegistry.list() after normalizing both sides (absolute, trailing separator removed, lower-cased on Windows);
  • derives a stable ref per level — DEEPSEEK_API_KEY_WS_<first 16 hex chars of sha1(normalized path), upper-cased> for a workspace and DEEPSEEK_API_KEY_SS_<first 16 hex chars of sha1(session id), upper-cased> for a session (a session id contains - and cannot be a ref name) — writes the key through the credentials service, and serves that ref's value;
  • rewrites only refs a provider actually reads: the default ref, every adapter's configured apiKeyEnv, and its own generated refs. Every other ref passes through untouched;
  • builds the session list from the sessionController service (list(), unwrapping its {items: […]} envelope) and falls back to the live sessions service (ctx.get('sessions').list()), so the panel still has sessions when the controller is missing, throws, or returns an empty list. It hides the two kinds of session the sidebar hides: subagent sessions (origin: 'subagent', or a parentSession) and blank sessions (created but never prompted — the sidebar shows only the one currently open). Without those two filters one workspace with a single real session listed thirteen;
  • reports its own version and a session probe (available, shape, source, count, withoutCwd, subagents, samples, error, plus sample workspace paths) in /wsk-api/state, so the panel can say why no sessions are listed: a stale host half (only a page reload instead of an app restart), a missing sessionController, a throwing list(), an unexpected envelope shape, or a cwd that matches no workspace path;
  • lists a directory that is not a registered workspace only if the sidebar would show it. It cannot decide that itself, because "only the currently open blank session counts" is knowledge the browser side holds (), so it sends every such directory plus a flag per session and a list naming the ones whose sessions are all blank — the browser half applies the same verdict the sidebar does;

The browser half (lib/client.js) registers into the sidebar.panellist slot (order: 1), the main slot (key workspace-api-key) and the shell.overlay slot (the entering-a-session warning), the same slots dsh-skill-mcp-panel and dsh-429-guard use. It is plain React.createElement with no build step and no bundled dependencies. It merges the host rows with the browser-side sessions store (ctx.get('sessions').list.getSnapshot(), which is what the sidebar itself renders), so session titles come from displayTitle, host rows are never dropped when that store only knows a subset, and sessions stay visible even when the host cannot list them. That store is provided by dsh-api-session-controller after the plugin's apply(), so both the panel and the overlay retry for it (every 500 ms, at most 40 times) instead of giving up on the first render. The overlay asks /wsk-api/check?sessionId=…&path=… about the session that currently owns the main view, asks at most once per session, and its buttons only ever navigate to the panel — the panel is where a key can actually be changed.

HTTP API (loopback only)

MethodPathPurpose
GET/wsk-api/statedefault ref status, workspaces with their sessions, overrides, invalid flags, plugin version and session probe
GET/wsk-api/resolvewhich level and ref the current session resolves to right now
GET/wsk-api/check?sessionId=…&path=… — read-only verdict for one session: effective level, key, stale flag, provider mismatch, needsAttention
POST/wsk-api/set{scope: 'workspace' | 'session', path | sessionId, title, key, provider?} binds a key; {useDefault: true} or an empty key clears it
POST/wsk-api/test{scope, path | sessionId | ref} (or {key}) runs one real request and returns the verdict; invalid marks the flag, ok clears it
POST/wsk-api/clear-flag{ref} or a target drops the invalid/quota flag

Only loopback callers are accepted (127.0.0.1, ::1, ::ffff:127.0.0.1) with a same-origin or absent Origin. No response ever contains a plaintext key, only a mask.

Workspace targets may be directories that are not registered workspaces; such entries appear as ungrouped directories, and only the ones the sidebar would actually show — a directory whose only sessions are brand-new and still empty (the sidebar shows just the currently open one) is not listed.

Install

Install through the market (设置 → 插件市场) once the plugin is listed, or manually:

# in the profile directory, e.g. ~/.dsh/profiles/desktop
pnpm add "dsh-workspace-api-key@github:COH2357/dsh-workspace-api-key"

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:

"dependencies": { "dsh-workspace-api-key": "link:/path/to/dsh-workspace-api-key" }

Tests

node test/host.test.mjs     # 136 assertions
node test/client.test.mjs   # 145 assertions

Neither suite needs a running DSH process. The host suite drives apply() against a mock context (fake credentials, workspaceRegistry, agents, sessionController, sessions, webServer, stubbed fetch) and covers ref derivation, the session → workspace → default cascade, workspace matching, redirecting a second adapter's apiKeyEnv, clearing back to the default, 401/402/429/5xx classification, the loopback and origin checks, state persistence, and the session probe including the {items} envelope, the live-session fallback, and subagent and blank sessions being hidden. The client suite loads lib/client.js through a minimal window.__ModuleLoader__ plus a small React runtime, renders the panel and the overlay, and asserts slot registration, the first render, session expand/collapse, per-session saving and testing, sessions recovered from the browser-side store when the host has none (with displayTitle winning and subagent sessions hidden), the ungrouped-directory card, the stale and mismatch prompts, the "go configure" hand-off, graceful degradation without a session list, and back-navigation when ctx.get('layout') only becomes available after the plugin was applied.

Requirements and limits

  • Verified against DSH Desktop 2.0.17 with @deepseek-ai/dsh* 0.2.0-rc.2 and @deepseek-ai/cordis 4.0.4. It has no runtime dependency and imports no @deepseek-ai/* package — only the host services (credentials, workspaceRegistry, agents, sessionController, webServer, llm, settings) and Node built-ins.
  • One key per level: a session has a single key whichever provider route reads it, and a key recorded for one provider is reported as a mismatch when the session runs on another. Model changes inside one provider are not treated as a mismatch, because a DeepSeek key is account-wide.
  • Sessions are matched to a workspace by their directory (cwd). The host's own list() skips stored sessions that never got a cwd, and the live-session fallback only knows sessions that are currently loaded, so the panel may show fewer sessions than the sidebar. Sessions visible only in the browser-side store carry no cwd of their own beyond the one the sidebar knows.
  • The connection test speaks the Anthropic Messages shape (POST <baseURL>/messages with x-api-key and anthropic-version: 2023-06-01), which is what the DeepSeek route uses. A provider that speaks a different protocol can still have keys bound per level, but the test may report a failure for a key that works.
  • Keys are stored through the credentials service in $DSH_HOME/.credentials.yaml under refs:, in plaintext, exactly like the default key. They are not written into session logs, the plugin's state file, or the plugin directory.
  • Matching is textual after normalization. If a session's cwd is a symlink or junction to a registered workspace path, it will not match and the level above is used.
  • Uninstalling the plugin leaves the generated *_WS_* / *_SS_* refs in the credentials file; remove them from the Models page.
  • If the panel lists workspaces but no expandable sessions, read the diagnostics line at the bottom of the panel. A red "the host half is still an older version" banner means the DSH process was never restarted: the host half is loaded once at startup, so closing the window or reloading the page is not enough — fully quit DSH Desktop and open it again. Otherwise the diagnostics line shows how many sessions the host sees (and from which source: sessionController, its live-session fallback, or the browser store), plus sample session cwd values next to sample workspace paths when none of them match.

License

MIT

retainedBy.mainView
blank
hiddenBlankDirs
  • keeps its own bookkeeping (which level maps to which ref, invalid flags, the provider a key was recorded for) in $DSH_HOME/storages/dsh-workspace-api-key.json;
  • observes failures through the agent/request-error waterfall without ever returning {kind: 'retry'} — it only records the verdict and calls next().