dsh-tool-picker
Manifest validPer-session switches for which tools a DSH conversation may call, plus a guest mode that keeps file tools inside the workspace and turns off shell and subagents.
dsh-tool-picker
给 DeepSeek Harness 加一个按会话控制模型能用哪些工具的小插件。
同一个 Harness 里,A 会话只给只读工具、B 会话放开全部、C 会话进「访客模式」只能碰工作区——互不影响。设置都落在磁盘上,重启不丢。
- Host 侧用
ctx.tools.restrict()/ctx.tools.guard()实现,作用于 agent 作用域上下文,所以工具列表(schema)、查找、执行三处同时生效——被关掉的工具模型根本看不见。 - 客户端是一个普通 DSH 插件:写作面板在输入框左侧,设置页多一个「访客模式」页面。
功能
1. 会话工具栏
输入框左侧的「工具」按钮打开面板:
- 逐项开关当前会话可用的工具(
read/write/edit/glob/grep/web_search…) - 顶部搜索框过滤工具名
- 「访客模式」快捷键(只影响当前会话)
- 底部显示
已启用 / 总数
只列出可被限制的工具:全局注册的工具与上层继承的工具。会话自己注册的工具不在此列(受限层管不到它们)。
2. 访客模式(workspace-only)
开启后:
- 关掉
bash/pwsh/shell/terminal/exec/subagent/workflow(子代理不继承守卫,所以整个禁掉) - 给
read/read_image/write/edit/glob/grep加路径守卫:只允许工作区内的路径
路径判断做了两层:先按字面 resolve() 比,再按 realpath() 比——所以工作区里放一个指向 C:\Windows 的 junction/软链接也穿不出去。越界时模型收到的是明确的拒绝理由,而不是一个含糊的失败:
访客模式:read 不能访问工作区之外的路径 "C:\Windows\win.ini"(工作区:E:\work\demo)
访客模式:glob 的模式 "../**/*.json" 可能越出工作区(工作区:E:\work\demo)
注意:这是工具层的守卫,与宿主沙箱模式(
read-only/workspace-write/danger-full-access)无关。开danger-full-access时它依然生效;但bash被关掉之前它管不住 shell 里跑出来的命令——这也是访客模式直接把 shell 类工具关掉的原因。
3. 模板
把一套工具开关 + 访客状态存成模板,一键套用到任意会话;支持导出/导入 JSON:
{
"version": 2,
"templates": {
"访客模式": { "tools": ["bash", "pwsh", "subagent", "workflow"], "guest": true },
"只读模式": { "tools": ["bash", "pwsh", "write", "edit"], "guest": false }
}
}
仓库里的 presets.json 就是一份可直接导入的样例。
4. 设置页:访客模式
设置左侧会多出一页「访客模式」:
- 新对话默认开启:新会话自动带访客模式,现有会话不受影响
- 按会话设置:列出侧边栏里的会话,逐个开关,不用一个个点开弹窗
已归档会话、子会话不出现;列表会去重并隐藏空会话。
5. 斜杠命令
不想点鼠标的话:
/tools 列出本会话工具与开关状态
/tools off write,edit 关掉指定工具(逗号分隔)
/tools on write 打开
/tools guest [on|off] 切换访客模式(不带参数则取反)
/tools default [on|off] 新对话是否默认开访客模式
/tools reset 清掉本会话记录,恢复全部工具
/tools templates 列出模板
/tools save <名字> 把当前状态存成模板
/tools apply <名字> 套用模板
/tools delete <名字> 删除模板
安装
通过插件管理器(推荐)
在 DSH 里让模型执行:
plugin_manager action=install_bundle target=<本地绝对路径 或 本仓库 npm spec>
或者手动把本包加进 profile 的 dsh.profile.bundles,让 loader 加载。
手动
# <profile>/cordis.patch.yml
- insert:
- id: dsh-tool-picker
name: 'dsh-tool-picker'
要求:
- 包必须是 ESM(
"type": "module") exports里必须带"./package.json"—— 缺了它,DSH 的客户端模块表会静默跳过整个客户端半身(不报错,面板就是不出来)dsh.bundle.patch指向cordis.patch.ymldsh.client.inject声明依赖的客户端服务包
数据
<profile>/data/dsh-tool-picker/state.json
{
"version": 4,
"defaults": { "disabled": [], "guest": false },
"sessions": {
"<sessionId>": { "disabled": ["write"], "guest": true }
},
"templates": {
"访客模式": { "tools": ["bash", "subagent"], "guest": true }
}
}
disabled记的是被关掉的工具名,不是允许列表——这样新工具默认就是开着的- 无会话记录时读到的是「全开、访客关」,不会继承
defaults(defaults只在会话启动时拷一份,避免老会话被全局开关牵连) - 写盘失败只记 warn,不阻塞会话
真相源:会话日志优先
这份 JSON 是缓存,不是权威。重开一个会话时插件先读它自己的日志:
session.requestHeader()折出最近一次请求里模型实际看到的工具名;- 与当前可限制集合相减,得到「被关掉的那些」;
- 有
request/header的会话(老对话、fork、从别的机器拷来的转录)按日志恢复,包括「一条都没关」; - 完全没有
request/header的会话才算新会话,此时才拷defaults。
request/header 与配套的 developer/message(里面记着 tool-removal / tool-addition 块)都是宿主自己写的,所以「模型看不到哪个工具」这件事天然可从日志重建——换机器、回滚 profile、删掉 state.json 都不会把工具偷偷放回来。访客模式本身是插件偏好(日志里没有),恢复时按「shell 与子代理全关、文件工具还在」这个特征反推。
实现说明
| 层 | 关键点 |
|---|---|
| Host | agent.ctx.tools.restrict({ deny }) 掩码该 agent 的工具;agent.ctx.tools.guard(fn) 做路径守卫。两者都是 context-owned effect,dispose 即撤销 |
| 会话生命周期 | 监听 agent/created(先按日志恢复,再退回 defaults)与 agent/disposed(清 disposer、计数) |
| RPC | 手写 typertRemote 绑定 + 原型上的 remote-methods 标记,走网关的 source descriptors 回退,无需代码生成 |
| 客户端 | ctx.remote.$mount(contribution) 挂自定义命名空间;槽位 conversation.input.left(面板)与 settings.section(设置页) |
| 持久化 | 直接读写 profile 下的 JSON(node:fs),不用 storage 服务 |
路径守卫怎么判
不维护字段名白名单:守卫递归走一遍这次调用的全部参数,跳过 content/text/prompt/old_string 这类正文字段,把其余「看起来像路径」的字符串(绝对路径、含分隔符、./..)逐个验证——词法上必须在工作区内,真实位置(跟随 junction / symlink)也必须在工作区内。所以别的插件带来的 notebook_path、target_file 一样拦得住,而在工作区里写一份提到 C:\Windows\win.ini 的文档不会被误伤。
失败与计数
tools.restrict()/tools.guard()抛错不再只写 console:错误记进该会话的degraded,面板顶部显示「部分设置没生效:…」,/tools输出里也会带一句。- 每次被守卫拒绝都会累加
blocked,面板与/tools显示「已拦截 N 次越界访问」,附带最后一次的拒绝原因——否则用户没法确认访客模式真的在干活。
兼容性
package.json 里现在声明了 engines:
"engines": {
"node": ">=22.12.0",
"dsh": "^0.2.0-rc.1"
}
DSH 的插件管理器读的是 engines.dsh(没有时才回退到 @deepseek-ai/* 的 peerDependencies),不满足会直接拦下安装/启用,并给出 nextStep;engines.node 只是给 pnpm 的提示。
范围取宽松的 ^0.2.0-rc.1(覆盖整个 0.2.x)。插件用到的 ctx.tools.view()、agent.ctx.tools.restrict()/guard()、session.requestHeader()、agent/created 事件在 0.2.x 早期就已存在;如果装到确实缺这些 API 的内核上,症状是「工具列表为空 / 状态恢复不出结果」,而不是崩溃——先关掉访客模式再排查。
已知限制
- 只能移除工具,不能凭空添加(
restrict的语义决定的) run_code不能点名限制(PTC 模式的传输层工具,点名会抛错)- 会话自己注册的工具不受控(它们不在可限制集合里)
- 子代理不继承父会话的守卫,所以访客模式直接禁掉
subagent/workflow - 访客模式只是工具层约束:工作区外读取被拦,但沙箱模式本身仍按 DSH 设置走(想更彻底可以同时把会话沙箱切到
workspace-write/read-only) - 客户端的 bundle 在宿主启动时快照:改了
client.js必须重启宿主,刷新页面无效
开发
node smoke.mjs # 假 ctx 跑一遍 state/setEnabled/setGuest/templates/命令/守卫/日志恢复
node --check client.js
smoke.mjs 用伪造的 ctx(tools/agents/provide/on/inject)验证 Host 侧逻辑,不需要启动 DSH。
License
MIT © 2026 ptilroko
English
A small DeepSeek Harness plugin for per-session tool control.
Each conversation gets its own allow/deny state: toggle tools from the button left of the composer, or switch on Guest mode to confine every file operation to the session workspace (shell and sub-agents are disabled, since tool guards are not inherited by child agents).
State lives in <profile>/data/dsh-tool-picker/state.json; templates can be exported/imported as JSON. Host side is ctx.tools.restrict() + ctx.tools.guard() on the agent-scoped context, so hidden tools disappear from the schema, from lookup, and from execution at once.
Comments
Loading…
Similar plugins
Personal toolbox for the dsh web profile with per-feature toggles: task-done desktop notifications, one-click restart, session deletion, plugin enable/disable, update checks, and UI utilities.
★ 0
↓ 137/wk
dsh plugin --profile web add dsh-toolsPersonal toolbox for the dsh web profile with per-feature toggles: task-done desktop notifications, one-click restart, session deletion, plugin enable/disable, update checks, and UI utilities.
★ 0
dsh plugin --profile web add dsh-toolsby Ryuu-64
Gives the agent four session tools: start a new session, send a message into another one, list sessions with their state, and wait for another session's turn to finish so its answer can be read back.
★ 0
MIT
JavaScript
Oct 8, 2026
dsh plugin --profile web add @ryuu-64/dsh-session-toolsby kmyy350234-dz
Keeps heavy tool groups out of every request and brings them back on demand, by keyword, or automatically at session start.
★ 3
MIT
JavaScript
Oct 30, 2026
dsh plugin --profile web add dsh-tool-drawerby weibaohui
dsh 插件 · 目录共享:任意可配置目录经 HTTP 在线浏览/上传/下载 + 对话框 @ 文件给 agent 处理
★ 1
MIT
JavaScript
Oct 29, 2026
dsh plugin --profile web add @weibaohui/dsh-file-shareby 9Ashwin
DSH plugin: a model-facing rename_session tool, so the agent can retitle a session from inside the conversation.
★ 0
MIT
JavaScript
Sep 13, 2026
dsh plugin --profile web add dsh-session-rename