dsh-browser-playwright-codex 0.6.1 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/NOTICE.md +5 -6
- package/README.md +130 -38
- package/SECURITY.md +18 -11
- package/lib/challenge-signatures.d.ts +114 -0
- package/lib/challenge-signatures.js +157 -0
- package/lib/challenge.d.ts +68 -8
- package/lib/challenge.js +287 -26
- package/lib/injected.js +13 -0
- package/lib/page-actions.d.ts +27 -56
- package/lib/page-actions.js +50 -18
- package/lib/page-diagnostics.d.ts +5 -0
- package/lib/page-diagnostics.js +60 -6
- package/lib/page-probes.d.ts +12 -0
- package/lib/page-probes.js +33 -0
- package/lib/page-snapshot.d.ts +14 -8
- package/lib/page-snapshot.js +50 -3
- package/lib/session.d.ts +21 -82
- package/lib/session.js +15 -3
- package/lib/snapshot-aria.js +16 -0
- package/lib/tool-render.js +58 -3
- package/lib/tool-types.d.ts +16 -0
- package/lib/types.d.ts +23 -0
- package/package.json +8 -7
- package/scripts/dsh-browser-verify.mjs +24 -12
- package/scripts/analysis/hl-compare.mjs +0 -149
- package/scripts/analysis/live-handoff-probe.mjs +0 -97
- package/scripts/analysis/needs-human-probe.mjs +0 -83
- package/scripts/analysis/repro-minimize.mjs +0 -112
- package/scripts/autocommit.ps1 +0 -23
- package/scripts/check-doc-links.py +0 -112
- package/scripts/measure-prompt-cost.mjs +0 -57
- package/scripts/preflight.mjs +0 -192
- package/scripts/prompt-cost.mjs +0 -73
- package/scripts/shipped-gates.mjs +0 -43
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,63 @@
|
|
|
3
3
|
Bilingual by intent: the version headings and the summary of each release are given in both
|
|
4
4
|
languages, the individual entries follow the language the surrounding code and docs use.
|
|
5
5
|
|
|
6
|
+
## 0.6.6 — 2026-10-17
|
|
7
|
+
|
|
8
|
+
一句话摘要:**四份独立审查(文档漂移 / 代码对抗 / 发行打包 / 中性冷读)查出的问题集中修掉**——两条安全缺陷、几处"假成功/假失败"、随包自检的假承诺、发布门禁漏洞与一批文档漂移。
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- **点击白名单可被祖先 `<a>` 绕过**:`browser_click` 的 `ref` / `text` / `precise` 三条路径只校验**被点元素自身**的 href,外层 `<a href="http://外站/">` 包住它时点击会导航到站外并返回成功。现在改为「自身 → 最近祖先 `<a href>`(穿 shadow host)」,与裸坐标路径同层,统一交给 URL 策略。两处独立审查各自用本地 fixture 复现过。
|
|
13
|
+
- **密码/验证码/卡号曾进入 prompt**:`browser_extract` 把 `pageData()` 拼进辅助模型的 prompt,而 `pageData()` 采集输入框**当前值**——登录页的明文密码会随请求发出去(实测 prompt 里出现 `hunter2`)。现在 `type=password` 与 `autocomplete` 为 `current-password` / `new-password` / `one-time-code` / `cc-number` / `cc-csc` / `cc-exp` 的字段一律置空值,**字段本身保留**(模型仍知道"这里有个码要填")。
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **填表核对两头错**:上一版把回读改成严格相等后,页面自动改写格式的输入框(全大写、千分位 `1,000`)被误判成"没写进去",而 `browser_fill` 的描述恰恰把 input-mask / currency 列为预期场景;更早的版本则相反,`ABCD→ZZZZ`、`John→Jane` 会被判成功。现在的归一化只放过"页面改写、不是我们写错":NFKC + 去大小写 + 去掉非字母数字;`1000≡1,000`、`abc≡ABC` 通过,`ABCD↔ZZZZ`、`1234↔12345` 仍然失败。contenteditable 也不再假失败(改读 `textContent`/`innerText`)。
|
|
18
|
+
- **挑战页识别的两处回归**(本轮自查引入的):给控件层加"页面得够空"的门控后,**正常登录页里嵌的真 reCAPTCHA / hCaptcha / Turnstile 完全不再识别**(登录页正是主要场景),已移除该门控;`CAPTCHA_ROLES` 曾删掉 `iframe`,导致名字就叫「验证码」的非厂商 iframe 不被识别,已恢复。
|
|
19
|
+
- **`[nothing here]` 提示三类误报**:静默页扫描深度 4 → 8 层(正文藏得深的正常页曾被判"几乎没有文字",与同一结果树里的正文自相矛盾);无标题的裸文档(`about:blank`、直接打开图片)不再提示;有挂起弹窗时不出这条(否则与 `[dialog]` 指令冲突)。
|
|
20
|
+
- **提示互斥**:已判定为拦截页时不再叠 `[http NNN]`;已识别为挑战时不再叠行为兜底提示。
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **测试 159 → 220**(+61):挑战识别 50 条(含真实页面测得的门控数值与反例:讲 hCaptcha 的文章、`验证码说明` 帮助链接、`获取验证码` 按钮、百度搜索页标题)、真浏览器端到端 6 条(真实 `cf-mitigated` 头、无签名拦截面被行为兜底抓住、`interactiveOnly` 下 iframe 仍识别、单页应用改 URL 后旧响应头不复用、真实 404 显示状态码、真实访客门显示静默页提示)、安全回归 5 条(外链祖先点击被拒、密码/OTP 不进 prompt、contenteditable 成功、同长度不同值失败)、本机路径守卫。
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **随包自检不再在 npm 安装形态崩溃**:`scripts/dsh-browser-verify.mjs` 的「`lib/` 与 `src/` 同步」一组在没有 `src/` 时**跳过**并打印说明,退出码 0 —— 安装形态显示「全部通过 (56 项,跳过 1 项)」,仓库形态仍是「(57 项)」。README / CHANGELOG 此前承诺的"装上即可跑"现在才真的成立。
|
|
29
|
+
- **依赖不再全是 `*`**:`@deepseek-ai/schemastery` 固定 `^3.18.4`,peer `cordis >=4.0.4`、`dsh-llm` / `dsh-tools >=0.2.0-rc.1`。修前空项目安装会解析到 `dsh-tools 0.0.1-rc.1`、`dsh-llm 0.0.1-rc.5`(远旧于测试基线);修后解析到 `0.2.0-rc.2`。代价:不同版本元组的预发布版(如 `0.2.1-rc.*`)会收到 **peer 警告**(不是失败,pnpm 默认不严格)。
|
|
30
|
+
- **发布门禁补齐**:CI 增加 `npm run typecheck` 与 `npm run preflight`(与 `prepublishOnly` 对齐);CI 与 publish 都新增「`npm run build` + `git diff --exit-code -- lib`」守卫——以前先 build 再比 mtime,`src` 改完忘重建也能全绿,而 `dsh plugin add github:…` 的安装路径会拿到旧 `lib/`。
|
|
31
|
+
- **文档漂移**:独立审查列出 13 条硬错误 / 11 条过期 / 8 条矛盾 / 8 条缺失,全部修掉——随包文档死链由 12 条清零(改 GitHub 绝对链接)、测试计数与成本数字按实际命令重取、`npm test:live` 与 `browser-plugin/dev.cordis.yml` 这类跑不通/不存在的引用纠正、URL 白名单不再声称覆盖 `hover`(实际不覆盖)、补写 `snapshot.engine` 说明,并在 `中文说明.md` 新增 **§4.14 挑战页识别**(四层判定与两条结果提示,明确"产出提示不是阻断,认不出来就停下来问人")。许可表述收敛为:代码与说明文档 MIT,CC BY-NC-SA 仅限 `FOCUS-STEALING.md` / `MODE-TRADE-OFFS.md` 两篇分析长文。
|
|
32
|
+
- **`.gitignore` 补登录态**:`dsh-storage-state.json` / `browser-profiles/` / `profile/`。默认写在 `~/.dsh/browser-profiles/`(仓库外),但只要有人把 `launch.profileDir` 指进 checkout,那个文件里就是明文 cookie。
|
|
33
|
+
|
|
34
|
+
## 0.6.5 — 2026-10-16
|
|
35
|
+
|
|
36
|
+
一句话摘要:**挑战页识别不再靠一份关键词表** —— 判定改成「厂商自有的机器信号优先,认不出来就问一句『页面变了吗』」,所以新出现的验证方式也能被接住;顺带清掉漏网的 4 处本机路径,并把 npm 包里 12 个用不上的开发脚本收成 2 个。
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **挑战页识别重做成分层判定**(新增 `src/challenge-signatures.ts`,重写 `src/challenge.ts`)。原来只有一份中英混杂的词表,而且词表里**根本没有「验证码」「安全验证」「拖动滑块」这些中文说法** —— 也就是说 README 承诺的「遇到验证码会停下来」,在中文站点上大半不成立:百度系的「安全验证」、滑块页的「拖动滑块完成拼图」一个都识别不到,插件会当作普通页面继续点。现在按**稳定性**排序,而不是按覆盖率:
|
|
41
|
+
1. **主文档响应头** —— 厂商对外的公开 API:`cf-mitigated: challenge`(Cloudflare 官方文档就是教用户读这个头)、`x-amzn-waf-action: challenge`(AWS WAF)、`x-iinfo`(Imperva)、`x-datadome*`(DataDome)。为此在 `src/page-diagnostics.ts` 已有的 `page.on('response')` 里认出**主文档导航响应**,把 status 与头透传进快照(`BrowserSnapshot.response`)。只认**产生当前文档的那次响应**:单页应用改了 URL 却没有文档加载时,旧响应不再复用 —— 否则会在一个好好的页面上报出墙;退回低层是安全的失败方向。
|
|
42
|
+
2. **厂商控件自己的无障碍名** —— reCAPTCHA / hCaptcha / Turnstile / Arkose / PerimeterX 的 iframe 标题就是他们自己的产品名;非 iframe 节点上的同名 token 必须**另有**「请你验证」字样才算数,而且那份旁证语汇**排除了它自己**(`reCAPTCHA` 里就含 `captcha`,不排除的话这条守卫是空的 —— 第一版就是这样,被测试当场抓出来)。
|
|
43
|
+
3. **文案(最后手段)** —— 标题里的 `安全验证` / `Just a moment...`,或控件上的命令式文案(`请输入验证码`、`拖动滑块完成拼图`);按「动词族」组合匹配而不是逐句枚举。**只有控件角色**(输入框 / 按钮 / 图片 / 滑块…)能凭「验证码」二字触发,`验证码说明` 这种帮助页链接不触发;`获取验证码` / `发送验证码` 这类正常登录控件也排除(另一个被测试抓出来的误判)。
|
|
44
|
+
4. **行为兜底(不认识任何厂商)** —— 动作之后 URL、标题、元素集合完全没变、且页面上几乎没有别的可点,就在这次结果里附一句「没有可见变化」,并**明说这是猜测**。这一层覆盖所有没有签名的厂商:新出现的验证方式靠它接住,代价只是多问用户一句,而不是拿账号去撞墙。
|
|
45
|
+
- **`interactiveOnly` 不再把 iframe 剪掉**(`src/injected.ts` 与 `src/snapshot-aria.ts` 两个引擎都改):iframe 的无障碍名就是内嵌控件自己的标题,是第 2 层唯一的证据。原来这个省 token 的开关一开,等于把验证码识别关掉 —— 一个**静默失效**。
|
|
46
|
+
- **已经认出是拦截面时,不再叠那句兜底提示**:「这是墙、原因是什么」才是可执行的信息,再叠一句更含糊的警告只是多花 prompt。
|
|
47
|
+
- **清掉仓库里漏网的 4 处本机绝对路径**(`README.md`、`PLUGIN-README.md`、`SNAPSHOT-RULES.md`、`scripts/autocommit.ps1`)。0.6.1 声称清干净了,实际漏了这四处;而它正是外部评审用来判断「这个项目离开作者的电脑就转不动」的证据。**这次改成脚本守**:`tests/doc-reality.test.ts` 新增守卫,扫全仓手写文本文件里「盘符 + 至少一段目录」的路径(已用探针验证:三条真实路径全部抓到,仓库里唯一命中是给读者的 `D:\path\to\…` 占位符,由白名单排除)。
|
|
48
|
+
- **npm 包从 85 个文件收到 75 个**:`files` 里那一整个 `scripts/` 收成逐个文件的白名单,只留 `doctor.mjs` 与 `dsh-browser-verify.mjs`(这两个只依赖 `lib/` 与 harness,装到 npm 的用户**能直接跑**)。顺带修掉一处假承诺:README 原来说 `measure-prompt-cost.mjs` 也随包可跑,但它依赖 `src/` 与 `shipped-gates.mjs`,在装好的包里**从来跑不起来**。`scripts/preflight.mjs` 的 `SHOULD_SHIP` 现在钉住这两个脚本,防止以后再被顺手丢掉。
|
|
49
|
+
- **新增上手一节「第一个任务」**(`README.md` / `PLUGIN-README.md` / `中文说明.md`):三条可以直接复制的话 + 你会看到什么 + 第一天两个习惯(先看再点、不可逆的那步说停)。四家外部评审独立提到「装完不知道第一步干什么、文档过载」,而 `examples/` 目录一直不存在。
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `tests/challenge.test.ts`:**35 条**用例,正例与**必须不触发**的反例各占一半 —— 讲 reCAPTCHA 的文章、`验证码说明` 帮助链接、`获取验证码` 按钮、只是提到「请输入验证码」的长段落、普通搜索页、只有「安全」二字的标题。另有一条守卫:旁证语汇不得被任何厂商自己的名字满足。
|
|
54
|
+
- `tests/scenarios.test.ts`:**4 条真浏览器端到端**。①真实 `cf-mitigated: challenge` 响应头一路走到渲染结果(钉住「监听 → 快照 → 判定 → 模型读到的文字」这条链,单测盖不住);②一个**完全没有任何签名**的拦截面(标题「请稍等」、一个点不动的按钮)由第 4 层兜底抓住,测试**先断言 `detectChallenge` 返回 null**,证明它不是靠签名抓到的;③`interactiveOnly` 下 iframe 名字仍在;④单页应用改 URL 后旧响应头不复用。
|
|
55
|
+
- `tests/snapshot-aria.test.ts`:`interactiveOnly` 保留 iframe 的用例。
|
|
56
|
+
- `tests/doc-reality.test.ts`:本机路径守卫(见上)。
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- **`PlaywrightSession.blockedSnapshot` 从未声明返回类型**,于是 `navigate()` 的推断返回类型变成一个联合类型,新加的字段在类型上根本不存在。类型检查抓出来的,不是运行期 —— 这正是把 `typecheck` 扩到 `tests/` 的价值。
|
|
61
|
+
- **兜底指纹原来只读两层**:深层文字变化会被误判成「页面没变」,于是在一个正常页面上报出「没有可见变化」的假警告。现在读到 4 层。
|
|
62
|
+
|
|
6
63
|
## 0.6.1 — 2026-10-16
|
|
7
64
|
### Changed
|
|
8
65
|
|
package/NOTICE.md
CHANGED
|
@@ -6,26 +6,25 @@ This repository mixes work from three different origins. They are **not** under
|
|
|
6
6
|
|
|
7
7
|
## 1. The plugin — MIT (`./LICENSE`)
|
|
8
8
|
|
|
9
|
-
Covers everything that is part of the plugin: `src/`, `tests/`, `scripts/`, `cordis.patch.yml`, `tsconfig.json`, `package.json`, `CHANGELOG.md`, and the plugin's own documentation (`PLUGIN-README.md`, `DIALOG-POLICY.md`, `SAFETY-RULES.md`, `SNAPSHOT-RULES.md`, `codex-rules.md`).
|
|
9
|
+
Covers everything that is part of the plugin: `src/`, `tests/`, `scripts/`, `cordis.patch.yml`, `tsconfig.json`, `package.json`, `CHANGELOG.md`, and the plugin's own documentation (`README.md`, `SECURITY.md`, `NOTICE.md`, `PLUGIN-README.md`, `DIALOG-POLICY.md`, `SAFETY-RULES.md`, `SNAPSHOT-RULES.md`, `codex-rules.md`, `中文说明.md`, `对比与优势.md`, and the articles under `docs/articles/`).
|
|
10
10
|
|
|
11
11
|
| Origin | What | Licence |
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| [ChenyuHeee/dsh-browser-playwright](https://github.com/ChenyuHeee/dsh-browser-playwright) v0.1.1 | The accessibility-snapshot engine (`src/injected.ts`, `src/snapshot-render.ts`), the tool family (`src/tool.ts`), the service/provider seams (`src/service.ts`, `src/playwright.ts`), the URL policy, and the test-suite shape. Copyright (c) 2026 dsh-browser-playwright contributors. | **MIT** |
|
|
14
|
-
| This fork's merged layer | Geometric click (`browser_click` with `precise: true`) with landing-element reporting, the CDP policy layer (`src/cdp-policy.ts`), the persistent profile + login-state export (`src/
|
|
14
|
+
| This fork's merged layer | Geometric click (`browser_click` with `precise: true`) with landing-element reporting, the CDP policy layer (`src/cdp-policy.ts`), the persistent profile + login-state export (`src/provider.ts`, `persistState`/`restoreState`), dialog parking, the console/network diagnostics tools, the contract/compat modules, and the fixes listed in [`CHANGELOG.md`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/CHANGELOG.md) | **MIT** |
|
|
15
15
|
|
|
16
16
|
**Why the plugin cannot be non-commercial:** it is a derivative of MIT-licensed work. MIT grants everyone the right to use, modify and redistribute the software **including commercially**, and a redistributor cannot add a non-commercial restriction to it. Any non-commercial claim over `src/` would be an unauthorised restriction — worse for downstream users than leaving it MIT. The MIT text in `./LICENSE` (including the upstream copyright notice) is unchanged.
|
|
17
17
|
|
|
18
18
|
## 2. This repository's analysis prose — CC BY-NC-SA 4.0 (`./LICENSE-DOCS`)
|
|
19
19
|
|
|
20
|
-
Covers only the
|
|
20
|
+
Covers only the two analysis documents produced *for this repository*:
|
|
21
21
|
|
|
22
|
-
- `README.md` (this repository's landing page)
|
|
23
22
|
- `FOCUS-STEALING.md`
|
|
24
23
|
- `MODE-TRADE-OFFS.md`
|
|
25
24
|
|
|
26
|
-
Share and adapt for **non-commercial** purposes with attribution and share-alike; commercial use is not permitted. Legal code: [`LICENSE-DOCS`](LICENSE-DOCS).
|
|
25
|
+
Share and adapt for **non-commercial** purposes with attribution and share-alike; commercial use is not permitted. Legal code: [`LICENSE-DOCS`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/LICENSE-DOCS) (the licence text lives in the repository; it is not part of the npm tarball).
|
|
27
26
|
|
|
28
|
-
Note the deliberate boundary:
|
|
27
|
+
Note the deliberate boundary: these two documents are **not shipped in the npm package** — only `README.md`, `CHANGELOG.md`, `SECURITY.md` and `NOTICE.md` travel with the plugin, and all four are MIT (§1). So everything an npm installer actually receives stays redistributable on MIT terms.
|
|
29
28
|
|
|
30
29
|
## 3. Third-party content that is **not** redistributed here
|
|
31
30
|
|
package/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> [](https://www.dsh.so/zh/artifact/dsh-browser-playwright-codex/) [](https://www.dsh.so/zh/artifact/dsh-browser-playwright-codex/)
|
|
4
4
|
|
|
5
|
-
**一句话**:让 AI 用你**已经登录的账号**在真实网页上干活 ——
|
|
5
|
+
**一句话**:让 AI 用你**已经登录的账号**在真实网页上干活 —— 填表、抓取列表、多标签查后台;浏览器窗口就在你桌面上、你随时看得见。原生弹框会被挂起、**绝不替你点「确定」**;验证码/登录页会被识别出来并把情况报给你、请你本人接手(识别不出来时也会停下来问人,而不是硬闯)。
|
|
6
6
|
|
|
7
7
|
**In one line**: the agent drives a **visible, profile-backed browser window** with the logins you
|
|
8
8
|
already have — filling in forms, scraping lists and working across tabs in real pages — through
|
|
9
|
-
accessibility snapshots with stable element refs
|
|
10
|
-
|
|
9
|
+
accessibility snapshots with stable element refs. Native dialogs are parked and reported instead of
|
|
10
|
+
auto-accepted; a login, 2FA or challenge page is handed back to you rather than guessed at. No
|
|
11
|
+
CSS-selector guessing, no full-DOM dumps.
|
|
11
12
|
|
|
12
13
|
```sh
|
|
13
14
|
# from npm
|
|
@@ -22,10 +23,16 @@ dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
|
|
|
22
23
|
- **登录一次,一直用** — a profile-backed window plus an exported session state, so a closed window or
|
|
23
24
|
a restarted harness comes back logged in.
|
|
24
25
|
- **全程看得见,也不抢你的焦点** — a real window you can watch, minimise or take over; the provider
|
|
25
|
-
keeps working while it is minimised ([FOCUS-STEALING.md](FOCUS-STEALING.md)).
|
|
26
|
+
keeps working while it is minimised ([FOCUS-STEALING.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/FOCUS-STEALING.md)).
|
|
26
27
|
- **遇事停下来叫人** — `alert` / `confirm` / `prompt` are parked and reported, answered only by an
|
|
27
|
-
explicit `browser_dialog` call
|
|
28
|
-
|
|
28
|
+
explicit `browser_dialog` call ([DIALOG-POLICY.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/DIALOG-POLICY.md)).
|
|
29
|
+
A challenge page is recognised from the interceptor's own response headers first
|
|
30
|
+
(`cf-mitigated: challenge` and friends; `x-iinfo` / `x-datadome*` additionally need a blocking
|
|
31
|
+
status), then from the vendor widget's accessible name, then from wording; if none of those match,
|
|
32
|
+
a page that stops changing after an action is reported as a possible wall instead of being retried.
|
|
33
|
+
Recognition adds a note that tells the agent to stop and hand the window back to you — that note is
|
|
34
|
+
advice to the model, not a hard block; the mechanism-level guarantee exists for native dialogs only
|
|
35
|
+
([docs/articles/dialogs-and-captcha.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/docs/articles/dialogs-and-captcha.md)).
|
|
29
36
|
- **ref 失效会明说** — per-document nonce refs; a stale ref fails fast with an explanation instead of
|
|
30
37
|
silently clicking a different element.
|
|
31
38
|
- **强力工具默认关闭** — `browser_evaluate` / `browser_cdp` are **not registered at all** unless your
|
|
@@ -33,9 +40,11 @@ dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
|
|
|
33
40
|
|
|
34
41
|
**安全一览 / Security at a glance** — dsh.so's scanner flags this repository's **one Critical finding**, and that finding **is** the
|
|
35
42
|
implementation of `browser_evaluate` (its dynamic evaluation), which this package ships switched off.
|
|
36
|
-
[SECURITY.md](SECURITY.md)
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
[SECURITY.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/SECURITY.md) says what each
|
|
44
|
+
finding actually is, whether a capability gate makes it reachable (two of the warnings are not gate-controlled:
|
|
45
|
+
the login-state write and the local test server in the acceptance script), and explains what the plugin can and
|
|
46
|
+
cannot do with your logins (URL policy, CDP allow-list, dialog policy, no telemetry, and it never types
|
|
47
|
+
passwords or 2FA codes). Counts move as the scanner and the code change — their own report is the
|
|
39
48
|
live number.
|
|
40
49
|
|
|
41
50
|
## Install
|
|
@@ -55,15 +64,44 @@ dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
|
|
|
55
64
|
dsh plugin --profile <name> add link:<absolute path to this directory>
|
|
56
65
|
```
|
|
57
66
|
|
|
58
|
-
**Compatibility.** Built and tested against DSH `0.2.0-rc.1
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
**Compatibility.** Built and tested against DSH `0.2.0-rc.1`. The dependency ranges are **floors, not
|
|
68
|
+
wildcards**: peers `@deepseek-ai/cordis` `>=4.0.4`, `@deepseek-ai/dsh-llm` `>=0.2.0-rc.1`,
|
|
69
|
+
`@deepseek-ai/dsh-tools` `>=0.2.0-rc.1`, and the direct dependency `@deepseek-ai/schemastery` `^3.18.4`. The
|
|
70
|
+
floors exist so an empty project cannot resolve a very old `0.0.1-rc.*` build, while a newer harness still
|
|
71
|
+
installs. One cost of npm's prerelease rules: a prerelease whose `[major, minor, patch]` tuple differs from
|
|
72
|
+
`0.2.0` (for example `0.2.1-rc.*`) is outside a `>=0.2.0-rc.1` range, so you may see a **peer warning** — a
|
|
73
|
+
warning, not an install failure (pnpm does not enforce peer ranges strictly by default). After a harness
|
|
74
|
+
upgrade, `npm run doctor` re-checks that every named export this plugin imports still exists — that is the
|
|
75
|
+
fast way to tell "the harness moved" from "the plugin broke".
|
|
61
76
|
|
|
62
77
|
`lib/` ships in the repository (and there is deliberately no `prepare` hook), so a fresh clone loads
|
|
63
78
|
without a build step — `npm run build` is only needed after editing `src/`.
|
|
64
79
|
|
|
65
80
|
The bundle mounts three rows: the `ctx.browser` seam (`service`), the Playwright provider (`playwright`), and the model-facing tool family (`tool`).
|
|
66
81
|
|
|
82
|
+
### Your first task
|
|
83
|
+
|
|
84
|
+
Install it and ask for something that leaves a visible result — there is nothing else to configure:
|
|
85
|
+
|
|
86
|
+
> Open `https://news.ycombinator.com` and list the titles of the first five stories with their links.
|
|
87
|
+
|
|
88
|
+
> Go to my orders page on `<site>` and tell me the delivery address on the most recent order.
|
|
89
|
+
|
|
90
|
+
> Fill in this contact form, but stop and show me the filled form before you submit it.
|
|
91
|
+
|
|
92
|
+
What you should see: a real browser window on your desktop doing the clicking, and the page coming back to
|
|
93
|
+
the model as a `ref`-numbered list. The first time it meets a sign-in, a 2FA prompt or a verification page
|
|
94
|
+
it **reports the page back to you** — take the window over by hand and it continues from where it left off.
|
|
95
|
+
Native dialogs are the one case that is enforced at the interface: they are parked and never auto-accepted.
|
|
96
|
+
The logins already in that profile are what make the rest work.
|
|
97
|
+
|
|
98
|
+
Two habits worth having on day one:
|
|
99
|
+
|
|
100
|
+
- **Let it look before it clicks.** Start from `browser_snapshot`; a `ref` is only valid in the result that
|
|
101
|
+
produced it (a stale one fails fast instead of clicking something else).
|
|
102
|
+
- **Say "stop" for the irreversible step.** Submitting, paying, deleting and sending are the points where
|
|
103
|
+
you want to read the form first.
|
|
104
|
+
|
|
67
105
|
### Browser binary
|
|
68
106
|
|
|
69
107
|
The provider probes `chromium` → `chrome` → `msedge` → `edge` channels automatically; any installed Chromium-family browser works with **zero download**. To use Playwright-managed Chromium instead:
|
|
@@ -78,7 +116,7 @@ Or pin a binary: `launch.executablePath` / `launch.channel` in the plugin config
|
|
|
78
116
|
|
|
79
117
|
## Tools
|
|
80
118
|
|
|
81
|
-
The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, so refs always come from the latest result. The registered set is capability-gated: `browser_evaluate`, `browser_cdp` and `browser_extract` appear only when their capability is switched on (their schemas are resident in the system prompt on every turn, so a tool that could only answer "disabled" would be pure prompt cost). Count the live surface with `npm run cost` — the shipped `cordis.patch.yml` keeps **both power gates off** (since 0.4.7), so a profile mounting this bundle sees **18 tools**; 19 or 20 once your deployment opts into `evaluate` / `cdp`, 21 once `extract` is configured. How to opt in:
|
|
119
|
+
The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, so refs always come from the latest result. The registered set is capability-gated: `browser_evaluate`, `browser_cdp` and `browser_extract` appear only when their capability is switched on (their schemas are resident in the system prompt on every turn, so a tool that could only answer "disabled" would be pure prompt cost). Count the live surface with `npm run cost` — the shipped `cordis.patch.yml` keeps **both power gates off** (since 0.4.7), so a profile mounting this bundle sees **18 tools**; 19 or 20 once your deployment opts into `evaluate` / `cdp`, 21 once `extract` is configured. How to opt in: paste the snippet under Known Limitations below, or use the settings card in `dsh-browser-toggle`.
|
|
82
120
|
|
|
83
121
|
| Tool | Purpose |
|
|
84
122
|
|---|---|
|
|
@@ -92,10 +130,10 @@ The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, s
|
|
|
92
130
|
| `browser_wait` | Wait a bounded duration for lazy content |
|
|
93
131
|
| `browser_tabs` / `browser_open_tab` / `browser_switch_tab` / `browser_close_tab` | Tab management; `browser_tabs` marks the `[active]` tab the other tools act on |
|
|
94
132
|
| `browser_screenshot` | PNG capture stored as a durable image attachment the model can view |
|
|
95
|
-
| `browser_extract` | Structured extraction: natural-language instruction → one parsed JSON value
|
|
96
|
-
| `browser_evaluate` | Run one JavaScript expression in the page (disabled by default)
|
|
133
|
+
| `browser_extract` | Structured extraction: natural-language instruction → one parsed JSON value; **only registered when its capability is enabled** |
|
|
134
|
+
| `browser_evaluate` | Run one JavaScript expression in the page (disabled by default); **only registered when its capability is enabled** |
|
|
97
135
|
| `browser_dialog` | Answer the native dialog blocking the page: `{ accept: true }` confirms, `false` cancels; errors with `NO_DIALOG` when nothing is pending |
|
|
98
|
-
| `browser_cdp` | Send one raw CDP command (DOM inspection / Input simulation allow-list only; disabled by default)
|
|
136
|
+
| `browser_cdp` | Send one raw CDP command (DOM inspection / Input simulation allow-list only; disabled by default); **only registered when its capability is enabled** |
|
|
99
137
|
| `browser_close` | Close the session's browser window; the profile keeps login state, so the next call reopens signed in |
|
|
100
138
|
| `browser_console_messages` | Read the page console messages captured for the current page (levels, text, source) |
|
|
101
139
|
| `browser_network_requests` | Read the network requests captured for the current page (method, URL, status, resource type) |
|
|
@@ -103,7 +141,8 @@ The default `toolPrefix` is `browser_`. Every action returns a fresh snapshot, s
|
|
|
103
141
|
### Parameter signatures
|
|
104
142
|
|
|
105
143
|
Every registered tool, with the parameters its schema actually declares (`*` = required, `?` = optional).
|
|
106
|
-
|
|
144
|
+
Hand-maintained and pinned by `tests/tool-schema.test.ts` in both directions: a missing tool, a renamed
|
|
145
|
+
parameter or a wrong required flag fails the test, so it cannot drift from the code.
|
|
107
146
|
|
|
108
147
|
- `browser_back` — _none_
|
|
109
148
|
- `browser_cdp` — `method`*, `params`? _(gated: registered only when enabled)_
|
|
@@ -128,7 +167,7 @@ Generated from the live tool surface and pinned by `tests/tool-schema.test.ts`,
|
|
|
128
167
|
- `browser_wait` — `ms`*
|
|
129
168
|
|
|
130
169
|
> **使用铁律**: 状态检查 / 无效不盲试 / 页面内容不可信 / 登录红线 / 风控分类等 Codex 文档纪律,
|
|
131
|
-
> 见 [`codex-rules.md`](codex-rules.md)(A 层已内嵌进各工具 description; B 层供粘贴进 DSH profile / 系统提示)。
|
|
170
|
+
> 见 [`codex-rules.md`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/codex-rules.md)(A 层已内嵌进各工具 description; B 层供粘贴进 DSH profile / 系统提示)。
|
|
132
171
|
|
|
133
172
|
### Snapshot example
|
|
134
173
|
|
|
@@ -146,6 +185,25 @@ Refs: 12
|
|
|
146
185
|
|
|
147
186
|
The model then calls `browser_click` with `ref: e2`. Invisible content is excluded, node and text caps bound the token cost, and truncation is flagged in the result.
|
|
148
187
|
|
|
188
|
+
#### Result notes
|
|
189
|
+
|
|
190
|
+
A result can open with one or more notes before the tree. Each says what the plugin saw; none of them
|
|
191
|
+
claims to *prevent* anything:
|
|
192
|
+
|
|
193
|
+
- `[human verification]` — a challenge page was recognised; the line above the note names the tier
|
|
194
|
+
(response header / widget / wording) and the evidence, and the note tells the agent to stop and ask
|
|
195
|
+
you to finish it in the open window instead of retrying.
|
|
196
|
+
- `[http NNN]` — the main document answered a non-2xx status and no challenge was identified, so the
|
|
197
|
+
tree may be an error page or an interception page rather than the content that was asked for.
|
|
198
|
+
- `[nothing here]` — the page has no clickable elements and almost no text: a gate or visitor check, a
|
|
199
|
+
page that is still loading, or one that needs a person. The note says to take one more snapshot,
|
|
200
|
+
then ask you rather than re-sending the same navigation.
|
|
201
|
+
- `[no visible change]` — the action left the page identical (after one action on a nearly empty page,
|
|
202
|
+
or after two identical results on a page with content). It is worded as a guess on purpose: it is
|
|
203
|
+
what an unrecognised wall looks like.
|
|
204
|
+
- A landing note (which element received the click), a parked-dialog note, and an engine-fallback note
|
|
205
|
+
can also appear.
|
|
206
|
+
|
|
149
207
|
An actionable element with no text of its own is named from its descendants — `<a href="/set"><span>搜索设置</span></a>` renders as `link "搜索设置" [ref=e7]`, and `<a href="/b"><img alt="图标链接"></a>` as `link "图标链接" [ref=e8]` — so a visible label and the ref that carries it always appear on the same line. A non-actionable child that merely repeats an ancestor name is folded away, so each label appears once, on the line that carries the ref; container roles are never named from their content (a `<select>` shows its chosen option, the way a text input shows its value).
|
|
150
208
|
|
|
151
209
|
## Configuration
|
|
@@ -168,9 +226,10 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
168
226
|
navigationTimeoutMs: 30000
|
|
169
227
|
ignoreHTTPSErrors: false
|
|
170
228
|
allowedDomains: [] # e.g. ['github.com'] restricts navigation
|
|
171
|
-
idleTimeoutMs:
|
|
229
|
+
idleTimeoutMs: 0 # shipped value: never auto-close (schema default 600000 = 10 min)
|
|
172
230
|
maxSessions: 8 # LRU eviction (persistent mode: one window)
|
|
173
231
|
snapshot:
|
|
232
|
+
engine: legacy # 'legacy' (default) or 'aria' (Playwright ariaSnapshot, refs aligned back)
|
|
174
233
|
maxNodes: 500 # token budget per snapshot tree
|
|
175
234
|
maxNameLength: 120
|
|
176
235
|
maxTextLength: 300
|
|
@@ -191,7 +250,12 @@ All tunables are patchable through the profile's `cordis.patch.yml` (later layer
|
|
|
191
250
|
maxOutputTokens: 2000
|
|
192
251
|
```
|
|
193
252
|
|
|
194
|
-
`
|
|
253
|
+
`snapshot.engine` picks the page-reading engine: `'legacy'` (the default) walks the injected DOM,
|
|
254
|
+
`'aria'` uses Playwright's official `ariaSnapshot({ mode: 'ai' })` and aligns the result back onto the
|
|
255
|
+
same `ref` ids; an aria tree that cannot be parsed falls back to legacy rather than handing the model an
|
|
256
|
+
empty tree. It is a `snapshot` key, next to `maxNodes` / `maxNameLength` / `maxTextLength` / `diff`.
|
|
257
|
+
|
|
258
|
+
`browser_extract` needs an auxiliary LLM route (`extract.provider` + `extract.model`); without one it fails with an actionable error. With a route configured, `extract.maxInputChars` bounds the page text in that prompt, and the prompt names the cap when it truncates. A gated tool is **not registered at all** while its capability is off, so its schema never reaches the system prompt; set `registerDisabledTools: true` to keep the whole surface discoverable (the tool then answers with the error naming the switch). The shipped layer in [`cordis.patch.yml`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/cordis.patch.yml) keeps `allowEvaluate` and `allowCdp` **off**, matching the schema defaults and this sample: that file travels inside the package, so anything it enabled would be enabled for everyone who installs it. Opt in per deployment, in that deployment's own patch layer. `browser_evaluate` executes arbitrary page JavaScript; `browser_cdp` stays off until `allowCdp: true`, and even then only the allow-list in `lib/cdp-policy.js` passes — network, storage, cookie, security and arbitrary-JS (`Runtime.evaluate`) commands are rejected with `CDP_DENIED`.
|
|
195
259
|
|
|
196
260
|
### Two levers that shrink what tools *return*
|
|
197
261
|
|
|
@@ -204,6 +268,9 @@ whose snapshots are long, and both are off by default because both change what t
|
|
|
204
268
|
anchor. Turn it on when a page re-renders often and you resent paying for the same tree twice.
|
|
205
269
|
- **`interactiveOnlyDefault`** (tool row) — makes `browser_snapshot` default to actionable elements
|
|
206
270
|
only; the model can still pass `interactiveOnly: false` per call when it needs the text around them.
|
|
271
|
+
Frames are kept either way: an iframe's accessible name is the embedded widget's own title, which is
|
|
272
|
+
how a challenge widget identifies itself, so pruning it would hide the wall the snapshot is meant to
|
|
273
|
+
reveal.
|
|
207
274
|
|
|
208
275
|
Both are honest trades, not free wins: a diff is smaller but requires the model to hold the previous
|
|
209
276
|
tree, and an interactive-only snapshot hides the prose the model may want to read. Measure before
|
|
@@ -248,13 +315,22 @@ npm install
|
|
|
248
315
|
npm test # full suite: engine/action integration on real Chrome, policy, rendering,
|
|
249
316
|
# schema assembly, plus the realistic scenario suites below
|
|
250
317
|
npm run verify # naming/folding + click/hover/dialog acceptance; self-contained, prints its own count
|
|
318
|
+
# (57 in a checkout; from an npm install it prints 56 + "1 skipped", because the
|
|
319
|
+
# `lib/`-vs-`src/` check has no `src/` to compare against)
|
|
251
320
|
npm run verify:live # same, plus a live sample of a real page (needs network)
|
|
252
321
|
npm run doctor # does the installed harness still export everything this plugin imports?
|
|
253
322
|
npm run cost # resident prompt cost of the tool schemas, per surface
|
|
254
323
|
npm run build # tsc → lib/
|
|
255
324
|
```
|
|
256
325
|
|
|
257
|
-
|
|
326
|
+
Two of those scripts ship with the package and work against an installed copy, because they need nothing
|
|
327
|
+
but `lib/` and the harness: `node scripts/dsh-browser-verify.mjs` (the acceptance suite) and
|
|
328
|
+
`node scripts/doctor.mjs` (re-checks the harness exports after an upgrade). The rest are repository-only
|
|
329
|
+
and run from a checkout — `measure-prompt-cost.mjs` reads `src/`, so it cannot work from a tarball.
|
|
330
|
+
|
|
331
|
+
One package-manager note: `package.json` declares `packageManager: pnpm@10.31.0`, while the commands above
|
|
332
|
+
(and CI) use **npm** with the committed `package-lock.json` (`npm ci`). Pick one and stay in it — mixing the
|
|
333
|
+
two in a checkout gives you two lockfiles and an ambiguous install.
|
|
258
334
|
|
|
259
335
|
### Test suites
|
|
260
336
|
|
|
@@ -263,15 +339,15 @@ npm run build # tsc → lib/
|
|
|
263
339
|
| Unit + engine integration | `npm test` (included) | Snapshot rendering, schema assembly, URL policy, provider actions on real Chrome |
|
|
264
340
|
| Realistic scenarios | `npm run test:scenarios` | Scripted-agent journeys over a realistic local store fixture: guest checkout with coupon and validation, login/session persistence, multi-tab research, lazy content, infinite scroll, modal dialogs, popups, screenshots, policy, owner isolation, idle disposal, snapshot budgets — at both the `BrowserSession` level and the model-facing `ctx.tools.execute` level (with fake attachments/LLM services) |
|
|
265
341
|
| Regression verify | `npm run verify` | The executable spec of snapshot naming/folding plus ref/text click-hover, the pending-dialog protocol and the tool surface (expected names derived from `contract.ts`) |
|
|
266
|
-
| Live smoke | `npm test:live` | Opt-in checks against the real internet (example.com, Wikipedia REST). Skipped unless `DSH_BROWSER_LIVE=1`; not part of CI |
|
|
342
|
+
| Live smoke | `npm run test:live` | Opt-in checks against the real internet (example.com, Wikipedia REST). Skipped unless `DSH_BROWSER_LIVE=1`; not part of CI |
|
|
267
343
|
|
|
268
344
|
The scenario suites are the plugin's most realistic tests: they drive the browser the way an agent does — snapshot, find a `ref`, act, re-snapshot — and therefore double as executable documentation of the model experience.
|
|
269
345
|
|
|
270
|
-
From a DeepSeek Harness source checkout, load this
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
346
|
+
From a DeepSeek Harness source checkout, the bundle layer to load is this repository's
|
|
347
|
+
[`cordis.patch.yml`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/cordis.patch.yml) — the
|
|
348
|
+
same file the package ships. Register the checkout through your profile with
|
|
349
|
+
`dsh plugin --profile <name> add link:<absolute path to this directory>` (above); this repository carries no
|
|
350
|
+
separate dev patch file.
|
|
275
351
|
|
|
276
352
|
## Model Experience
|
|
277
353
|
|
|
@@ -291,7 +367,19 @@ Measured with ``npm run cost`` (per-turn, resident in the system prompt):
|
|
|
291
367
|
| Shipped bundle layer (this package's `cordis.patch.yml`: both power gates off) | 18 | 12,755 | ≈ 3,543 |
|
|
292
368
|
| Everything registered (`registerDisabledTools: true`, plus `extract`) | 21 | 15,608 | ≈ 4,336 |
|
|
293
369
|
|
|
294
|
-
|
|
370
|
+
This paragraph describes the 0.6.0 change; the table above is today's measurement. Three things changed
|
|
371
|
+
there, and only one of them was about size. (1) The click family was rewritten so each rule is stated once
|
|
372
|
+
instead of three times: -390 characters, with every rule, example and rationale pinned by
|
|
373
|
+
`tests/tool-descriptions.test.ts`, so a trim that eats one fails the build. (2) A gated tool is registered only
|
|
374
|
+
while its capability is live: `browser_extract`'s schema alone is about 456 characters (≈ 127 tokens/turn),
|
|
375
|
+
which a deployment that leaves `extract` unconfigured does not pay — a separate comparison, so it is not part
|
|
376
|
+
of the net below. (3) Four descriptions were corrected to match the code they describe (dialog blocking, the
|
|
377
|
+
gated diagnostics `browser_fill` points at, persistent-mode `browser_close`, and the click family’s landing
|
|
378
|
+
note), which added 211 characters back because the old wording promised behaviour the implementation did not
|
|
379
|
+
have. Net for that release: 18,065 → 17,886 characters with everything registered. The shipped layer's total
|
|
380
|
+
is identical to the schema-defaults row above, because it keeps both power gates off — those schemas are not
|
|
381
|
+
registered at all and cost nothing. What is left is per-tool prose rather than repetition, so a further cut
|
|
382
|
+
trades away real guidance.
|
|
295
383
|
|
|
296
384
|
#### KV Cache effect
|
|
297
385
|
|
|
@@ -303,15 +391,18 @@ The page state itself (DOM, cookies, storage) lives in the browser context and n
|
|
|
303
391
|
|
|
304
392
|
## Further reading
|
|
305
393
|
|
|
306
|
-
Written for people deciding whether to hand an agent their browser
|
|
394
|
+
Written for people deciding whether to hand an agent their browser. These live in the GitHub repository
|
|
395
|
+
(`docs/articles/`); the npm tarball ships only `README.md`, `CHANGELOG.md`, `SECURITY.md` and `NOTICE.md`, so the
|
|
396
|
+
links below go to GitHub rather than to files inside the package:
|
|
307
397
|
|
|
308
|
-
- [让 AI 用你已经登录的账号干活](docs/articles/login-state.md) — why session cookies are the hard part, and
|
|
398
|
+
- [让 AI 用你已经登录的账号干活](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/docs/articles/login-state.md) — why session cookies are the hard part, and
|
|
309
399
|
what the export does to a minimised window.
|
|
310
|
-
- [AI 开着浏览器抢你的焦点:三个根因](docs/articles/focus-stealing.md) — all three traced into source, with a
|
|
311
|
-
|
|
312
|
-
- [遇到验证码和弹窗,为什么它不该替你点「确定」](docs/articles/dialogs-and-captcha.md) — mechanism-level
|
|
313
|
-
dialog parking, and the same philosophy behind
|
|
314
|
-
|
|
400
|
+
- [AI 开着浏览器抢你的焦点:三个根因](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/docs/articles/focus-stealing.md) — all three traced into source, with a
|
|
401
|
+
113-line reproduction script that needs no part of this plugin.
|
|
402
|
+
- [遇到验证码和弹窗,为什么它不该替你点「确定」](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/docs/articles/dialogs-and-captcha.md) — mechanism-level
|
|
403
|
+
dialog parking, what the challenge detector does and does not guarantee, and the same philosophy behind
|
|
404
|
+
stale refs failing loudly.
|
|
405
|
+
- [官方 Discussions 发帖草稿](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/docs/articles/discussions-post.md) — a compatibility report in the format the
|
|
315
406
|
community asks for, ready to paste.
|
|
316
407
|
|
|
317
408
|
## Known Limitations and Deferred Work
|
|
@@ -334,8 +425,9 @@ Written for people deciding whether to hand an agent their browser:
|
|
|
334
425
|
```
|
|
335
426
|
- **Chromium family only** — Firefox/WebKit channels are not probed; providers are swappable if another engine is needed.
|
|
336
427
|
- **Extract needs a dedicated model route** — it does not reuse the main request's route; misconfiguration fails loudly at call time.
|
|
337
|
-
- **JS-driven navigations are not statically checkable** — `allowedDomains` covers `browser_navigate`, `browser_open_tab`, every click
|
|
428
|
+
- **JS-driven navigations are not statically checkable** — `allowedDomains` covers `browser_navigate`, `browser_open_tab`, every navigating click (`browser_click`: `ref`, `text`, `precise` and raw-coordinate modes) and `browser_switch_tab`; `mode: "hover"` reveals content without activating anything, so it is not checked against the URL policy. A button whose handler runs `location.href = …` can still leave the allowed hosts, and so can a `<form>` whose `action` points at another host (pressing its submit button is a navigation this check does not statically see), and a tab that is already sitting on a disallowed host stays open (the agent simply refuses to drive it); deploy an external network guard for hard isolation.
|
|
338
429
|
- **Stability waits are bounded by design** — a perpetually animating element falls back to a forced click on its current rect rather than stalling; a page that never settles is snapshotted after the cap. This is intentional: the agent must never hang.
|
|
430
|
+
- **Challenge detection reads the responses it has actually seen** — the response-header tier applies to a document response this session observed after it attached to the tab. A tab that was already sitting on a challenge page when the session took it over falls through to the widget and wording tiers and, failing those, to the no-progress backstop: it reports a *possible* wall instead of guessing the vendor. A page that is not recognised still never goes silent: a non-2xx document carries an `[http NNN]` note, and a page with no clickable elements and almost no text carries a `[nothing here]` note.
|
|
339
431
|
|
|
340
432
|
### Ref and lifecycle safety (fixed)
|
|
341
433
|
|
|
@@ -355,7 +447,7 @@ MIT
|
|
|
355
447
|
<!-- changelog:today -->
|
|
356
448
|
## 更新记录
|
|
357
449
|
|
|
358
|
-
(此列表只记 **minor 里程碑**;每个补丁版本见 [CHANGELOG.md](CHANGELOG.md)。)
|
|
450
|
+
(此列表只记 **minor 里程碑**;每个补丁版本见 [CHANGELOG.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/CHANGELOG.md)。)
|
|
359
451
|
|
|
360
452
|
### 0.6.0(2026-10-16)
|
|
361
453
|
|
|
@@ -386,7 +478,7 @@ MIT
|
|
|
386
478
|
|
|
387
479
|
### 0.3.0(2026-09-26)
|
|
388
480
|
|
|
389
|
-
- **源码化**:插件重新变成有源码的工程 —— `src/` 10 个 TypeScript
|
|
481
|
+
- **源码化**:插件重新变成有源码的工程 —— `src/` 10 个 TypeScript 模块(当时;现在 `src/` 是 32 个 `.ts`,`lib/` 是 32 个 `.js` + 32 个 `.d.ts`)是唯一真源,`lib/` 由 `npm run build` 生成,不再手改产物。
|
|
390
482
|
- **代码规范**:引入 Prettier 配置(`.prettierrc.json`),`npm run format` / `npm run format:check` 统一格式;严格类型检查 0 错误。
|
|
391
483
|
- **新增两只诊断工具**:`browser_console_messages`(控制台报错)与 `browser_network_requests`(网络请求),用于"点了没反应"时快速定位原因。
|
|
392
484
|
- **接口与实现同步**:`BrowserSession` 补齐 9 个实现里已有的方法;`errors.ts` 补齐 12 个实际在用的错误码;`BrowserSnapshot` 补 `dialogNote` / `landingNote`;`TabInfo` 补 `active`;`runtime-state` / `cdp-policy` 新增类型声明。
|
package/SECURITY.md
CHANGED
|
@@ -6,7 +6,7 @@ the thing worth being careful about. This file answers the two questions a reade
|
|
|
6
6
|
1. **What can it do to me?**
|
|
7
7
|
2. **Why does dsh.so's scanner report a Critical finding?**
|
|
8
8
|
|
|
9
|
-
If you only read one line: **the
|
|
9
|
+
If you only read one line: **the Critical finding is the implementation of `browser_evaluate`, a tool
|
|
10
10
|
this package ships switched off** — it is not registered at all until a deployment turns it on.
|
|
11
11
|
|
|
12
12
|
---
|
|
@@ -20,13 +20,13 @@ the agent is supposed to stop and hand the page back to you.
|
|
|
20
20
|
|
|
21
21
|
**It is visible and interruptible.** The window lives on your desktop. You can watch every action,
|
|
22
22
|
minimise it (the provider detects a minimised window and keeps working without raising it — see
|
|
23
|
-
[FOCUS-STEALING.md](FOCUS-STEALING.md)), or close it: login state is exported first and restored on
|
|
23
|
+
[FOCUS-STEALING.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/FOCUS-STEALING.md)), or close it: login state is exported first and restored on
|
|
24
24
|
the next launch.
|
|
25
25
|
|
|
26
26
|
**Native dialogs are never auto-accepted.** `alert` / `confirm` / `prompt` / `beforeunload` are
|
|
27
27
|
parked as a pending state, reported to the model, and answered only by an explicit `browser_dialog`
|
|
28
28
|
call — a click that raises `confirm()` returns the parked dialog instead of accepting it. See
|
|
29
|
-
[DIALOG-POLICY.md](DIALOG-POLICY.md).
|
|
29
|
+
[DIALOG-POLICY.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/DIALOG-POLICY.md).
|
|
30
30
|
|
|
31
31
|
**Which hosts it may touch is policy, not goodwill.** `allowedDomains` is enforced on every path that
|
|
32
32
|
can navigate: `browser_navigate`, `browser_open_tab`, every click variant (ref, text, geometric,
|
|
@@ -55,7 +55,8 @@ While a gate is off the tool is **not registered at all**, so it does not exist
|
|
|
55
55
|
costs no prompt tokens. But this file travels inside the package, so if it enabled them, every
|
|
56
56
|
installer would get a backdoor they never asked for; the opt-in belongs to the deployment, and it
|
|
57
57
|
lives in that deployment's own patch layer (the bundled Settings card writes it, or edit your
|
|
58
|
-
profile patch — see the configuration section of the
|
|
58
|
+
profile patch — see the configuration section of the
|
|
59
|
+
[README](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/README.md#configuration)).
|
|
59
60
|
|
|
60
61
|
**No telemetry.** The plugin does not phone home and does not upload page content anywhere. The only
|
|
61
62
|
outbound traffic is what the page itself requests — plus one exception you control: `browser_extract`
|
|
@@ -71,8 +72,10 @@ Nothing else.
|
|
|
71
72
|
## 2. What the scanner reports, and which gate contains it
|
|
72
73
|
|
|
73
74
|
dsh.so runs `dsh-plugin-vet` over the whole repository. Their scan of **0.6.0 (2026-10-16)** reports
|
|
74
|
-
**1 Critical, 3 Warning, 25 Info** across 80 files — and it is **right about the capability**.
|
|
75
|
-
each
|
|
75
|
+
**1 Critical, 3 Warning, 25 Info** across 80 files — and it is **right about the capability**. Below is what
|
|
76
|
+
each reported item actually is, and what decides whether it is reachable. Not every item sits behind a
|
|
77
|
+
capability gate: the login-state write happens whenever login persistence works, and the acceptance script's
|
|
78
|
+
local server exists only while that script runs.
|
|
76
79
|
|
|
77
80
|
> For the record: their v0.4.9 scan — before this plugin was split into responsibility-scoped modules —
|
|
78
81
|
> reported 2 Critical, 5 Warning, 26 Info across 57 files. Counts move as the scanner and the code
|
|
@@ -85,6 +88,9 @@ each one, with the gate that decides whether it is reachable at all.
|
|
|
85
88
|
| Warning | HTTP request to a bare IP (`scripts/dsh-browser-verify.mjs`) | The acceptance suite starts a **local** `http.server` on `127.0.0.1` with a fixture page, so `npm run verify` needs no DSH and no internet. | Only when you run that script. |
|
|
86
89
|
| Info ×25 | `localStorage` reads, `process.env` reads, hardcoded `127.0.0.1`, dev-only probe scripts | Restoring login state; test hooks (`DSH_BROWSER_STATE_FILE`, `PLAYWRIGHT_DOWNLOAD_HOST`); and scripts under `scripts/analysis/` that the scanner itself marks *"Dev-only file — not executed at install/runtime"*. | — |
|
|
87
90
|
|
|
91
|
+
The scanner counts the two `fs.writeFileSync` call sites (the login-state export and the small runtime-state
|
|
92
|
+
file) as **two separate Warning findings**; they share one row above because they are one persistence path.
|
|
93
|
+
|
|
88
94
|
Two honest notes:
|
|
89
95
|
|
|
90
96
|
- These numbers **will not go to zero**, because `browser_evaluate` exists in the code whether or not
|
|
@@ -92,22 +98,23 @@ Two honest notes:
|
|
|
92
98
|
- We did **not** try to hide the finding (moving the call, obfuscating it, or suppressing the rule).
|
|
93
99
|
That would be deceiving the tool that is trying to protect users, and it would make this file a lie.
|
|
94
100
|
- Module names, not line numbers: this file used to cite a line number in `src/playwright.ts`, which the
|
|
95
|
-
0.5.x split made meaningless (the code lives in `src/session.ts` now, and
|
|
96
|
-
|
|
101
|
+
0.5.x split made meaningless (the code lives in `src/session.ts` now, and its entry point
|
|
102
|
+
`src/playwright.ts` is 56 lines). File names survive refactors; line numbers do not.
|
|
97
103
|
|
|
98
104
|
## 3. How to check all of this yourself
|
|
99
105
|
|
|
100
106
|
```sh
|
|
101
|
-
npm test #
|
|
107
|
+
npm test # 219 tests: contracts, tool schemas, scenarios,
|
|
102
108
|
# aria engine, CDP policy, diff, click/tab allow-lists, snapshot rendering,
|
|
103
109
|
# prompt budget, docs-vs-reality consistency
|
|
104
|
-
npm run verify # 57 self-contained acceptance checks
|
|
110
|
+
npm run verify # 57 self-contained acceptance checks in a checkout (56 checks + 1 skipped
|
|
111
|
+
# from an npm install, where there is no src/): no DSH, no network, local fixture server
|
|
105
112
|
npm run doctor # the host packages this plugin imports are still exporting what it uses
|
|
106
113
|
npm run cost # resident prompt cost of the registered tool surface
|
|
107
114
|
```
|
|
108
115
|
|
|
109
116
|
- CI runs the whole suite on **ubuntu-latest and windows-latest** for every push
|
|
110
|
-
([workflow](.github/workflows/ci.yml)).
|
|
117
|
+
([workflow](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/.github/workflows/ci.yml)).
|
|
111
118
|
- The gates really are off in the package:
|
|
112
119
|
`git show v0.4.7:cordis.patch.yml` (0.4.7 is where that changed).
|
|
113
120
|
- Sandbox evidence, produced by someone else: [dsh.so's L4/L5 records](https://www.dsh.so/zh/artifact/dsh-browser-playwright-codex/)
|