dsh-plugin-screenshot-ask — DSH 截图提问
DeepSeek Harness(DSH)插件:原生框选截图,直接进输入框提问。 仓库:https://github.com/sky1ine139/dsh-plugin-screenshot-ask · 仅 Windows
在 DSH 输入框左侧加一个 📷 按钮(或按 Ctrl+Shift+A):DSH 窗口自动最小化 →
屏幕变暗 → 拖动框选 → 选区旁出现「确认 / 取消」按钮 → 确认后窗口回来,
选区直接作为图片附件落进输入框,接着打字提问、回车发送。
和系统快捷键截图最大的区别:全程不落盘。图片以字节流从宿主进程走到浏览器,
成为一个普通的图片草稿附件,不再往 图片 文件夹里堆文件。
点按钮 / Ctrl+Shift+A
→ 宿主进程把浏览器窗口最小化(这样才看得到、也截得到被它挡住的内容)
→ 整个虚拟屏幕盖上变暗的冻结帧(多显示器全覆盖,按物理像素抓取)
→ 拖框选范围(8 个手柄微调,方向键 1px / Shift+方向键 10px 挪动)
→ 点选区旁的「确认」(或双击 / Enter / 右键)→ 窗口恢复并回到输入框
→ PNG 经官方 composer 图片通道进入输入框 → 输入问题 → 回车发送
特性
- 截图时自动隐藏 DSH 窗口:像豆包那样先把界面让开,选完再回来;脚本被强杀时宿主也会把窗口恢复。
- 原生框选,不弹浏览器权限框:走的是宿主侧 PowerShell 浮层,不需要 「选择要共享的屏幕」对话框,也不占用屏幕录制权限。
- 覆盖全部显示器:浮层是整块虚拟屏幕(含负坐标副屏),并按 per-monitor-v2 DPI 感知取真实像素, 混合缩放(100% + 120%)下选区与抓帧不会错位。
- 明确的确认 / 取消:选区旁边一条工具条 —— 尺寸读数 +「确认」+「取消」。
- 不落盘:默认不写任何文件。想看落盘截图时才在配置里给
saveFolder。 - 走官方草稿通道,不合成 drop:直接调会话服务的
createDrafts+inputActions.addAttachments(与内置文件选择器同一条路径),所以它就是一张图片附件: 有缩略图、可删除、可配文字 —— 不会被别的插件截走,也不会被存成文件。 - 两种入口:按钮左键框选 / 右键整屏;页面内快捷键
Ctrl+Shift+A。 - 超大可自动降采样:长边超过
maxEdge时等比缩小;同时守住 DSH 附件通道的两条硬线 (单边 ≤ 2000px、单图 ≤ 5MB),超了会继续缩,不会因为图太大被拒。 - 安全:捕获路由默认只接受 loopback 请求(截的是宿主机的屏幕,不能被远程访客触发)。
安装
方式一:直接从 GitHub 装(需要 pnpm;本插件无构建步骤,装完即可用)
$bin = "$env:LOCALAPPDATA\DeepSeekHarness\dsh-pkg\node_modules\@deepseek-ai\dsh\lib\bin.js"
node $bin plugin --profile web add github:sky1ine139/dsh-plugin-screenshot-ask
方式二:本地打包再装(自己改代码时用这种)
# 注意:pnpm 的 file: 规范会在空格处断开,所以仓库路径和输出目录都别带空格
cd <这个仓库的本地路径>
npm pack --pack-destination "$env:USERPROFILE\.dsh\plugin-packs"
$bin = "$env:LOCALAPPDATA\DeepSeekHarness\dsh-pkg\node_modules\@deepseek-ai\dsh\lib\bin.js"
node $bin plugin --profile web add "$env:USERPROFILE\.dsh\plugin-packs\dsh-plugin-screenshot-ask-0.2.3.tgz"
必须重启 dsh web 进程(宿主半段与浏览器 bundle 都在进程启动时装载,刷新页面不够):
powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\.dsh\restart-dsh-web.ps1"
重启后打开日志里新打印的带 token 地址(形如 http://127.0.0.1:3080/?token=...,
restart-dsh-web.ps1 会顺便弹一个浏览器标签页),输入框左下方出现相机按钮。
卸载:
node $bin plugin --profile web remove dsh-plugin-screenshot-ask
使用
| 操作 | 效果 |
|---|---|
| 左键点按钮 | 最小化窗口并进入框选 |
| 右键点按钮 | 直接截取整个屏幕(多屏为整个虚拟桌面) |
Ctrl+Shift+A |
等同左键点按钮(页面有焦点时生效) |
| 拖动 | 拉出选区;松手后选区保留 |
| 拖选区内部 | 移动选区 |
| 拖 8 个白点 | 缩放选区 |
方向键 / Shift+方向键 |
选区挪 1px / 10px |
| 点工具条「确认」 | 截取选区并回到输入框 |
双击 / Enter / 右键选区 |
同上(键盘或纯鼠标都能确认) |
点工具条「取消」/ Esc / 右键空白处 |
放弃本次截图,窗口照常恢复 |
浮层内 Ctrl+A |
全选整个屏幕 |
截取成功后输入框里会出现一张图片缩略图,直接打字提问即可;也可以点缩略图上的 删除按钮反悔,或再截一张(会依次排队进同一条附件栏)。
配置
在 ~/.dsh/profiles/web/cordis.patch.yml 里按 id 覆盖(该文件当前是 [],改成下面这样即可):
- id: screenshot-ask
config:
maxEdge: 2000 # 只用于「调小」:长边上限,填更小值图片更小;DSH 附件上限是 2000,填更大也按 2000
saveFolder: '' # 非空且目录存在时,额外往这里存一份 PNG(默认不落盘)
timeoutMs: 180000 # 浮层忘关时的兜底超时(超时会强杀脚本并自动恢复被最小化的窗口)
hideWindow: true # false = 截图时不最小化 DSH 窗口(想连 DSH 界面一起截时用)
allowRemote: false # true = 允许非 loopback 来源触发截图(不建议)
改完同样需要重启 dsh web。
工作原理
宿主半段
lib/index.js:在ctx.webServer上注册两条精确路由POST /api/screenshot-ask/capture({mode:'rect'|'full', keepWindow?:true})与GET /api/screenshot-ask/status;capture 拉起lib/capture.ps1,把它的 JSON(含 base64 PNG)原样回给浏览器。带并发闸、超时、loopback 校验; 若脚本被超时强杀,宿主会读脚本留下的窗口句柄把浏览器窗口恢复回来。截屏脚本
lib/capture.ps1:- 取前台窗口(就是显示 DSH 的浏览器)→
ShowWindow(SW_MINIMIZE),并把句柄写进状态文件; - 抓一张整屏冻结帧、压暗,作为覆盖整个虚拟屏幕的浮层背景(在
Paint里画, 不是BackgroundImage—— 超大窗体上后者不渲染,会退化成一块灰); - 框选 / 移动 / 8 手柄缩放 / 方向键微调都在同一套客户区坐标里绘制,与
CopyFromScreen天然对齐;选区旁画工具条(尺寸 +「确认」+「取消」),鼠标命中测试与绘制共用同一套布局函数; 重绘走统一失效区域:上一次与当前的选区框 + 工具条四块矩形一起失效。 工具条在选区下方 14~50px 处,只按选区外扩失效会漏掉它 —— 那正是"直接框选后出现带状、 手动调整一下就正常"的成因(手动调整触发了工具条自己的失效); 顶部提示条按显示器逐屏居中绘制:只在整块虚拟屏上居中会让它骑在多屏接缝上, 每块屏各露半截(看起来像凭空多出一个被截断的黑框); - 确认 → 先隐藏浮层再抓帧(浮层不会进图)→ 结束前
finally恢复并聚焦原窗口。 输出{ok, dataUrl, width, height, sourceWidth, sourceHeight, bytes, dpiAware, hidden, restored}。 纯 ASCII 源码(Windows PowerShell 5.1 会把无 BOM 的 UTF-8 当 ANSI 读,因此中文串由Text-Of按码点拼);几何逻辑带-SelfTest,多屏覆盖带-Probe亮度自检,-DemoDrag能合成真实拖拽并用像素读数验证工具条确实被画出来。
- 取前台窗口(就是显示 DSH 的浏览器)→
DPI:进程按
per-monitor-v2 → per-monitor → system依次降级申请 DPI 感知。 只用SetProcessDPIAware(system)时,副屏若缩放不同,Windows 会把鼠标坐标虚拟化 (实测 120% 屏上坐标 ×1.2),选区与抓帧就会错位 —— 这是本插件踩过的坑。浏览器半段
lib/client.js:window.__ModuleLoader__.load经典脚本 bundle, 注册到conversation.input.left槽位;拿到 dataURL 后取当前会话 id(ctx.get('sessions')), 用ctx.get('conversation').createDrafts(sessionId, [file])建一张浏览器侧图片草稿, 再交给该会话的inputActions.addAttachments(ids)追加进输入框 —— 与内置文件选择器 (ui-conversation 的addFiles)完全同一条路径:不派发任何 DOM 事件,其他插件的 drop 监听器无从插手,字节也不落盘。追加被拒(提交中的相位)时会releaseDraftAttachments回收并提示,不会泄漏草稿。最后把焦点交回输入框。曾经的实现是「合成一个 document 级
drop、塞进DataTransfer」,那是错的:dsh-files也监听 document 的drop,会把文件上传到.dsh-filess/<session>/落盘, 再往输入框塞路径 —— 输入框里已有图片草稿时这一步会失败,于是截图变成一张 文件卡外加一条红色报错。现在只在会话服务不可达(老版本 DSH)时才回退到 drop。
已知限制
- 仅 Windows:截屏依赖 Windows PowerShell + System.Drawing。非 Windows 平台路由返回 501。
- 没有系统级全局热键:浏览器页拿不到全局键盘。要「在任何窗口里按一下就截」,
需要桌面壳(DSH Desktop / Electron 壳)。当前
Ctrl+Shift+A只在 DSH 页面有焦点时生效。 - 隐藏的是「按下按钮那一刻的前台窗口」:通常就是 DSH 所在的浏览器。若想连 DSH 界面
一起截下来,把
hideWindow设为false(或调接口时带keepWindow: true)。 - 浮层是独立置顶窗口:截图时它会盖住整个屏幕(这正是它不弹权限框的代价)。
- 不做标注:目前只有框选与整屏,没有红框/箭头/文字。要标注可以配合
dsh-capture(见下)或后续版本。
和现成插件的关系
DSH 插件市场里已经有两个同类插件,本插件是它们的组合与取舍:
deepseekbluefish/dsh-screenshot-plugin: 同样的原生框选浮层,但截图存进~/Pictures/DSH-Screenshots并只在输入框写入[Shot N HH:mm]标记,需要再配视觉插件让 agent 去读文件 —— 正好是「占空间」的痛点。Max-Null/dsh-capture(@max-null/dsh-capture): 框选 + 标注(矩形/箭头/文字)+ 图片直入输入框,功能更全;但纯浏览器模式下走getDisplayMedia,每次截图都要在系统选择器里先选一次屏幕,且无法截取被 DSH 窗口 遮住的内容之外的整屏自由度。- 本插件 = 前者的原生捕获(无权限对话框、被遮挡内容也能截)+ 后者的直入附件, 并且默认零落盘。
想要「标注 + 全局热键」的完整体验,可考虑装 DSH Desktop 桌面壳(dsh-capture、dsh-appshots 在壳内提供全局快捷键)。
故障排查
| 现象 | 处理 |
|---|---|
| 输入框旁边没有相机按钮 | 插件没被装载:确认 dsh plugin --profile web add 成功、profile 的 dsh.profile.bundles 里有 dsh-plugin-screenshot-ask,然后重启 dsh web |
| 点按钮提示「截图失败:… HTTP 404」 | 宿主半段没加载(如无 webServer 的 TUI 部署),Web 版才有这条路由 |
| 提示「没有可用的输入框」 | 没有打开的会话;先进入一个会话再截图 |
| 浮层只有一块屏幕变暗 / 只有一个显示器能框选 | 运行 -Probe 看两台显示器的 meanLuma 是否都被压暗;仍不行就贴 -DemoRect ... -DemoMs 3000 的输出(里面 realWindowRect 是操作系统认定的真实窗口矩形) |
| 选区位置和实际截到的区域错位 | 典型的混合 DPI 问题:看 -CaptureRect 输出里的 dpiAware 是否为 true;另外检查环境变量 LIB(绿色版 VC6 会污染 C# 编译,脚本已临时置空处理) |
| 窗口最小化后没回来 | 正常路径由脚本 finally 恢复;被超时强杀时宿主会用状态文件恢复。若仍卡住:powershell -File lib/capture.ps1 -Mode restore -StateFile "$env:TEMP\dsh-screenshot-ask-hwnd.txt" |
| 图太大、发送慢 | 调小 maxEdge(如 1600) |
| 截图变成一张「文件卡」,还弹红字「文件已上传但未能加入输入框」 | 这是别的插件(dsh-files)抢到了 document 级 drop:它把文件落盘到 .dsh-filess/<session>/ 再往输入框塞路径。本插件 v0.2.1 起不再合成 drop,走会话草稿 API;若仍出现,说明 ctx.get('conversation') 不可达(老版本 DSH),控制台会打印 using the drop fallback,可临时停用 dsh-files 或升级 DSH |
| 连拍两张只进了一张 | 第二张时输入框处于提交相位会被拒(提示「输入框正忙」),等这一条提交完再截;草稿不会被泄漏 |
| 输入框已经有图片草稿,能否继续加 | 可以,同一条附件栏按顺序排队,最多 20 张 |
| 工具条位置/尺寸数字和选框对不上(例如徽标写 1146、选框只有 800 宽) | v0.2.1 及更早的残留像素:改小选区后选框重画了、工具条那块没重画,于是看到的是旧的工具条(位置和数字都还是旧选区的)。v0.2.2 起重绘范围包含"旧/新 选区框 + 工具条"四块矩形。判据:同一个 Paint 里画的框和文字不可能不一致 —— 不一致就一定是有一块没被重画。回归自检:-DemoDrag "2600,400,3746,800" -DemoDrag2 "3746,600,3400,600" -DemoMs 5000 -KeepWindow(先建 1146 宽选区,再从右中手柄收窄到 800;读 toolbarProbe.confirm 应为 37,132,246 且 at.x 对新选区居中) |
| 某块屏幕顶部凭空多出一个半截黑框(文字被截断,比如只看到「Enter 确认 | Esc 取」) | v0.2.2 及更早:顶部提示条按整块虚拟屏居中,正好骑在两屏接缝上,于是每块屏各露半截。v0.2.3 起改为每块显示器各画一条完整提示。自检:-DemoDrag ... -KeepWindow 输出里的 hintProbe,每块屏的 delta 应接近 0(提示文字中心与该屏中心之差) |
不想最小化窗口就加 -KeepWindow:
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoRect 200,150,400,250 -DemoMs 3000 -KeepWindow
自测(不需要重启,直接跑脚本):
# 几何自检
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -SelfTest
# 无头截取一块区域,验证 base64 PNG 通路
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -CaptureRect 40,60,320,200
# 每台显示器的实时平均亮度(用来判断遮罩是否覆盖全屏)
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -Probe
# 渲染自检:弹出自检浮层 3 秒后自动关闭,并报告真实窗口矩形 / 覆盖情况 / Paint 错误
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoRect 200,150,400,250 -DemoMs 3000
# 合成鼠标点击工具条按钮(真跑一遍命中测试)
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoRect 200,150,400,250 -DemoMs 6000 -DemoClick confirm
# 合成一次真实的框选拖拽,并读屏幕上工具条三处的实际像素
# 期望:confirm=37,132,246(确认键蓝底)cancel=66,66,70(取消键灰底)
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoDrag 2600,400,3400,800 -DemoMs 4200 -KeepWindow
# 拖拽 + 点确认,走完整条链路(应返回 width=800 height=400 的 PNG)
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoDrag 2600,400,3400,800 -DemoClick confirm -DemoMs 6000 -KeepWindow
# 回归:先建 1146 宽选区,再从右中手柄收窄到 800(旧版本会留下旧的工具条)
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoDrag 2600,400,3746,800 -DemoDrag2 3746,600,3400,600 -DemoMs 5000 -KeepWindow
开发
node scripts/selftest.mjs # capture.ps1 端到端(自检 + 区域 + 整屏 + 降采样)
node scripts/client-smoke.mjs # 浏览器半段:假 DOM / 假 React / 假会话服务,覆盖
# 草稿 API 投递、忙时回收、连拍两张、drop 回退、快捷键
node scripts/overlay-probe.mjs # 全链路:窗口隐藏/恢复 + 遮罩是否覆盖每一台显示器(会短暂最小化窗口)
# 排查用:量一张已有截图里选框/工具条的实际像素位置,或把某块区域放大出来看
powershell -File scripts/scan-shot.ps1 -Path <截图.png>
powershell -File scripts/crop-shot.ps1 -Path <截图.png> -Out out.png -X 1600 -Y 860 -W 524 -H 100 -Scale 2
改完重新 npm pack + dsh plugin add + 重启即可(或 npm run selftest / npm run smoke)。
许可
MIT。浮层实现与 file: 交付思路分别改编自
dsh-screenshot-plugin 与
dsh-capture(均为 MIT),详见 LICENSE。
No comments yet. Be the first to write one.