@iiwate/pi-silent-gui

Pi extension: private-desktop GUI smoke with Windows Job Object lifecycle and strict audio failure handling

Packages

Package details

extension

Install @iiwate/pi-silent-gui from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@iiwate/pi-silent-gui
Package
@iiwate/pi-silent-gui
Version
0.2.0
Published
Jul 25, 2026
Downloads
107/mo · 17/wk
Author
iiwate
License
MIT
Types
extension
Size
133.6 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-silent-gui

Windows-only Pi 扩展:在隔离的 Win32 私有桌面中启动和操作 GUI 程序,并输出无损窗口截图。整个过程不切换用户桌面、不使用剪贴板,也不调用 SendInput

安装

要求:

  • Windows 10 或更高版本;
  • Node.js >= 20;
  • Python >= 3.10,且 python 在 PATH;
  • 提权 Pi 的默认非提权路径需要当前用户的 Explorer shell。
pip install pycaw
pi install npm:@iiwate/pi-silent-gui

安装后执行 /reload。可用 PI_SILENT_GUI_PYTHON 指定 Python 解释器。

工具

Tool 作用
silent_spawn 在私有 Desktop 和 Job Object 中启动进程;默认非提权,音频 guard 就绪后才恢复目标线程
silent_message 按完整窗口 PNG 坐标投递 click/key,自动分流客户区 child 与非客户区,支持批量 steps
silent_capture 捕获完整窗口,返回绝对 PNG 路径、原始尺寸、客户区/DPI 元数据和 all_black
silent_kill 终止 Job、停止 broker,并清理受约束的 session 临时目录

启动

{
  "exe": "app.exe",
  "cwd": "C:/path/to/app",
  "args": ["--example"]
}

目标默认非提权。只有 Pi 已经提权时,才可显式允许管理员目标:

{
  "exe": "admin-tool.exe",
  "allow_elevated": true
}

扩展不会调用 runas,也不会触发 UAC。

消息

单次 click/key 或批量步骤都在同一个私有桌面和初始顶层 hwnd 上执行。单步 repeat 为 1..100,delay_ms 为 0..10000,总动作数不超过 1000。

click 的 x/ysilent_capture 完整 PNG 共用同一窗口坐标系,(0,0) 是 PNG 左上角,不需要也不允许调用方估算标题栏高度。运行时使用 Window/Client Rect、坐标转换和 WM_NCHITTESTHTCLIENT 会下降到最深可见、非透明 child 并投递客户区鼠标消息;其他命中向顶层窗口异步投递对应非客户区消息,避免标题栏或菜单模态循环阻塞 down/up。

{
  "session_id": "...",
  "steps": [
    { "action": "click", "x": 50, "y": 50 },
    { "action": "key", "key": "return", "repeat": 2, "delay_ms": 50 }
  ]
}

响应包含 coordinate_space: "window"window.client {x,y,width,height}window.dpi。click 还返回 hit_test、实际 target_hwnddispatch,以及 point.window/screen/targetHTCLOSE 会附带 system_command: "close"。成功只表示消息已完成有界投递,界面效果应由后续截图验证。批量响应在 clicks 中按执行顺序返回每次 click 的投递元数据。

截图

{
  "session_id": "...",
  "window_class": "ExampleWindow",
  "out_path": "C:/evidence/window.png"
}

out_path 可省略;默认文件写入当前 session 临时目录。截图始终保留完整 Window Rect,不裁剪客户区。返回值包含 coordinate_space: "window"、相对完整 PNG 的 window.client {x,y,width,height}window.dpi,不内联图像。

清理

{
  "session_id": "..."
}

Pi session_shutdown 会对仍登记的 session 执行两次 best-effort kill。

隔离与生命周期

