DeepSeek Harness Plugin Hub

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

探索

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

社区

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

相关链接

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

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

DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
W

dsh-websearch-toggle

Websearch Toggle

DeepSeek Harness 中 web_search 的实时开关,添加到侧边栏插件页面的官方 Web search 卡片中,未更改任何官方代码。

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

npx -y @deepseek-ai/dsh plugin --profile web add github:mathangler/dsh-websearch-toggle#ab53fbc6a7845cffed77767ce2edf1cd7d1d43a2
README兼容性版本

兼容性与来源证明

Websearch Toggle 以 dsh-websearch-toggle 发布,当前版本为 0.5.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

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

版本

0.5.0stable
2026/10/22
0.4.1stable
2026/9/22
0.3.3stable
2026/9/17
查看其余 1 个版本收起版本
0.1.0stable
2026/9/11

相关插件

正在加载相关插件…

最新版
0.5.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/10/22
查看源码 ↗
README Badge

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

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

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

认领这个 Plugin →
报告问题

相关插件

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

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Experimental Session Titles Bundle@deepseek-ai/dsh-experimental-session-titles-bundle新的用户提示到达时刷新会话标题。Personalized Recommendations@deepseek-ai/dsh-personalized-recommendations回答下方以卡片形式提供个性化页面和内置插件推荐Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profile用于 Agent Teams Remote 和 UI 插件的实验性 Web 配置层

README

dsh-websearch-toggle

A live on/off switch on the Web search plugin page in DeepSeek Harness.

Open the sidebar Plugins page → the Web search entry → the switch is the first control of that entry's page, above the API key, endpoint and max-uses fields it governs. Nothing appears on the card until you open it, and the collapsed card carries no control of its own.

English | 中文

What the switch does

Open the sidebar Plugins page. A switch appears at the trailing end of the official Web search card's title row — the same position and style as the switch every card in the Installed group already carries. The official card, its page and its fields are not modified in any way: this plugin appends one switch to that card after render, and removes it when the plugin unloads.

Off

  • Every agent stops receiving the web_search tool. The tool — and the prompt section that tells the model to use it — is withheld from the prompt assembly of the next model step, in every session, including ones that already exist. The model never sees the tool, so it never calls it and nothing is sent to the billed DeepSeek search endpoint.
  • A call that was already dispatched when the switch flipped is refused by a global tool guard instead of reaching the provider.

On — both effects are undone on the next step. No restart, no new session.

What it does not touch

  • web_fetch and the local fetch provider keep working. This is a search switch, not an internet switch.
  • No profile file is edited. The web-search-deepseek row stays exactly where the bundle put it, so the switch is reversible at any moment — unlike editing cordis.patch.yml or an agent preset.
  • The endpoint, key and max-uses values you stored are kept, not cleared. They are simply ignored while the switch is off.

Requirements

DSH 0.2.1 or newer. The settings model below arrived in 0.1.7 and is unchanged in 0.2.1; the switch's styling was re-copied for 0.2.1, which restyled the control (see "Matching the shell's switch"). Three facts decide whether the switch works at all:

  1. A plugin's settings page IS its Config schema. The plugin exports Config = z.object({...}); the settings system projects it into a form automatically. apply(ctx, config) receives the resolved config, and config.enabled.get() is a live accessor — a settings write is visible on the very next step with no subscription to wire. The old settings.installSection(...) / settings.get(...) calls do not exist in 0.1.7.
  2. The settings namespace IS the Loader entry id. This plugin's row is id: websearch-toggle in cordis.patch.yml, so its namespace is websearch-toggle and the browser half reaches it with configForms.get('websearch-toggle'). A namespace a plugin invents cannot be reached by its own browser half.
  3. The field must be marked .volatile(). SettingsForms.describe() builds every namespace through volatileForm(schema), which keeps only fields carrying meta.volatile and returns undefined when none do — and describe() then skips the entry entirely. A schema without it produces no namespace at all, the browser reads nothing, and the switch sits disabled. .volatile() also marks the field user-settable at runtime rather than fixed by the composition.

.volatile() arrived in @deepseek-ai/schemastery 3.18.3, and npm's latest tag for that package is 3.18.2 — which does not have it. So the dependency floor is load-bearing, not decoration: ^3.18.2 cannot reach 3.18.3, pnpm then installs a copy where .volatile is not a function, and the Host Loader reports the row as failed to import. It is pinned at ^3.18.3 for that reason.

A link: dev install does not catch this: it resolves the platform's bundled schemastery (3.18.3) rather than the declared dependency, so every dev-side check passes while a real install fails. That is exactly how this shipped once.

On 0.1.7 the choice lands in the profile's cordis.patch.yml as a config override for the entry (settings.yaml is no longer the live document):

