Bundle
dsh-window-pin
DSH 窗口置顶:把桌面应用窗口固定在最前面(Win32 SetWindowPos / WS_EX_TOPMOST)。左侧栏底栏一个图钉按钮,另提供 window_pin 模型工具。 / Pin the DeepSeek Harness desktop window always-on-top, toggled from a sidebar pin button.
- Source
- MARIOMLY
- License
- MIT
- Updated
- Updated 19 hours ago
Readme
# dsh-window-pin
给 DSH 桌面应用窗口加一个**置顶(始终在最前)**开关。
左侧栏底栏(「设置」左边)会出现一个图钉按钮:
- 空心图钉 = 未置顶,点击把窗口固定在最前面;
- 实心图钉 + 高亮 = 已置顶,点击取消。
另外注册了一个模型工具 `window_pin`,可以直接说「把窗口置顶」让 Agent 代劳:
```
window_pin { action: "status" | "on" | "off" | "toggle" }
```
## 它到底做了什么
浏览器**没有**把原生窗口设为置顶的 API,所以真正的动作在宿主侧完成:
```
图钉按钮 → windowPin Remote 命名空间 → 宿主 spawn PowerShell
→ SetWindowPos(hwnd, HWND_TOPMOST) → 读回 WS_EX_TOPMOST 确认
```
跨进程改 z-order 是允许的。**不要**把它和跨进程子类化混为一谈:后者
(`SetWindowLongPtr`)会被 Windows 以 `ERROR_ACCESS_DENIED` 拒绝,本项目实测过。
## 置顶状态会被记住
落盘的是**意愿**(`desired`),不是窗口句柄。桌面窗口重建后(重启服务、
关掉窗口再从托盘叫回),句柄变了、也不再是 topmost,但页面再次加载时会读到
`desired` 并把置顶补回来——只在**桌面应用窗口**里做这件事,普通浏览器标签页
的加载不会去动别人的原生窗口。
## 目标窗口是怎么选出来的
按顺序(`lib/window-topmost.ps1`):
1. 上次记住、且仍然活着的窗口句柄;
2. **前台窗口**——只要它的标题带着标记。点按钮就会让那个窗口成为前台,所以这条最准;
3. 标题带标记、且进程看起来是 Chromium `--app` 的可见窗口;
4. 第一个标题带标记的可见窗口。
标记默认是 `DeepSeek Harness`:这个字符串**硬编码**在
`@deepseek-ai/dsh-client-ui-layout` 里(`const productTitle = "DeepSeek Harness"`),
不是从品牌服务来的,所以换皮肤不会让它失效。
`status` / 工具输出会带上「选中的是哪个窗口、依据是什么、候选有几个」,
选错时能直接看出来。
## 安装
```powershell
dsh plugin --profile web add <本包目录>
```
`dsh.bundle.patch` 会把 `window-pin` 这一行插进 profile 的组合树,重启后生效。
## 本机的两个硬约束(都实测过,别踩回去)
**1. 子进程必须用 `spawn` + 文件句柄,不能用 `execFile`。**
`execFile` 为了把输出收集成字符串**一定会建管道**,传给它的 `stdio` 文件句柄
根本不被使用,于是:输出进了管道(读文件恒为 0 字节,退出码 0、stderr 也空),
受限模式下还直接抛 `spawn EPERM`。`spawn` 严格照 `stdio` 数组执行。
**2. 辅助脚本必须 ASCII-only 且无 BOM。**
PowerShell 5.1 用 ANSI 代码页解码无 BOM 的 `.ps1`,一个非 ASCII 字节就是
一个潜在解析崩溃。中文目录靠 `import.meta.url` / 调用方传参绕开,不写进脚本字面量。
脚本开头强制 `[Console]::OutputEncoding = UTF8`,否则 CJK 标题会以 OEM 代码页
发出去变成乱码。
## 离线校验
```powershell
pnpm run typecheck # 客户端半
pnpm run build # tsdown(cjs) + tools/wrap-client.mjs
node verify.mjs # 静态断言 + 只读跑一次辅助脚本,失败即 exit 1
node verify.mjs --live # 额外做一次真实的「置顶 → 读回 → 取消 → 读回」
```
`verify.mjs` 的每一条断言都对应一个真实踩过的坑(客户端必须是 ModuleLoader
工厂而不是裸 ESM、strict codec 必须带非空 `typeSymbol`、解包必须同时要求
`ok` 与 `value`、端点名不能撞网关保留名……),改代码前先看它。
## 已知边界
- 只支持 Windows(需要 Win32 `SetWindowPos`)。
- 没有托盘菜单项:托盘属于 `dsh-desktop-app`,本插件刻意不碰它。
- 一次调用约 400–500 ms(PowerShell 启动 + `Add-Type` 编译 C#);
按钮图标是即时反馈的,不至于觉得卡。
## License
MIT
Install
dsh plugin --profile web add github:MARIOMLY/dsh-window-pin
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-window-pin from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.