DeepSeek Harness Plugin Hub

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

探索

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

社区

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

相关链接

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

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

Tool Gateway — DeepSeek Harness 插件(DSH Plugin)
← Plugins
T

dsh-tool-gateway

Tool Gateway

把 DSH 的工具目录收成三个元工具:find_tools 查、call_tool 调、call_tools 批量调;其余工具不出现在模型可见目录里,但仍然可被调用

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

npx -y @deepseek-ai/dsh plugin --profile web add github:leolee9086/dsh-tool-gateway#682d90c4793744ebdc7243060d147375221ab347
README兼容性版本

兼容性与来源证明

Tool Gateway 以 dsh-tool-gateway 发布,当前版本为 0.2.1。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

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

版本

0.2.1stable
2026/9/25
0.2.0stable
2026/9/25
0.1.1stable
2026/9/22

相关插件

正在加载相关插件…

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

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

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

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

认领这个 Plugin →
报告问题
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录

README

dsh-tool-gateway

把 DSH 的工具目录收成三个元工具:find_tools 查、call_tool 调、call_tools 批量调。 其余工具不出现在模型可见的工具表里,但仍然可以被调用 —— 只是入口变成了这几个。

另外两件配套的事:硬禁用名单(某些工具在任何调用路径上都不许执行),以及右侧栏的 工具箱面板(随时关掉某个工具 —— 关掉之后守卫拒绝、检索目录里也查不到它)。

它解决什么

工具一多,真正的挑战不是上下文长度,而是模型会不会只盯着被截断的那份工具列表将就。 表越长越容易挑错;表被截断,模型就会忘掉那些没露面的工具,转而用几个熟面孔凑合。

这个插件把工具目录收起来,逼出"每一次动手之前先查清楚有什么"这个动作: 那份长列表不在眼前了,模型只能先 find_tools 问、再 call_tool 调。

顺带解决的一件更贵的事:前缀缓存

工具定义在每次请求的最前部,所以工具表一变,整段前缀缓存就废了 —— 重新计费、首字延迟变长。 而工具表恰恰是最容易变的那一块:接一个 MCP server、插件热插拔、换一个 preset 组合,都会改它。 工具越多,这件事越贵。

网关把模型可见的工具表恒定成那三个元工具:

  • 装插件、接 MCP、用面板关掉某个工具 —— 模型可见的工具表一个字节都不变。 变的只是 find_tools 能检索到什么、以及守卫放不放行。
  • 想真正改工具表,只有动这三个元工具本身(call_tools 在 PTC 开着时会自动不挂)。

这也是面板只用「拒绝」、不用 restrict() 的原因:后者会改模型可见工具表,那才会毁缓存。

三个元工具

工具干什么什么时候用
find_tools按名字、描述、中文或拼音检索,返回完整参数 schema不知道有什么工具、或不知道参数怎么写
call_tool调一个工具,参数 {tool_name, arguments}一次调一个
call_tools写一段程序批量调用工具要连着调好几个,而中间结果不必进对话

call_tools 干活的是一段模型写的 TypeScript 程序(DSH 的 PTC 运行时负责跑它)。 程序里只有两个函数可调:tools.find_tools({query}) 与 tools.call_tool({tool_name, arguments})。 只有程序 return 的值会回到对话里 —— 读十个文件、跑十次搜索,中间那些内容不必挤进上下文。

它的参数、输出与界面呈现都对齐 DSH 官方 PTC 模式的 run_code:description 必填 (那是卡片标题,也是审批弹窗里给人看的第一行字)、控制参数只在运行时真的支持时才出现、 输出是结构化的 {logs, result?, sandbox?}、失败是带 kind 的 CodeRunFailedError。

唯一刻意不一样的地方:官方 run_code 的 SDK 段落是遍历注册表生成的(把一百多个工具 的名字和参数全喂给模型),我们这里是手写的两条声明 —— 那正是这个插件要消掉的东西。

什么时候不挂 call_tools

  • PTC 模式已经开着(mode: 'ptc' 或 'both'):那时 DSH 自己的 run_code 就在工具表里, 再加一个功能重叠的只会让模型困惑该用哪个(而在 mode: 'ptc' 下它本来也调不到 —— 那个模式的执行守卫只放行 run_code)。
  • 部署里没挂 PTC 运行时(ctx.ptcRuntime 缺席):挂了也跑不了。