每个 session 使用一个持久 Python broker:

  1. 生成调用方不可指定的唯一私有 Desktop;
  2. 创建带 KILL_ON_JOB_CLOSE 的 Windows Job Object;
  3. 使用 STARTUPINFOEX + PROC_THREAD_ATTRIBUTE_JOB_LIST,在进程创建时原子加入 Job;
  4. 目标保持 suspended,直到 WASAPI 音频通知和首轮静音枚举完成;
  5. broker 持续跟踪 Job 内进程、维持静音并处理 stop;
  6. broker 异常退出时,Job handle 关闭并终止整棵进程树。

默认路径不会因 Pi 已提权而让目标继承管理员 token;拿不到可信的非提权 Explorer token 时会直接拒绝启动。

安全边界

  • 永不调用 SwitchDesktopSendInput 或剪贴板 API;
  • Desktop、Job 和 session 状态均绑定 12 位 session ID namespace;
  • kill 只操作登记的 Job 和 PID+创建时间身份,不按 exe 名或 PPID 全局扫描;
  • PID 身份核验、终止和等待在同一个进程 handle 上完成;
  • 客户区和 hit-test 使用 SendMessageTimeoutW 限时;非客户区 down/up 使用 PostMessageW 异步有界入队;
  • 几何、message、capture 只临时切换对应 helper 操作线程的 per-monitor DPI awareness,并恢复原 context;
  • 临时目录只允许位于 %LOCALAPPDATA%\Temp\pi-silent-gui\<session_id>
  • 删除前逐级持有目录 handle,拒绝 junction、符号链接和其他 reparse point;
  • 异常 spawn 仅在 Job 和 broker 身份完整时登记恢复 session;
  • Python stdout/stderr 持续排空,各自只保留最后 1 MiB,不因输出超限强杀 launcher。

清理失败会保留 session 登记和结构化错误,供后续重试。

截图保真

  • 捕获完整 Window Rect,保持原始宽高,不缩放、不裁剪;
  • 上限为 32 * 1024 * 1024 像素,超限明确失败,不降质输出;
  • 使用 24-bit RGB 无损 PNG;
  • GetDIBits 未返回全部扫描行时立即失败;
  • 只重试等待 Job/窗口出现,找到窗口后 PrintWindow 只执行一次;
  • 全黑画面仍作为有效证据写入,并以 all_black: boolean 明示;
  • TS 返回前验证绝对路径、文件身份、PNG/IHDR 和窗口尺寸。

静音边界

broker 在恢复目标线程前注册 WASAPI session-created 通知,并辅以启动期 50ms、稳定期 200ms 轮询。SetMute 失败或连续音频 sweep 失败会终止整个 Job。

Windows 用户态 per-process mute 无法形式化证明“绝不产生任何采样”。本扩展保证的是:音频 guard 未建立时不恢复目标,guard 失效时 fail closed。

临时文件

%LOCALAPPDATA%\Temp\pi-silent-gui\<session_id>\
  session.json
  cap_*.png

silent_kill 全成功后永久删除该 session 目录;显式指定到其他位置的证据截图不会随 session 删除。

测试

npm run test:unit
npm run test:integration
npm run test:smoke
npm test
  • unit:capture/window/process 单元测试和 TS 伪造响应 fail-closed;
  • integration:后端 CLI、故障注入、扩展 schema、PNG、有界输出、恢复登记和 abort 清理;
  • smoke:真实 broker/Job 生命周期,以及 WinVer 的 GetDlgItem(IDOK) child 点击和 HTCLOSE 非客户区关闭。

发布通过 prepublishOnly 强制运行全量测试;内部测试源码不进入 npm 包。

已知限制

  • 部分 DirectX、OpenGL、Vulkan、Chromium GPU 或受保护窗口不支持 PrintWindow
  • DirectInput / GetAsyncKeyState 程序可能不响应窗口消息;
  • 目标显式使用 Job breakaway 或系统级服务代理时可能超出当前 Job;
  • 高完整性或反作弊程序可能拒绝窗口消息;
  • 本扩展不是通用 computer-use,也不提供 OCR 或 UIA。

License

MIT