一个截图工具,外加两个默认关闭的自动截图时机。需要模型支持图片输入。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-screenshot-feedback-hook-mcp
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:lkh081231/screenshot-feedback-hook-mcp#path:/dsh-plugin
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
English | 中文
一个 DeepSeek Harness 插件:让 agent 看到自己刚产出的真实画面 —— 前端页面、EasyEDA/CAD 工程图、任意桌面应用 —— 并据此自我纠正。
它是 screenshot-feedback-hook-mcp 的 dsh 那一半;截图与压缩仍然全部由 Python 包负责(mss + Pillow,Windows/Linux/macOS 通用),本插件只负责 dsh 这一侧的接线。

为什么要做原生插件,而不是直接桥接 Claude Code hook
Claude Code 的 hook 只能回传文本,能做到的极限就是把文件路径丢回去、指望 agent 自己去读。dsh 有持久图片附件服务,所以原生插件可以把截图提交进附件库、再作为真正的图片块送进对话 —— agent 什么都不用做。
| 路径 | 图片怎么到达模型 |
|---|---|
take_screenshot 工具 |
工具结果里直接带图片块 |
| 工具执行后(默认关) | 截图作为附加上下文随工具结果一起进上下文 |
| 轮次结束时(默认关) | 截图被 steer / inject 进下一步 |
前置条件
- dsh
v0.1.0-rc.8或更高 —— 本插件用到的每个 API 都是对着这个 tag 核对过的。 - PATH 上有 pnpm ——
dsh plugin是转发给它执行的。 - 一个能跑
capture --json的screenshot-feedback-hook-mcp>= 0.3.0 —— 截图与压缩都由它做,装法见下面三条路。更早的版本没有这个子命令,插件会识别出来并点名让你升级。 - macOS 上的屏幕录制授权 —— 而且未授权不一定会被检测出来,信任截图之前先读平台注意事项。
- 一个支持图片输入的模型 —— 见支持图片的模型。没有的话插件会拒绝截图,并说明怎么换。
怎么把截图 CLI 装上
插件对这个 CLI 的唯一接触点是配置里的 command / args 两个字段(见配置),它拿 command 去 PATH 上查一次可执行文件,仅此而已。所以下面三条随便挑一条,uv 不是硬性依赖,只是默认那条路。
A. 装 uv —— 默认配置直接可用,推荐
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
装完什么都不用改:默认就是 command: uvx + args: ['screenshot-feedback-hook-mcp'],uvx 会按需从 PyPI 拉包(首次需要联网)。
uv 自己不需要 Python —— 它是独立二进制,会按需下载解释器。所以对「机器上还没有可用 Python」的人,这条反而门槛最低;B 那条要求你先有 Python。
B. 已经有 Python,想常驻安装
pipx install screenshot-feedback-hook-mcp
# 或 uv tool install screenshot-feedback-hook-mcp
然后改配置。后应用的层会替换整个 config,所以要重述你想要的所有键:
# ~/.dsh/profiles/<name>/cordis.patch.yml
- id: screenshot-feedback
name: dsh-screenshot-feedback-hook-mcp
config:
command: screenshot-feedback-hook-mcp
args: []
monitor: 0
C. 装在 venv 里,或者根本不想动 PATH
command 直接填绝对路径,其余同 B:
command: C:\Users\you\proj\.venv\Scripts\screenshot-feedback-hook-mcp.exe
args: []
command/args/cwd不在设置卡片上 —— 它们决定去哪里找可执行文件,属于部署事实而不是用户偏好。所以走 B / C 必须编辑上面那个 YAML 文件,在设置页里点不出来。好在装错了给的信息是可执行的:命令找不到时插件会直接告诉你「装 uv,或者 pipx 装完把
command改成什么、args清空」;装了旧版 Python 包时会点名让你升级,而不是丢一段 argparse usage 让你自己猜。
安装
本插件已经收录进 awesome-dsh-plugin(vision 分类),可以直接从 dsh-market 里装 —— 市场卡片现在给的就是下面这条 npm 写法。
dsh plugin --profile web add dsh-screenshot-feedback-hook-mcp
dsh web
然后让 agent「截个图,告诉我屏幕上是什么」。用别的 profile 就把 --profile 换掉。
从 GitHub 安装,以及为什么不能直接指向源码目录
想装未发布的 main 分支就用 git 写法,这条也能用:pnpm 会克隆仓库、跑本包的 prepare 脚本把 lib/ 构建出来,再按打包结果安装。
dsh plugin --profile web add github:lkh081231/screenshot-feedback-hook-mcp#path:/dsh-plugin
pnpm 10 起对构建脚本设了闸门。 首次安装(构建产物还没进 pnpm 的内容寻址 store)可能停在
ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED。在~/.dsh/profiles/<name>/pnpm-workspace.yaml的onlyBuiltDependencies里放行即可 —— 照着报错里打印的那一行原样抄,然后重跑上面的命令。构建产物进了缓存之后就不再过这个闸门。
不要让 dsh plugin add 指向源码工作区。 dsh plugin --profile web add ./screenshot-feedback-hook-mcp/dsh-plugin 会把这个目录装成 pnpm 的 link:,它会以两种方式失败:
lib/是构建产物,不在 git 里。 pnpm 不会为 link 依赖跑prepare,于是main: "lib/index.js"指向一个不存在的文件,dsh 启动即崩:dsh: plugin tree failed to load: ... Cannot find module '~/.dsh/profiles/web/node_modules/dsh-screenshot-feedback-hook-mcp/lib/index.js'就地构建能解决上一条,但会引出更糟的问题。 Node 按真实路径解析 link 过来的包,所以
npm install之后落在dsh-plugin/node_modules/里的那几份@deepseek-ai/dsh-*会盖住宿主的 —— 正是从 0.1.0 升级那节讲的双实例故障,它会让该 profile 里所有工具调用挂掉,不止截图。
要从 checkout 安装,先打包,让只有 files 白名单里的东西进 profile:
git clone https://github.com/lkh081231/screenshot-feedback-hook-mcp.git
cd screenshot-feedback-hook-mcp/dsh-plugin
npm install && npm run build && npm pack
dsh plugin --profile web add ./dsh-screenshot-feedback-hook-mcp-0.2.1.tgz
不管走哪条路,装完都确认 ~/.dsh/profiles/<name>/node_modules/@deepseek-ai/ 下没有任何 dsh-* 包 —— 那里只该有 schemastery 和 cosmokit。
每个版本改了什么行为,见 变更记录。
从 0.1.0 升级(必看)
0.1.0 会把 dsh 的运行时包当成普通依赖装进 profile,在 profile 里造出第二份 @deepseek-ai/dsh-tools,盖掉 dsh 自己那份。结果不只是截图不能用 —— 该 profile 里任何工具调用都会崩:
Cannot read properties of undefined (reading 'prepare')
0.1.1 起改成 peer 依赖,不会再往 profile 里装任何 dsh 包。已经装过 0.1.0 的,把被污染的 node_modules 一并清掉再装:
dsh plugin --profile web remove dsh-screenshot-feedback-hook-mcp
rm -rf ~/.dsh/profiles/web/node_modules
dsh plugin --profile web add dsh-screenshot-feedback-hook-mcp
装完确认一下 ~/.dsh/profiles/<name>/node_modules/@deepseek-ai/ 里没有任何 dsh-*(只该有 schemastery 和 cosmokit)。
它是怎么注册进 dsh 的
本包是一个 dsh 组合包(bundle) —— 一个附带配置层的 npm 包,不需要你手写任何 patch。它靠 package.json 里的 dsh.bundle manifest 声明自己贡献什么:
{
"name": "dsh-screenshot-feedback-hook-mcp",
"main": "lib/index.js",
"files": ["lib", "cordis.patch.yml"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
cordis.patch.yml 就是那一层,按包名引用插件模块(不是相对路径,否则 Node 解析不到已安装的代码):
- insert:
- id: screenshot-feedback
name: dsh-screenshot-feedback-hook-mcp
config:
command: uvx
args: ['screenshot-feedback-hook-mcp']
monitor: 0
dsh plugin --profile <name> add ... 会在 profile 目录里转发给 pnpm 装包,认出 dsh.bundle 后把包名追加进该 profile 的 dsh.profile.bundles:
{
"name": "dsh-profile-web",
"dependencies": { "dsh-screenshot-feedback-hook-mcp": "..." },
"dsh": { "profile": { "bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-screenshot-feedback-hook-mcp"
] } }
}
启动前先只验证这一层,不真的跑起来:
dsh --profile web --dump-config # 应当出现 `# == dsh-screenshot-feedback-hook-mcp` 层与 id: screenshot-feedback 那一行
卸载:dsh plugin --profile web remove dsh-screenshot-feedback-hook-mcp,依赖和对应的层会一起消失。
生效配置的层顺序是:各组合包的 patch(按 dsh.profile.bundles 顺序,@deepseek-ai/dsh-base 在最前)→ profile 自己的 cordis.patch.yml → home 级 $DSH_HOME/cordis.patch.yml → 每个 --patch overlay。所以你可以在自己 profile 的层里覆盖本包的行,不用改这个包。
配置

装好之后日常调参在设置 → 插件 → 插件配置 → 截图反馈那张卡片上,它写的是
$DSH_HOME/settings.yaml,叠在组合层之上、免重启。卡片上每个字段都标注是否被
你覆盖过,并且能一键重置回组合层的值。
组合包插入的那一行 id 是 screenshot-feedback,适合放固定的部署事实。要改它,
在自己 profile 的 $DSH_HOME/profiles/<name>/cordis.patch.yml 里写一行同 id 的
配置。后应用的层会替换整个 config,所以要重述所有你想要的键:
- id: screenshot-feedback
name: dsh-screenshot-feedback-hook-mcp
config:
command: uvx
args: ['screenshot-feedback-hook-mcp']
monitor: 1
autoAfterTools: true
autoAfterToolsDelayMs: 2000
| 字段 | 默认 | 卡片上可改 | 说明 |
|---|---|---|---|
command |
uvx |
截图可执行文件。用 pipx / uv tool 装过的,改成 screenshot-feedback-hook-mcp 并清空 args。 |
|
args |
['screenshot-feedback-hook-mcp'] |
置于子命令之前的固定参数。 | |
cwd |
'' |
子进程工作目录;留空用 host 的 cwd。 | |
monitor |
0 |
✓ | 0 = 全部显示器拼接,1..N = 单屏。编号用 list_monitors 查。 |
delayMs |
0 |
✓ | 手动截图前的等待,等页面 / 工程图渲染完成。 |
maxEdge |
1568 |
✓ | 最长边像素。不要超过 2000,附件库会拒绝更大的图。 |
targetKb |
80 |
✓ | 字节预算。dsh 没有 Claude Code 那条 25k token 的 MCP 输出上限,要看清细节可以调大。 |
captureTimeoutMs |
30000 |
✓ | 单次截图超时(在等待时间之外另算)。 |
warnOnTextOnlyModel |
true |
✓ | 闸门拒绝时提示该怎么办(纯文本模型 / 路由解析不出来)。每种原因每会话一次。 |
autoAfterTools |
false |
✓ | 命中的工具执行完就截图。 |
autoAfterToolsMatcher |
edit|write|str_replace_editor |
✓ | 工具名匹配。纯 `[A-Za-z0-9_ |
autoAfterToolsDelayMs |
1500 |
✓ | 自动截图前的等待。 |
autoOnTurnStop |
false |
✓ | 轮次即将结束时截图。 |
autoOnTurnStopDelayMs |
1500 |
✓ | 自动截图前的等待。 |
autoOnTurnStopSteer |
true |
✓ | true = steer 让模型再跑一步看图;false = 只 inject 进上下文。 |
command / args / cwd 刻意不上卡片:它们决定去哪里找可执行文件,属于部署
组合,不是用户偏好。改 config 会触发 HMR 热替换,改卡片则连热替换都不需要——
插件每次触发都重读配置。
设置页那张卡片是怎么接上去的
dsh 的插件配置标签页渲染的是两份账本的交集:Host 服务了哪些 settings 命名
空间,以及浏览器里有哪些卡片注册在这些键上。所以这个包同时提供两半,用同一个
命名空间 screenshot-feedback 配对:
- Host 半侧(
src/index.ts)用@deepseek-ai/dsh-settings的installSettingsSection注册命名空间,把cordis.yml那一行当作组合层base, 并把配置读取器指向解析后的 scope。没挂 settings 服务时它自动退回组合层, 行为与从前完全一致。 - 浏览器半侧(
src/client/)把一张 React 卡片注册进settings.plugin.item这个 keyed slot,键就是同一个命名空间。它经ctx.settingsScope读写,写入用 读取时的 revision 设栅,所以已经和文档脱节的表单会被拒绝而不是覆盖并发改动。
浏览器半侧靠 package.json 的 dsh.client 声明被发现,产物是 lib/client.js:
{
"exports": { "./client": { "default": "./lib/client.js" } },
"dsh": { "client": { "platform": "web", "inject": ["@deepseek-ai/dsh-client-ui-settings-plugins"] } }
}
[!NOTE] dsh 官方产出这种 bundle 的
clientBundle预设没有发布到 npm(官方 README 把这条列为已知限制),所以本包在 tsdown.config.ts 里自己复刻 了那份产物契约:lazy-CJS 闭包工厂、window.__ModuleLoader__.load的 banner/footer、以及只让模块表里那几个 specifier 保持require()。升级 dsh 时 要跟着复核packages/client/tsdown.client.ts与packages/client/web/src/platform.ts。
关于两个自动时机
两个默认都关:每张截图都会一直跟着后续每次请求走,直到发生压缩。
autoAfterTools挂在tools/post-execute,把截图挂到工具结果旁边。它不可能死循环,并且会跳过失败的工具调用(那时候的画面说明不了任何事)。autoOnTurnStop挂在agent/turn-stopping。在那里 steer 会强制模型再跑一步、再次回到同一个停止边界 —— dsh 的 Claude Code hook 桥接把stop_hook_active恒置为false且没有连击上限,所以照搬的 Stop hook 会让 agent 无限续跑。本插件按payload.turn去重:一个 turn 最多截一次。
截图失败绝不会阻断任何东西 —— 只记一条日志,工具流水线和轮次照常走。
平台注意事项
截图本身在 Python 包里,所以下面这些对它的每一个前端都成立,本插件也不例外。
- Windows:开箱即用。
- Linux:X11 开箱即用。纯 Wayland 下
mss受限;CLI 会探测会话类型,每张截图都带上这条告警送给模型。注意它只看XDG_SESSION_TYPE=wayland,所以 XWayland 下截图其实正常时也照样提示。 - macOS:在「系统设置 → 隐私与安全性 → 屏幕录制」里勾选运行 dsh 的终端/IDE,然后完全退出并重启该应用。未授权时截到的不是黑屏,而是壁纸 + 菜单栏 —— 其他应用的窗口一个都不在图里。
[!WARNING] macOS 未授权不一定会被检测出来,请先手动验证一次。
现在的检测是事后启发式:把画面缩到 16x16 看灰度极差,只有接近纯色(纯黑、纯色壁纸)才会触发提示。照片壁纸下不会触发,而那正是 macOS 的默认样子。
也就是说模型可能收到一张「看起来完全正常的桌面截图」、一句告警都没有,而它要看的窗口根本不在图里。这比黑屏更糟:模型会以为是自己的页面没渲染出来,跑去 debug 一份没问题的代码。
所以在打开两个自动时机之前,先手动跑一次并亲眼看一下:
uvx screenshot-feedback-hook-mcp capture --out shot.jpg图里有你的窗口,就说明授权到位、下面的一切都能正常工作。确定性的检测(
CGPreflightScreenCaptureAccess(),与画面内容无关)已列入计划,但需要一台 macOS 机器才能验证。
触发了的环境告警是会送到模型面前的:它们跟在截图的 warnings 里,渲染成图片旁边的一行 <warnings>,所以模型看到的是原因,而不只是一张坏掉的图。
支持图片的模型
dsh 只有在当前这条确切路由声明了图片输入(ctx.llm.resolveModelInfo(...).inputModalities)时才会把图片放进对话 —— 和内置 read_image 工具是同一道闸。在 dsh v0.1.0-rc.8 上,内置的 deepseek-official 路由只公布 deepseek-v4-flash 和 deepseek-v4-pro,两者都是纯文本模型。
[!IMPORTANT] 设置页声明不了模态。「设置 → 模型」的模型卡片只能编辑
id/ 名称 / 上下文窗口 / 最大输出,没有模态字段 —— 在那里新加的模型一律按纯文本处理,本插件会拒绝截图。加完自定义模型后,请点该页的**「打开配置文件」**,在settings.yaml里手工给这条模型补上input: [text, image](llm-deepseek下的字段名是inputModalities: [text, image])。手写的字段不会被之后在设置页里的编辑抹掉。
拿到支持图片的路由有三条路:
在设置 → 模型里添加 Anthropic / OpenAI 等 catalog provider,选它的视觉模型。
自定义 provider 在
$DSH_HOME/settings.yaml里声明模态:llm-pi-ai: providers: my-gateway: apiKeyEnv: GATEWAY_API_KEY api: openai-completions baseURL: https://gateway.example/v1 models: - id: vision-model input: [text, image]catalog provider 改用
modelOverrides.<模型id>.input;整条路由可以用defaultInput: [text, image]兜底。如果你的 DeepSeek 端点确实提供视觉模型,在
llm-deepseek.models里给它加inputModalities: [text, image]。
这些字段是对你端点的断言,端点本身不支持的话不会因此变得支持。
不用这个插件的其他接法
- 桥接 MCP server:
@deepseek-ai/dsh-mcp-client可以把同一个 Python 包当 MCP server 跑起来,工具名变成mcp__screenshot__take_screenshot。图片闸门一样,但没有自动截图。 - 桥接已有的 Claude Code hook:
@deepseek-ai/dsh-hooks-claude-code能跑现成的hooks.json。只用PostToolUse,matcher 里写 dsh 的小写工具名,并加--image-tool read_image,否则 agent 会被指去调一个不存在的工具。那边不要用Stophook —— dsh 上stop_hook_active恒为false,CLI 自带的防死循环逻辑根本不会触发。
开发
npm install
npm run typecheck
npm test
npm run build
真实截图的集成测试默认跳过,要跑就指向一个已安装的 CLI:
DSH_SCREENSHOT_CLI=../.venv/Scripts/screenshot-feedback-hook-mcp.exe npx vitest run
[!WARNING] 所有
@deepseek-ai/dsh-*与@deepseek-ai/cordis一律是 peer,绝不能放进dependencies。 本地开发靠devDependencies提供,运行时必须由 host 那份安装提供。把任何一个挪回dependencies,pnpm 就会在 profile 里物化出第二份副本,盖掉 dsh 建在~/.dsh/profiles/node_modules/的符号链接;而 dsh-tools 的调度器是用模块局部Symbol索引的,两份副本会让ctx.tools[TOOL_RUNTIME_SCHEDULER]变成undefined,该 profile 里所有工具调用(read/write/bash全都算)都会以Cannot read properties of undefined (reading 'prepare')崩掉。tests/packaging.spec.ts守着这条线。
MIT License.
链接
同类插件
liustack/modlens★ 4192
为纯文本模型架起视觉桥梁:粘贴图片,输出结构化 JSON 证据(OCR、版面、语义)。
ysr666/dsh-vision-router★ 1141
为纯文本 Agent 提供视觉能力:内置免 Key 视觉链 + 像素级视觉工具(看图问答、定位、裁剪、像素对比、取色、OCR、矢量化、抠图、截图);粘贴图片即可用。
Anionex/dsh-vision-toolkit★ 887
让纯文本模型处理视觉任务:粘贴图片后自动切换到 Vision Toolkit 变体,支持图片问答、多图比较、长截图 OCR、截图还原前端 UI、元素定位与像素对比。默认无需 API Key——图片经作者自建的免费服务处理,每台机器每天 100 张;也可改为指向自己的服务商。
fandc520/dsh-comfyui★ 105
让 DeepSeek Harness 的 Agent 直接驱动本地或远程 ComfyUI:comfyui_run / comfyui_object_info / comfyui_workflow 工具生成与编辑图像、视频,附带工作流库(图工作流提取:按分量 / 主流程 / 整体)、加载区分辨率自动匹配、实时队列、SDXL 与 Wan 2.1 模板、配套 skill 与同源媒体代理。
dickpy/dsh-imagegen★ 104
面向 DSH Web GUI 的 AI 生图插件:通过可配置的 OpenAI 兼容端点(gpt-image-2 / gpt-image-1 / dall-e-3)实现文生图与图生图,提供 api_url/api_key 设置卡片与侧边栏分栏生图工作台。
sunxin-ai/dsh-design-qa★ 44
给纯文本模型的设计稿保真判定:`deepseek_vision` 工具从任意 OpenAI 兼容视觉路由借来一只眼,让模型判断实现与设计稿是否一致——并附上支撑该判定的基准(4 组夹具、23 处注入缺陷、逐格原始输出)与其依赖的提问纪律。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。