检测用公开的 tools.schemas(agent) 看 run_code 在不在可见集里 —— 那正好是 modeFor(scope) !== 'native' 的判据。检测按会话算(preset 可以用 tools.presentAs() 单独选一个呈现模式),读不出来时保守地不挂:少一个能力不等于出错, 而抢 PTC 的位置会真的出错。

装

dsh plugin --profile web add github:leolee9086/dsh-tool-gateway#v0.2.1

dsh plugin --profile <name> <args...> 在 profile 目录里转发给 pnpm。装完它会依据本包 package.json 的 dsh.bundle.patch 声明,把本包追加进 dsh.profile.bundles —— 于是每次启动自动插入加载行,不需要手工编辑 profile 的 cordis.patch.yml。

本包的 lib/ 随仓库提交,所以从 git 安装不需要 pnpm 的构建授权(allowBuilds): 拉下来就是能直接加载的产物,包里没有 prepare 脚本,安装时不会在你机器上跑构建。 想锁得更死,把 #v0.2.1 换成具体的 commit sha。

重启 DSH 生效。不需要改任何 preset —— 它挂在 profile 层(根作用域)一次, 对所有 preset、所有会话生效。

(本地开发时也可以用 file:// 直接引用源码目录的 lib/host.js, 客户端半边仍然走 package.json 的 exports["./client"] 与 dsh.client 声明。)

会话级开关

会话标题栏上有一个 chip(工具箱 开 / 工具箱 关),点一下切换这个会话 要不要施加约束。默认是开 —— 装上这个插件就是要约束行为;关掉是你在某个会话里的显式选择。

  • 状态持久在 $DSH_HOME/storages 下的 tool_gateway/sessions 里,重启后仍然生效。
  • 子代理会话跟随父会话:你只看得见父会话,而子代理的工具组合本来也是从父那里继承的。
  • 关掉之后:模型看到完整工具表、可以直接调用任何工具、系统提示里也不再出现那段使用说明。 元工具仍然在(它们只是不再是唯一入口)。
  • 生效时机:执行守卫立刻生效(下一次工具调用就放开或拦住); 工具表与提示段落从下一轮请求开始生效(装配发生在每一轮开始的时候)。
  • 切换时会往会话里注入一条通知给模型看。 模型看不见"装配"这件事本身,没有这条通知, 它只能在"我明明刚用过 read、怎么现在调不了了"的困惑里自己猜。通知走 agent.inject(), 作为一条 user 消息进会话日志(所以模型可见的东西仍然可以从日志重建),不唤醒 driver: 点完开关再发一条消息,模型就在同一次请求里看到它。 通知说的是"刚刚变了什么"(事件),系统提示里那段说的是"现在该怎么用"(状态), 所以两者措辞不同、不会互相替代。状态没变时不写库也不通知 —— 连点两下或者页面重复提交不该在会话里堆两条"模式已关闭"。

没有 webServer / connection 服务的部署(比如 headless)不会挂这个界面, 网关本身照常工作,开关固定为默认的"开"。

工具箱面板

右侧栏底部的「工具」按钮打开一个面板,里面是这个会话能看到的全部工具 —— 包括被网关 收起来的那些。每个工具一行、一个开关。

  • 关掉一个工具 → 它从 find_tools 的结果里消失;模型就算照着历史去调也会被守卫拒绝, 拒绝理由里写清了「被关掉了、去哪儿打开」。同时会给这个会话注入一条通知,告诉模型刚刚 变了什么(同一条通知机制,见上一节)。
  • 按会话记:关掉一个工具是「这次别用它」的决定,换一个会话该不该用它是另一个决定。 状态就在工具箱模式那份记录的 disabledTools 字段里,同一张表、同一条 ownerSessionId 归属规则(子代理会话里关掉的工具记在父会话名下,通知发给正在看的那个会话)。
  • 三个元工具不给关(关了就没有工具入口了);config.ban.deny 里的名字也不给点, 那是配置说了算。
  • 会话 id 由页签 seat 直接交给正文,面板不需要问用户在哪个会话。

为什么加它不会拖慢前缀缓存:网关开着的时候,模型可见的工具表里只有那三个元工具。 开关一个被收起来的工具,改的是目录索引(find_tools 检索到什么)与守卫的判定, 两处都不在请求最前部那段工具定义里,所以整段前缀缓存不受影响。这也是面板只用「拒绝」、 不用 restrict() 的原因 —— 后者会改模型可见工具表。

硬禁用名单

一部分工具在任何会话、任何调用路径上都不许执行。它和「网关没让它露面」是两件事:

拦住模型直接调用拦住 call_tool 的子分发
被网关收起来拦住不拦(那正是元工具的用途)
被硬禁用拦住拦住

默认禁用的是 ask_user_question(选择题卡片那一类交互)。它由同一条守卫的第一段判断 实现 —— 排在 parent 那条判断之前,所以连 call_tool 也绕不过去。

这一块原本是独立插件 dsh-tool-ban,现在并进网关:两者用的是同一个 ctx.tools.guard, 分成两个包只会让「拒绝理由谁先谁后」变成两个包之间的隐式约定。它的可见性屏蔽(restrict) 那一半也一并搬来了 —— 网关被关掉的会话暴露完整工具表,那一半在那里才有用。

配置里写 ban: { deny: [] } 表示这次一个都不禁(显式写空数组才算数;完全不写 deny 才用默认名单)。

它怎么工作

两层,缺一不可:

  1. 可见性 —— 在 system-prompt/assemble 瀑布里过滤 assembled.tools, 模型可见的工具表只剩那几个元工具(具体几个按会话算,见上)。这一步不碰注册表。
  2. 执行守卫 —— ctx.tools.guard() 拒绝模型对其它工具的直接调用。 少了这一层,模型只要从历史里记得某个工具名、或者猜中一个,直接调用就会成功 —— 注册表里那些工具一直都在。

放行条件是"子分发"(execution.parent !== undefined)而不是"名字在白名单里": call_tool 内部发起的调用带着 parent,模型直接发起的没有。这与 DSH 自己 PTC 模式的 塌缩是同一种机制 —— 限制调用路径,不限制可见性。

call_tool 走 ctx.tools.execute(),也就是注册表的公开执行入口:审批、守卫、 沙箱策略、会话日志一步不少,与模型直接调用一个工具没有任何区别。

文件

文件职责
src/host.js入口:接线,不实现任何一件具体的事。keepFor(agent) 在这里(含 PTC 检测)
src/catalog.js检索索引(jieba 切词 + 拼音 + minisearch 倒排)
src/meta-tools.jsfind_tools / call_tool,以及三个元工具共用的 invokeTool
src/code-tools.jscall_tools:把一段程序交给 PTC 运行时,只绑两个函数
src/gateway.js可见性过滤、执行守卫(硬禁用先判)、提示段落,都按 keepFor(agent) / enabledFor(agent) 分支
src/ban.js硬禁用名单:静态名单的判定 + 按 agent 抹掉模型可见工具表(原 dsh-tool-ban)
src/deliver-context.js子调用带回来的非文本内容(图片等)怎么送到模型眼前
src/session-key.js一个 agent 的开关记在哪个会话名下(子代理跟随父)
src/switch-state.js开关状态与工具名单的读写与持久化(默认:约束开、一个都不禁)
src/route.js给浏览器用的 HTTP 接口:会话开关(chip)+ 这个会话的工具清单与工具开关(面板)
src/producer-source.js本插件在 V4 会话里的消息源归属(写错会让整轮毫秒级静默失败)
src/switch-notice.js开关变化时写给模型的那条通知(两种粒度:整个工具箱模式、单个工具)
src/client.js浏览器半边:会话标题栏的 chip + 右侧栏的工具箱面板

检索

find_tools 支持中文、英文、拼音全拼、拼音首字母:

查询命中
zhihu / 知乎知乎相关工具
sousuo / zhanneisousuo描述里含"搜索"的工具
tupian图片相关工具
搜索描述里含该词的工具

实现是 jieba 搜索引擎模式切词 + 按词分段的拼音 + minisearch 倒排索引。 索引按 agent 惰性构建(preset 注册的工具在 agent 自己的作用域层,全局视图看不到它们), tools/change 时失效重建。

实测结论(包括踩过的坑)记在 docs/检索栈实测.md。

配置

- insert:
    - id: tool-gateway
      name: "dsh-tool-gateway"
      config:
        maxResults: 5      # find_tools 一次返回几条,默认 5
        ban:
          deny: [ask_user_question]  # 硬禁用名单;显式写 [] 表示一个都不禁
          hide: true                 # 还要把被禁的工具从模型可见表里摘掉
          # reason: "…"              # 拒绝理由(会被模型读到),不写用内置那条
          # maxHideAttempts: 5       # 可见性摘除的重试上限

边界

  • 不动会话历史。 不改写、不压缩、不按历史分档、不扫会话事件判断状态。 已经跑过一段的会话里那些直接调用 read、bash 的记录原样留着, 说明里讲清楚了"入口变了",模型会遵守。
  • 非文本内容(图片等)用 agent.inject 投递,不用 deferContext。 后者把上下文并进 结果的 additionalContexts、由 agent loop 暂存进「下一步的 inbox」再以 agent/inbox/spliced 事件 splice 进会话;实测它一次都没落地 —— 会话里从来没出现过那种事件,工具返回的图 只留在 tool/ptc-dispatch 的审计记录里(surface=log-only),模型永远看不到。 改用的 agent.inject 正是工具箱开关通知走的那条路,实测能到模型眼前。 两条通道的取舍写在 src/deliver-context.js。
  • 注入的消息用生产者自己的来源 kind。 往会话里追加消息时 source.kind 必须是 plugin:dsh-tool-gateway —— V4 起消息源归生产者所有,退役的 { kind: 'plugin', plugin } 会被会话准入直接拒绝。症状很隐蔽:那一轮在毫秒级失败、错误码是 UNKNOWN、 会话日志一个字节都不写,重试每次死在同一处。两个发出点(开关通知、工具结果的 非文本补充)共用 src/producer-source.js,kind 只在那一处拼。
  • 开关状态不写会话日志。 它不是"懒"而是"不能":Session.append 没有写 ignorable 标记的通道,而读者遇到不认识的、没有该标记的事件必须拒绝重建整个会话。 所以开关状态走 storage domain($DSH_HOME/storages),与会话日志无关。 通知是另一回事:它作为一条合成 user/message 进日志(模型可见的东西因此仍然可重建), 所以它的 source.kind 必须归生产者所有 —— 见上一条。
  • 覆盖子代理。 子代理加入父方的组装、走同一条装配瀑布,全局监听器与根作用域守卫 都生效;开关也跟随父会话。父方通过 toolFilter 限制掉的工具,子代理既查不到也调不到。
  • 过滤出错时降级:白名单一个都没匹配上就放行完整目录并告警一次 —— 工具多一点只是浪费,会话起不来是事故。
  • 不处理非文本块的内容:call_tool 把图片等非文本块经 deferContext 作为独立 上下文送出,不做转码或裁剪。call_tools 的子调用也一样(程序里调到的图片会附在那次结果之后)。
  • call_tools 的程序里能调到的,就只有那两个函数。 绑定只给 tools.find_tools 与 tools.call_tool —— 程序不能直接写 tools.read({...})。 这不是限制调用(子调用走的还是注册表的公开执行入口,审批、守卫、沙箱一样不少), 而是不让工具目录再被喂回模型:官方 PTC 的 SDK 段落会列出全部工具的签名, 我们这段只有两行。
  • 程序结束就中止还在飞的子调用。 程序写了 tools.call_tool(...) 却没 await, 那个调用不会在结果返回之后继续跑下去 —— 它会随这次运行一起停掉,而且会等它 收完尾才把结果交出去(不等的话,它的会话日志会落在这次调用之后)。
  • 依赖三个包:@node-rs/jieba(原生模块)、pinyin-pro、minisearch。前两个都是 预编译分发、无 install 脚本、零运行时依赖树。

开发

pnpm install
pnpm test          # 164 个测试
pnpm build         # 校验后逐字节拷贝 src/ → lib/

lib/ 随仓库提交,安装方不需要在自己机器上跑构建。 客户端半边(src/client.js)是手写的 window.__ModuleLoader__.load 自注册脚本, 构建只做语法校验、不转换 —— 它由 web server 原样下发。

相关插件

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

Acp App@deepseek-ai/dsh-acp-appdsh ACP 配置文件包:基于 dsh-base 的仅限自动化的 JSON-RPC stdio 和进程生命周期管理Remote Web Ui@linxin666/dsh-remote-web-ui通过扫码配对访问 dsh Web GUI,共享一个官方界面:设置按钮旁的二维码可将手机和 PC 配对到同一个 Web GUI(手机采用竖屏触控适配层,PC 使用完整桌面界面),通过一次性令牌和 rUniver Officedsh-univer-officeDSH × Univer 集成,内置协作 Gateway 和 Viewer:内联预览、实时浮动 Worktree 窗口,以及 DeepSeek Harness 中的会话结束审查操作。DSCODE@toddzheng024/dscode-bundle完整的 DeepSeek 编码代理,支持持久化 shell、Ultra 协作和自动权限审查。