dsh-test-window
给 DeepSeek Harness 的「测试窗口落哪块屏」插件:在设置里选显示器,Agent 拉起的游戏/编辑器/预览窗口就会开在那块屏上,不再盖住 DSH 窗口。
English · 安装 · 快速上手 · Agent 工具 · 配置
它解决什么
多屏工作时,Agent 用命令拉起的窗口(Godot 游戏、编辑器、npm run dev 的浏览器页、预览服……)会按程序自己的习惯出现在随机位置——最常见的是正好压在 DSH 窗口上,把对话挡住。
本插件把「测试窗口开在哪」变成一个设置项,并且同时覆盖两条路径:
- 设置页手动拉起(
/launch) - Agent 调用
open_test_window工具
一次选择,两边都生效。
功能
- 🖥️ 显示器选择:设置页列出真实物理屏幕(DPI 感知、物理像素、含坐标/工作区/是否主屏),点选即存
- 🔍 认得准窗口:只认启动后新出现的可见窗口——启动脚本几秒后才拉起的程序、交给 explorer 打开的程序都能命中;GPU 驱动/引擎创建的隐藏辅助窗口(如 Godot 4 + NVIDIA 的
NVOpenGLPbuffer)和控制台黑框不会被误当成目标 - 🪟 自动摆放并保持:等到进程窗口出现后移动/最大化到目标屏,并在
settleMs(默认 6 秒)内持续校正——引擎常会回写自己保存的窗口几何 - 🕶️ 不闪控制台:程序在一个「创建即隐藏」的新控制台里启动(
CREATE_NEW_CONSOLE+SW_HIDE),主屏不会闪一下黑框;.bat里的timeout/pause依然正常 - ⌨️ 不抢键盘:焦点策略三档(从不 / 空闲时 / 总是),默认「空闲时」——你正在打字、拖动、长按、开着右键菜单时,新窗口只摆好位置、不上前;程序自己抢走的焦点会被还回去
- ✋ 不打断你的操作:检测到鼠标按着、窗口正在移动/缩放、或弹出菜单打开时,暂停位置校正并把位置交给你,绝不和你的拖动抢
- ♻️ 窗口重建也能跟上:程序把窗口销毁重建(引擎切换渲染后端)时,跟上新窗口而不是报「窗口已关闭」
- 🔁 失败自动降级:程序顽固拒绝最大化时,退化为「铺满目标屏工作区」的普通窗口,而不是留下一个越界窗口
- 🎯 精确判定:最大化窗口天然比屏幕大 8 像素(边框),判定逻辑对最大化态放宽,不会把正确结果误判成越界
- 🧰 Agent 工具:
open_test_window(command, cwd?, displayIndex?, titleContains?, maximize?, topMost?, focus?, timeoutMs?, settleMs?),并会把「要开窗口就用它」写进每个会话的系统提示 - 🚪 如实回报:窗口被程序关掉返回
window-closed,命令启动失败返回spawn-failed/command-failed(带退出码),不会假装成功 - 🌐 仅本机:所有 HTTP 路由只接受回环地址
环境要求
- Windows 10/11(依赖 PowerShell + Win32 窗口 API;macOS/Linux 不支持)
- PowerShell 5.1+(系统自带)
- DeepSeek Harness >= 0.2.1-rc.2
- Node.js
^22.19.0 || >=24.0.0
安装
cd "/d/Program Files/DeepSeek Harness/resources/runtime/cli/bin"
./dsh.cmd plugin --profile desktop add "git+https://github.com/Aily-DSH/dsh-test-window.git"
或本地开发:
dsh plugin --profile desktop add "file:/D:/path/to/dsh-test-window"
装完重启一次 DeepSeek Harness 客户端(宿主半与注入清单都只在启动时计算)。重启后打开 设置 → 测试窗口。
快速上手
- 设置 → 测试窗口,从列表里选一块屏(例如
2 · 1920×1080 @ 640,-1080) - 勾选「最大化」「置顶」,选「窗口出来之后谁拿键盘焦点」(默认「空闲时才切到前台」),保存
- 在「立即启动命令」里填一个命令点启动,例如
godot --path D:/GC/MyGame,看窗口是否落在目标屏 - 之后让 Agent 拉窗口时,它会自动带上这个设置
Agent 工具:open_test_window
给 Agent 用的入口,参数:
| 参数 | 类型 | 说明 |
|---|---|---|
command |
string (必填) | 打开窗口的程序或 shell 命令,如 godot --path D:/GC/MyGame、npm run dev |
cwd |
string | 工作目录 |
displayIndex |
number | 覆盖设置里的显示器(1 起,与设置页一致) |
titleContains |
string | 窗口标题子串;命令把窗口交给别的进程时(浏览器)用它命中 |
maximize |
boolean | 覆盖「最大化」 |
topMost |
boolean | 覆盖「置顶」 |
focus |
never / idle / always |
覆盖焦点策略 |
foreground |
boolean | 旧参数:true = focus: always,false = never |
timeoutMs |
number | 等窗口出现的最长毫秒数 |
settleMs |
number | 窗口出现后继续校正矩形的时长(慢引擎可调大) |
返回结构含 window(ok / pid / title / x / y / width / height / zoomed / inside / userMoved / focusGiven / attempts / maximizeFellBack)与 display,并在工具输出里渲染成一句中文说明。
配置
配置文件:~/.dsh/dsh-test-window/config.json
| 字段 | 默认 | 说明 |
|---|---|---|
displayIndex |
0 |
目标显示器序号(1 起;0 表示未选择) |
device |
"" |
设备名,如 \\.\DISPLAY2 |
maximize |
true |
摆放后是否最大化 |
topMost |
false |
是否置顶 |
focus |
idle |
焦点策略:never 从不 / idle 空闲时 / always 总是 |
focusIdleMs |
1500 |
idle 的判定阈值:用户超过这么久没动键盘鼠标,才允许把窗口切到前面 |
timeoutMs |
20000 |
等窗口出现的超时 |
settleMs |
6000 |
窗口出现后持续校正的时长 |
lastCommand/lastCwd |
"" |
上次启动的命令,方便复跑 |
路由(仅回环):
| 路由 | 用途 |
|---|---|
GET /api/dsh-test-window/state |
配置、显示器列表、运行中窗口 |
POST /api/dsh-test-window/config |
增量更新配置 |
POST /api/dsh-test-window/launch |
立即启动并摆放一个命令 |
POST /api/dsh-test-window/stop |
停掉本插件拉起的窗口 |
工作原理
launch(command)
└─ spawn watch-window.ps1(隐藏窗口;先热身,写 ready 文件)
Phase 0 记录启动前已有的窗口快照(之后判断「哪些是这次新冒出来的」)
├─ 由它启动命令:CreateProcess + CREATE_NEW_CONSOLE + SW_HIDE
│ 控制台真实存在(timeout/pause 可用),但窗口从不显示 → 不闪、不抢焦点
│ PID 写回 spawn 文件,连同一棵树作为窗口归属依据
Phase 1 等窗口出现(-TimeoutMs),每 300ms 重读进程树;隐藏状态下摆位,再显示,最后最大化
Phase 2 每 50ms 校验矩形,漂移就重启摆位;最大化最多重试 6 次;窗口被销毁则接管新窗口
· 用户按着鼠标 / 正在移动窗口 / 开着菜单 → 暂停校正(拖到窗口上时直接停手)
· 没被授权抢焦点时,谁抢走焦点就把焦点还回去
Phase 3 最终校验;没有可见窗口 → no-window(启动器非零退出 → command-failed)
└─ 读取结果 JSON,交给设置页或 open_test_window 返回
为什么不闪、不抢
| 老做法 | 问题 | 现在 |
|---|---|---|
shell + detached + windowsHide:false |
生成可见控制台,主屏闪一下黑框,还会把键盘焦点抢走 | CREATE_NEW_CONSOLE + SW_HIDE:控制台真实存在但从不显示 |
windowsHide:true |
没有控制台句柄,.bat 里的 timeout / pause / set /p 直接失败 |
同上,句柄完整 |
启动后 SetForegroundWindow |
你正在打字,焦点被抢走、输入被打断 | 默认「空闲时」策略:有输入活动时不切前台;程序自己抢的一律还回去 |
| settle 阶段每 50ms 重新摆位 | 你拖动窗口时被拽回原位、菜单被点掉 | 检测到按住鼠标 / 移动窗口 / 菜单打开就暂停或停手 |
它会挑哪个窗口
「测试窗口」指的永远是程序自己弹出来的那个可见主窗口,所以挑选规则是:
- 只认可见窗口,且不是桌面/托盘/输入法/工具窗这类系统装饰;
- 启动前已经存在的窗口一律不算——聊天窗口、你本来开着的程序永远抓不到;
- 按可信度分四档:
owned(属于命令的进程树)>fresh(属于本次启动后才起的新进程)>other(等待期间新冒出来的窗口)>console(启动它的那个黑框)。owned命中就立刻落地;fresh要等启动器退出、且过了 900ms 才认,免得抢到同一时刻其它程序的新窗口;other/console只在超时前什么都没等到时才兜底; - 进程树每 300ms 重读一次——
.bat/npm这类启动器往往几秒后才拉起真正的程序,只在启动瞬间抓一次 PID 是抓不到的; - 排除 GPU 驱动/引擎的隐藏辅助窗口(
NVOpenGLPbuffer、__wglDummyWindow*等)。
要点:
- 最大化窗口的矩形允许比显示器大 16 像素(
$maximizedOverhang),否则会把「正确最大化」误判为越界 - 隐藏状态下先
SetWindowPos再SW_MAXIMIZE会被 Windows 按旧显示器钳制,因此顺序是「显示 → 最大化」 - 引擎(如 Godot 编辑器)会在启动后几百毫秒回写几何,所以需要 settle 阶段持续校正
已知限制
- 只支持 Windows
- 用
titleContains时若进程快速换名,可能错过窗口(放宽timeoutMs或换个更稳的标题片段) - 全屏(
fullscreen)模式的程序不受摆位控制 - 需要桌面会话(无人值守/服务会话下窗口 API 不可用)
卸载
dsh plugin --profile desktop remove dsh-test-window
删除配置目录 ~/.dsh/dsh-test-window/(可选)。
目录结构
lib/index.js 宿主半:配置、路由、启动与等待、工具注册
lib/client.js 客户端半:设置页(显示器列表、选项、立即启动)
lib/scripts/displays.ps1 枚举物理显示器
lib/scripts/watch-window.ps1 等待并摆放窗口(三阶段校正)
cordis.patch.yml 插入插件行
赞赏
如果这个插件帮你省了钱,欢迎扫码请作者喝一杯 ☕(纯属自愿,不影响使用和更新)
生态与收录
- GitHub topic:dsh-plugin——DSH 社区约定的插件标记,本仓库已加上
dsh/dsh-plugin/deepseek-harness/plugin等 topics。 - 社区目录:已向 cccakeee/awesome-dsh-plugins 提交收录 PR; 收录后可在 deepseekharnessplugins.com 与 awesome-dsh-plugin.com 搜到。
- 客户端插件市场:DSH 的「设置 → 插件市场」只读配置里的索引 URL(不搜 npm)。索引格式 1 的示例见
Aily-DSH/dsh-plugin-index,把它的 jsDelivr 地址加进
dsh.profile.indexes即可在客户端内直接看到 dsh-test-window。 - 查不到时:npm / GitHub 搜索有索引延迟,新包一般 1–3 天;先用
dsh plugin --profile desktop add git+https://github.com/Aily-DSH/dsh-test-window.git直接装。
English
dsh-test-window picks the monitor that Agent-launched test windows open on. Choose a display once in Settings → 测试窗口 and every window started through the settings page or the open_test_window agent tool is moved, maximized and kept there — so a game or editor never pops up on top of the chat. Windows-only; uses two PowerShell helpers for DPI-aware monitor enumeration and a three-phase window-placement loop that survives apps restoring their own geometry.
dsh plugin --profile desktop add "git+https://github.com/Aily-DSH/dsh-test-window.git"
# restart the client once
Sponsor: if this plugin saved you money, you can buy the author a coffee — scan assets/reward-qr.png.
No comments yet. Be the first to write one.