- id: websearch-toggle
  name: dsh-websearch-toggle
  config:
    enabled: false

Three checks guard these, because no other test noticed either rename — the host booted clean and the bundle still served HTTP 200 while the switch was dead:

node test/verify-installed.mjs   # the INSTALLED copy imports and localizes
node test/dependency-floor.mjs   # the schemastery floor really has .volatile()
node test/locale-resources.mjs   # the localizable title/description reach the platform
node test/service-contract.mjs   # every injected service really is registered
node test/wiring.test.mjs        # the namespace, the volatile field, the effects
node test/style-parity.mjs       # the switch styles match the shipped primitive

Run service-contract and wiring after every DSH upgrade.

Platforms

Windows, macOS and Linux all load the same bytes. Nothing that ships is platform-specific:

  • the host half is plain JavaScript on Cordis — no shell-out, no process.platform, no node:os, no filesystem path of its own;
  • the browser half is DOM and CSS, so it is the browser that matters, not the OS (corner-shape is simply ignored where unsupported, exactly as it is for the shell's own switch);
  • cordis.patch.yml is a two-line YAML insert;
  • the only path this plugin ever causes to be written is the one the platform itself picks for the profile's cordis.patch.yml.

The one surface caveat is not about the OS: the switch itself lives in the Web UI (dsh.client.platform: "web"). Installed into a headless profile the switch would have nowhere to appear, while the host effect still honours whatever position is stored.

The checks under test/ locate the installed @deepseek-ai packages themselves (see test/dsh-install.mjs), so they run unchanged from any OS, any cwd, and any Node manager. test/serve-verify.ps1 is the one Windows-flavoured helper — it is a local verification harness and needs PowerShell 7, which also runs on macOS and Linux. No test ships in the package, so none of this affects an install.

Display metadata

Plugin Manager and Settings read a plugin's title and description without activating it, from <id>/locale/*.json, and pick the file matching the active locale. This plugin ships two:

{ "meta": { "title": "网页搜索开关", "description": "在官方「网页搜索」卡片上添加一个开关…" } }

The contract is strict, and test/locale-resources.mjs checks each part of it: locale/en.json is the base — without it the platform reads no locale file at all; every *.json beside it is a locale whose filename must be a language id; meta.title and meta.description must be non-empty strings when present, since a blank one throws instead of falling back; the files are resolved through the package exports map, so ./locale/*.json must be exported; and files must list them or a packed install ships without them. Switch DSH's language and both the title and the description follow it.

The icon

icon.svg follows the shipped plugins' convention: a top-level icon in package.json, a 36x36 canvas, line-art stroke weight, and the blue/teal gradient palette the other plugin icons use. It is a magnifier whose lens holds the switch this plugin adds, with the knob at the left — the OFF position, which is what the plugin looks like once it has withheld the tool.

The platform reads it through the package exports map, inlines it as a data:image/svg+xml URI, and accepts SVG, PNG, JPEG or WebP up to 256 KiB. A filename proves nothing about the bytes, so test/locale-resources.mjs checks the file is inside the package, listed in files, and parses as a closed SVG with a viewBox — and test/verify-installed.mjs decodes what the platform actually served.

Verify what is INSTALLED, not what is in the working tree

node test/verify-installed.mjs [profileDir]

This calls the platform's own readPluginMeta and imports the host half exactly as the Loader does, against the installed copy. It exists because two failures passed every dev-side check while failing for real users: the settingsScope -> configForms rename, and a schemastery range whose floor lacked .volatile(). Both were invisible in a link: install, which resolves the platform's bundled packages rather than the declared dependency, so the tree under test was not the tree an install resolves. Run it after any install and after every DSH upgrade.

Matching the shell's switch

The switch is a hand-rolled DOM control, because the Plugins page offers no slot inside an existing card. That makes matching the shell's own switch a maintenance chore, and it has gone stale twice — once when a copy missed corner-shape: round, and again in 0.2.1, which changed the track radius to 999px, moved the focus ring onto the --dsw-focus-ring-* tokens, and added an OFF colour for the thumb this plugin was missing entirely.

test/style-parity.mjs is the guard, and it does not hardcode a path: up to 0.1.7 the reference was @deepseek-ai/dsh-client-ui-primitives/lib/Switch.module.css, and in 0.2.1 that package is gone with the rules compiled into the web frontend's CSS under hashed class names. So the check scans every bundled stylesheet for the switch rule, rewrites the hashed selectors onto this plugin's prefixed ones, and requires every shipped declaration to be present verbatim.

Install

dsh plugin --profile web add github:mathangler/dsh-websearch-toggle

Then restart dsh web: a bundle row is composed at boot, so a running server does not serve the new plugin until it restarts.

Built against DSH 0.1.6-alpha.2. Version 0.1.x targeted 0.1.5, where plugin configuration lived in Settings; 0.2.x targets the sidebar Plugins page that 0.1.6 introduced.

Working on this plugin

github: and link: are mutually exclusive specs:

dsh plugin --profile web remove dsh-websearch-toggle
dsh plugin --profile web add link:<absolute path to this directory>

link: symlinks the directory, so edits take effect on the next server start. Updating an installed github: plugin also needs remove + add, because pnpm skips resolution while the spec is unchanged.

Check the bundle list after any remove + add. dsh plugin remove drops the package from dsh.profile.bundles in the profile's package.json, and the following add does not always put it back — the dependency is installed, but the row never composes, so the plugin silently does nothing while its browser half is still served. If the switch does not appear, confirm the package is listed in dsh.profile.bundles and re-add it there if not.

State

One boolean in <dshHome>/settings.yaml:

web-search-toggle:
  enabled: false

dshHome is $DSH_HOME when set, otherwise ~/.dsh. Absent means on — the shipped behaviour — so a deployment that never writes the key keeps its capability.

There is no custom route and no separate state file. The browser writes this namespace through the platform's own settings Remote, with the same revision fence every other settings form uses, so the browser's write is the commit and the Host learns about it from the namespace it installed.

Why it is built this way

web_search is not a model feature and not a switch the platform exposes. The tool-web row of an agent preset registers the tool into that preset's scoped layer, and a preset is fixed for the life of the session, so this plugin cannot unregister it; ctx.tools.restrict() is explicitly refused in an unscoped context ("a context-global restriction would mask every agent"). The supported seam is therefore the assembly waterfall: system-prompt/assemble returns a value the registry treats as authoritative, so removing the tool and tool:web_search from it is exactly what tool-web would have declined to register had its search flag been false — but live, and reversible per step.

The browser half registers no slot at all. Instead it appends one switch to the official card after render. The reason is that the Plugins page offers only three seats (plugins.item, plugins.bundle.config, plugins.row.config), and all three ADD a card or a page — none can put a control INSIDE an existing entry. Worse, plugins.item's own catalog says: "a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it." Reusing web-search therefore does not decorate the official card, it evicts it — an earlier build of this package did exactly that and deleted the official Web search form. So the official card is left alone and the switch is appended to it, then removed on unload.

The card is found by li[data-plugin-item="web-search"], a stable data- attribute the page sets itself — never a hashed CSS-module class name.

The schema must be a real one

The settings schema must be a genuine schemastery object, and this is not a style preference. settings.describe() calls schema.toJSON() on every registered schema and hands the result to the browser, and that ONE call decides whether each official plugin page exists at all (each registers only for a namespace it sees in that list). A hand-rolled validator without toJSON() therefore does far worse than misdescribe this plugin: it throws inside describe() and removes the Shell, Agent loop, Subagent and Web search pages from the Plugins page entirely. An earlier 0.2.x build of this package did exactly that. The schema is now imported from the platform's own published package, so the wire format is the real one rather than a copy of it.

The settings schema is declared inline rather than with @deepseek-ai/schemastery: this package installs into <profile>/node_modules, so it resolves modules from its own directory, and settings.installSection accepts any schemastery-compatible validator. A four-line validator over the single field this plugin owns keeps the package dependency-free and installable wherever DSH puts it.

The settings schema aside, the host half needs no dependency of its own; the only one it declares is @deepseek-ai/schemastery, because a real schema is not optional (see above).

Tests

node test/host-core.test.mjs   # 14 — the toggle's state machine and projections
node test/wiring.test.mjs      # 10 — namespace install, assembly waterfall, guard
node test/load.test.mjs        # 15 — the browser half against a fake DOM

Each file runs directly rather than through node --test, which spawns per-file children (unavailable under a confined sandbox).

The browser-half suite uses a React stub with real state and effects and a render loop that settles them. A no-op useState would let every assertion pass vacuously — the component would sit on its "reading…" placeholder forever and "no switch until the namespace answers" would be true for entirely the wrong reason.

Caveats

  • DOM append, not a slot. The switch is found by the official card's own data-plugin-item attribute and appended to the card head. A future release that renames that attribute needs the matcher updated.
  • Client-only presence. If the browser half fails to load, the switch disappears from the page but the Host effect stays exactly as stored.
  • The switch governs this deployment's process. A second server started from the same home reads the same settings.yaml but composed at its own boot.
  • One DSH quirk, unrelated to this plugin: an already-running session may still list web_search while the switch is off, but a call to it is refused by the guard rather than sent to the provider.