dsh-browser-playwright-codex
A Codex-merged fork of dsh-browser-playwright for DeepSeek Harness — published as a real TypeScript source tree, together with the diagnosis and the measurements that got it there.
面向 DeepSeek Harness 的浏览器插件(上游 dsh-browser-playwright + Codex 风格能力整合版)。0.3.0 起本仓库发布的是完整源码树**(类型、测试、验收工具俱全),另附这个插件上"最小化窗口被拽回桌面"等问题的完整诊断与实测数据。**
0.3.0 is the release where "compiled output only" ended. Up to 0.2.0 this plugin existed as a build artifact whose source was not on the machine; since 0.3.0 everything is source: src/ (12 typed modules), tests/ (71 tests), scripts/ (acceptance + diagnostics tooling), and a CHANGELOG.md that says what changed and why. The old compiled-only snapshot is in this repository's git history.
0.3.0 就是"只有编译产物"这个状态结束的版本。 0.2.0 及以前它是一份源码已失的编译产物;0.3.0 起全部是源码,旧的"只有产物"快照留在本仓库的历史里。
Status: what is fixed, and how it is gated / 现状:修了什么,靠什么守住
| # | Symptom / 症状 | State / 状态 |
|---|
| 1 | A minimized window is raised on every tab switch / tab creation 切标签、开新标签时最小化窗口被弹出 | Fixed (src/playwright.ts isWindowMinimized / createBackgroundPage, switch gated) · Upstream patch ready (patches/) |
| 2 | A minimized window is raised by any tool call, even one that fails before touching the page 任何一次调用都弹窗,连"导航前就失败"的调用也弹 | Mitigated in the fork (the login-state export is skipped while minimized) — but not reproducible in a minimal harness, so the mechanism is still unexplained: docs/FOCUS-STEALING.md §Cause 3 |
| 3 | Clicking a target="_blank" link (or a page-side window.open()) still raises the window 点 target="_blank" 链接 / 页面 window.open() 仍会拽窗 | Open — help wanted. Chromium's own default (a new tab must be shown); the plugin does not issue that call, the page does |
| 4 | browser_cdp was completely dead — CDPSession.send was called unbound, so every command failed as a misleading protocol error
browser_cdp 整体失效(方法解绑,报错误导) | Fixed + two gates: a tool-level DOM.getDocument round-trip test and a verify group |
| 5 | In non-persistent mode the login-state export was written to <cwd>/.tmp as plaintext cookies/localStorage, on every call 非持久模式把登录态明文写进进程工作目录,且每次调用都写 | Fixed (stateFile is string | undefined again) + a regression test |
| 6 | allowedDomains was bypassable: browser_click_at (both modes) never asked the policy, and a page-opened tab could be driven 白名单可被 browser_click_at 与"网页自开的标签页"绕过 | Fixed — one shared rule for every click path, and switch_tab refuses hosts outside the allow-list + 3 tool-level tests |
| 7 | javascript: links were unclickable through browser_click (non-http scheme treated as a disallowed host)
javascript: 链接被误当非法主机拒绝 | Fixed — javascript: is exempt (in-page, navigates nowhere); other non-http(s) schemes stay refused |
What the fork still lists as known limitations (JS-driven location.href navigation, cross-origin iframes, canvas widgets, extract needing its own model route, …) is in docs/PLUGIN-README.md — the plugin's own README, kept verbatim.
Everything else the plugin does (snapshot, evaluate, screenshot, click, fill, scroll, wheel, goto, reload, storageState, CDP session creation) was measured to leave a minimized window alone. / 插件其它操作实测都不会碰最小化的窗口。
Install / build / 安装
One command, straight from GitHub — no npm account, no build step:
dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
From a local checkout instead:
git clone https://github.com/miqian-nomad/dsh-browser-playwright-codex
cd dsh-browser-playwright-codex
dsh plugin --profile <name> add link:<absolute path to this directory>
What is verified about the one-liner, and what is not. Verified: fetching the repository works, and the build-script gate no longer blocks the install (lib/ ships, there is no prepare hook — see below). Not verified: full dependency resolution inside a fresh profile. Installing it in a bare directory instead of a profile fails with ERR_PNPM_FETCH_404 on @deepseek-ai/dsh-type-meta, a package that is absent from public npm — part of the harness family is not published there, and our @deepseek-ai/* requirements are written as *. Inside a real DSH profile the harness supplies those packages, but this repository has not been through that test; if it fails for you, the local-checkout route above is the one that is exercised daily.
Why there is no prepare hook: lib/ is committed here (unlike upstream, which ships it inside the npm tarball). pnpm 11 refuses to run a git dependency's build scripts unless the consumer allowlists the package — ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED — so a prepare hook would make the one-liner above fail on a clean machine. If you edit src/, run npm install && npm run build yourself; npm run verify fails if lib/ is older than src/ (check [7]).
Config lives in the DSH profile (cordis.patch.yml in this repo is the bundle layer: headful persistent window, allowEvaluate / allowCdp on, idle never auto-closes). A restart of DSH is required for a rebuilt lib/ to load.
Name note: the package is still named dsh-browser-playwright inside package.json (the module ids in cordis.patch.yml mirror it), so this fork cannot be installed into a profile that already has the upstream package — pick one. The repository name is what distinguishes the fork.
一条命令从 GitHub 装(不需要 npm 账号、不需要构建):
dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
lib/ 是入库的(上游是把 lib 打进 npm tarball,这里 GitHub 就是分发渠道):pnpm 11 默认拒绝执行 git 依赖的构建脚本(ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED),所以本仓库故意不放 prepare 钩子——否则上面那条命令在干净机器上会直接失败。改过 src/ 要自己 npm run build;npm run verify 的 [7] 会在 lib/ 落后于 src/ 时报错。
Verification / 验收
Nothing here is asserted without a command that reproduces it:
| Command | What it is | Last result |
|---|
npm test | 71 tests: contract/tool-schema, provider, scenario journeys (checkout, login, multi-tab, stale refs, lazy load, infinite scroll, popup, dialog), CDP policy, click/tab URL policy, snapshot rendering, description budgets | 68 pass / 0 fail / 3 live-skipped |
npm run verify | Self-contained acceptance script (no DSH, no network): snapshot naming/folding, hover modes, dialog handling, tool-surface ↔ contract, CDP binding, lib/ vs src/ freshness | 52/52 |
npm run doctor | Are the harness packages this plugin imports still exporting what it uses, and where does playwright-core resolve from at deploy time? | all available |
npm run cost | Resident prompt cost per configuration layer (schema defaults / shipped bundle / everything registered) | 20 tools ≈ 4,176 tok/turn · 22 ≈ 4,842 · 23 ≈ 4,968 |
The two analysis documents / 两份分析文档
These are the reason this repository exists at all — they answer questions the plugin's own README cannot:
docs/FOCUS-STEALING.md — "why does a minimized browser window come back?" Four causes, each marked measured or unmeasured, with the plugin-free reproduction and an explicit limits section. Includes the honest correction that native dialogs do not need a visible window.
docs/MODE-TRADE-OFFS.md — measured headless vs headful (UA, viewport, DPR, responsive breakpoints, screenshot size), what actually needs a human eye, the live minimize ⇄ show hand-off that avoids mode switches entirely, and the conclusion that "default to headless" costs the most exactly when a human is needed.
中文:前者回答"最小化窗口为什么会被拽回来"(四条根因,逐条标注实测/未实测);后者是 headless 与 headful 的实测代价对照,结论是"默认 headless 的代价恰好都落在需要人的那一刻"。
Provenance & license / 来源与许可
- Upstream: ChenyuHeee/dsh-browser-playwright v0.1.1, MIT — the accessibility-snapshot engine, the tool family, the URL policy, the test suite. Copyright (c) 2026 dsh-browser-playwright contributors.
- Merged on top (Codex-inspired layer): geometric click (
browser_click_at) with landing-element reporting, the CDP policy layer, the persistent profile + login-state export, dialog parking, the console/network diagnostics tools, and the fixes listed above.
- Licensing is deliberately split — see
NOTICE.md:
- MIT (
LICENSE): src/, tests/, scripts/, configs, CHANGELOG.md, and the fork's own plugin docs.
- CC BY-NC-SA 4.0 (
LICENSE-DOCS): the analysis prose written for this repository — this README, docs/FOCUS-STEALING.md, docs/MODE-TRADE-OFFS.md.
- No OpenAI code is redistributed here.
Help wanted / 求助
- Upstream patch —
patches/upstream-window-activation.patch applies cleanly to upstream main and its helper code now compiles in this tree, but it has never run against upstream's test suite. Also tracked upstream: ChenyuHeee/dsh-browser-playwright#7.
- Cause 2 above — reproduce or refute "a long-lived instance raises a minimized window on a post-op
context.storageState()". A stable reproduction is worth more than any patch here.
target="_blank" / window.open() — stop it restoring a minimized window without rewriting the page's semantics behind the user's back. Known approaches and their costs are listed in the analysis.
- Other platforms / Chromium versions — every measurement in this repository comes from one Windows 11 machine; data for or against these mechanisms elsewhere is welcome.
Open an issue or a PR — both are welcome. / 欢迎开 issue 或直接提 PR。