Skip to content
dsh.fish
Bundle

dsh-plugin-screenshot-ask

DSH 截图提问插件:输入框相机按钮或 Ctrl+Shift+A → 自动最小化窗口 → 原生全屏框选浮层(确认/取消按钮)→ 选区直接作为图片附件进入当前会话输入框,全程不落盘。Screenshot-to-ask for DeepSeek Harness: the window hides, a native region overlay covers every monitor, and the crop lands in the composer as an image attachment.

Source
sky1ine139
License
MIT
Updated
Updated yesterday

Readme

# dsh-plugin-screenshot-ask — DSH 截图提问

> DeepSeek Harness(DSH)插件:**原生框选截图,直接进输入框提问**。
> 仓库:<https://github.com/sky1ine139/dsh-plugin-screenshot-ask> · 仅 Windows

在 DSH 输入框左侧加一个 📷 按钮(或按 `Ctrl+Shift+A`):**DSH 窗口自动最小化** →
屏幕变暗 → 拖动框选 → 选区旁出现「确认 / 取消」按钮 → 确认后窗口回来,
**选区直接作为图片附件落进输入框**,接着打字提问、回车发送。

和系统快捷键截图最大的区别:**全程不落盘**。图片以字节流从宿主进程走到浏览器,
成为一个普通的图片草稿附件,不再往 `图片` 文件夹里堆文件。

```text
点按钮 / 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;本插件无构建步骤,装完即可用)

```powershell
$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
```

**方式二:本地打包再装**(自己改代码时用这种)

```powershell
# 注意: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
powershell -ExecutionPolicy Bypass -File "$env:USERPROFILE\.dsh\restart-dsh-web.ps1"
```

重启后打开日志里新打印的带 token 地址(形如 `http://127.0.0.1:3080/?token=...`,
`restart-dsh-web.ps1` 会顺便弹一个浏览器标签页),输入框左下方出现相机按钮。

卸载:

```powershell
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 覆盖(该文件当前是 `[]`,改成下面这样即可):

```yaml
- 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`:
  1. 取前台窗口(就是显示 DSH 的浏览器)→ `ShowWindow(SW_MINIMIZE)`,并把句柄写进状态文件;
  2. 抓一张整屏冻结帧、压暗,作为覆盖**整个虚拟屏幕**的浮层背景(在 `Paint` 里画,
     不是 `BackgroundImage` —— 超大窗体上后者不渲染,会退化成一块灰);
  3. 框选 / 移动 / 8 手柄缩放 / 方向键微调都在同一套客户区坐标里绘制,与 `CopyFromScreen`
     天然对齐;选区旁画工具条(尺寸 +「确认」+「取消」),鼠标命中测试与绘制共用同一套布局函数;
     重绘走**统一失效区域**:上一次与当前的**选区框 + 工具条**四块矩形一起失效。
     工具条在选区下方 14~50px 处,只按选区外扩失效会漏掉它 —— 那正是"直接框选后出现带状、
     手动调整一下就正常"的成因(手动调整触发了工具条自己的失效);
     顶部提示条**按显示器逐屏居中绘制**:只在整块虚拟屏上居中会让它骑在多屏接缝上,
     每块屏各露半截(看起来像凭空多出一个被截断的黑框);
  4. 确认 → 先隐藏浮层再抓帧(浮层不会进图)→ 结束前 `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` 能合成真实拖拽并用像素读数验证工具条确实被画出来。
- **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`](https://github.com/deepseekbluefish/dsh-screenshot-plugin):
  同样的原生框选浮层,但截图存进 `~/Pictures/DSH-Screenshots` 并只在输入框写入
  `[Shot N HH:mm]` 标记,需要再配视觉插件让 agent 去读文件 —— 正好是「占空间」的痛点。
- [`Max-Null/dsh-capture`](https://github.com/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
powershell -NoProfile -ExecutionPolicy Bypass -File lib/capture.ps1 -DemoRect 200,150,400,250 -DemoMs 3000 -KeepWindow
```

自测(不需要重启,直接跑脚本):

```powershell
# 几何自检
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
```

## 开发

```powershell
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`](https://github.com/deepseekbluefish/dsh-screenshot-plugin) 与
[`dsh-capture`](https://github.com/Max-Null/dsh-capture)(均为 MIT),详见 `LICENSE`。

Install

dsh plugin --profile web add github:sky1ine139/dsh-plugin-screenshot-ask

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source