dsh-browser-playwright-codex 0.5.1
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 +486 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/SECURITY.md +114 -0
- package/cordis.patch.yml +66 -0
- package/lib/cdp-policy.d.ts +69 -0
- package/lib/cdp-policy.js +105 -0
- package/lib/compat.d.ts +37 -0
- package/lib/compat.js +31 -0
- package/lib/contract.d.ts +74 -0
- package/lib/contract.js +117 -0
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +21 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +12 -0
- package/lib/injected.d.ts +35 -0
- package/lib/injected.js +350 -0
- package/lib/playwright.d.ts +647 -0
- package/lib/playwright.js +2515 -0
- package/lib/runtime-state.d.ts +45 -0
- package/lib/runtime-state.js +181 -0
- package/lib/service.d.ts +56 -0
- package/lib/service.js +96 -0
- package/lib/snapshot-aria.d.ts +121 -0
- package/lib/snapshot-aria.js +485 -0
- package/lib/snapshot-render.d.ts +28 -0
- package/lib/snapshot-render.js +100 -0
- package/lib/tool.d.ts +77 -0
- package/lib/tool.js +907 -0
- package/lib/types.d.ts +242 -0
- package/lib/types.js +5 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +108 -0
- package/scripts/analysis/hl-compare.mjs +149 -0
- package/scripts/analysis/live-handoff-probe.mjs +97 -0
- package/scripts/analysis/needs-human-probe.mjs +83 -0
- package/scripts/analysis/repro-minimize.mjs +112 -0
- package/scripts/autocommit.ps1 +23 -0
- package/scripts/check-doc-links.py +112 -0
- package/scripts/doctor.mjs +144 -0
- package/scripts/dsh-browser-verify.mjs +475 -0
- package/scripts/measure-prompt-cost.mjs +57 -0
- package/scripts/prompt-cost.mjs +73 -0
- package/scripts/shipped-gates.mjs +43 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Bilingual by intent: the version headings and the summary of each release are given in both
|
|
4
|
+
languages, the individual entries follow the language the surrounding code and docs use.
|
|
5
|
+
|
|
6
|
+
一句话摘要:**把「看着危险」变成「说得清楚」** —— 新增 `SECURITY.md`,把 dsh.so 扫描报的 **2 Critical / 5 Warning / 26 Info** 逐条对上:那 2 个 Critical 就是 `browser_evaluate` 的实现,而**包里默认关闭**(关着时工具根本不注册)。README 第一屏重写(中文一句话 + 一条命令安装 + 五条差异点 + 安全入口),并又清掉两处「出厂开启闸门」的过期说法。
|
|
7
|
+
|
|
8
|
+
## 0.5.1
|
|
9
|
+
|
|
10
|
+
一句话摘要:**npm 首发 + 元数据写成用户会搜的词** —— 包以 `dsh-browser-playwright-codex` 发布到 npm,于是安装可以只写包名;同时把 `package.json` 的 description / keywords 从术语改成需求词,补上 `repository` / `homepage`,并加 `prepublishOnly`(发布前强制 build + test)。**运行时一行未改。**
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Package metadata is written for search, not for maintainers.** The npm description used to open
|
|
15
|
+
with "accessibility-snapshot interaction with stable element refs" — precise, and useless in a search
|
|
16
|
+
box. It now opens with what a user does (fill in forms, scrape lists, work across tabs, using the
|
|
17
|
+
logins you already have); keywords gained `automation`, `browser-automation`, `web-scraping`,
|
|
18
|
+
`form-filling`, `deepseek`, `ai-agent`, `rpa`; `repository` and `homepage` were missing entirely, so
|
|
19
|
+
the npm page had no link back to its source.
|
|
20
|
+
- **The README first screen carries the same words** (Chinese and English), because it is the other half
|
|
21
|
+
of the same search surface — the Chinese one-liner used to say "上网办事", which nobody searches for.
|
|
22
|
+
- **GitHub topics gained the single-word head terms** (`browser`, `automation`, `web-automation`,
|
|
23
|
+
`browser-automation`, `web-scraping`, `form-filling`, `rpa`, `ai-agent`, `deepseek`), because the
|
|
24
|
+
repository search weights topics heavily.
|
|
25
|
+
- **`prepublishOnly: npm run build && npm test`** — a publish can no longer ship a `lib/` older than
|
|
26
|
+
`src/`, which is the one way this package could go out subtly broken.
|
|
27
|
+
- **The README 更新记录 now states it lists minor milestones only**, so a patch release cannot make the
|
|
28
|
+
log look like the project stopped at the previous version.
|
|
29
|
+
- **No runtime change:** `src/` and `lib/` are untouched; tools, gates and the CDP policy are exactly
|
|
30
|
+
what 0.5.0 shipped.
|
|
31
|
+
|
|
32
|
+
## 0.5.0
|
|
33
|
+
|
|
34
|
+
一句话摘要:**CDP 白名单从「家族前缀匹配」收紧为「精确逐条列举」** —— `DOM.get` / `DOM.query` 两个前缀不再放行任何东西(它们此前多授权了 9 个 DOM 调用,并且会授权这两个家族将来新增的成员);同时经复核收回 9 条更早手工列上、现已判定不需要的调试器/UA 内部特性。允许面**只收不放**:终表 19 条全部是原本就已放行的权限,插件自己的后台建页与窗口探测走内部会话、不受影响。
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **The CDP allow-list matches exact names only** — breaking for agent-issued CDP.
|
|
39
|
+
`DOM.get` / `DOM.query` were matched as families, which authorised nine DOM calls beyond the
|
|
40
|
+
reviewed list (`getAnchorElement`, `getDetachedDomNodes`, `getElementByRelation`, `getFileInfo`,
|
|
41
|
+
`getFlattenedDocument`, `getImplicitAnchorCandidates`, `getNodeStackTraces`, `getSearchResults`,
|
|
42
|
+
`getTopLayerElements`) and would have authorised every future member of those families without
|
|
43
|
+
anyone noticing. The policy is now 19 full method names (`CDP_ALLOWED_METHODS`, frozen) compared
|
|
44
|
+
by equality, so an unknown name is denied by construction.
|
|
45
|
+
- **Nine more DOM methods were reviewed out** (hand-listed earlier, not prefix spill):
|
|
46
|
+
`collectClassNamesFromSubtree`, `getContainerForNode`, `getFrameOwner`,
|
|
47
|
+
`getNodesForSubtreeByStyle`, `getQueryingDescendantsForContainer`, `getRelayoutBoundary`,
|
|
48
|
+
`performSearch`, `requestChildNodes`, `requestNode`. All eighteen stay denied and stay listed as
|
|
49
|
+
denied in the tests: bringing one back is a policy decision, not a bug fix.
|
|
50
|
+
- **Nothing gained:** every one of the 19 allowed names was already allowed before this change, and the
|
|
51
|
+
plugin's own `Target.createTarget` / `Browser.getWindowForTarget` calls use the internal session, so
|
|
52
|
+
background tabs and the window probe are unaffected.
|
|
53
|
+
- **The tests pin the property, not examples.** `tests/cdp-policy.test.ts` writes the reviewed list out
|
|
54
|
+
independently and asserts it equals the source (so widening the policy takes two edits and shows up in
|
|
55
|
+
review), and a probe set — uppercase/lowercase/digit/dot suffixes, case flips, truncation, trailing
|
|
56
|
+
space, whole-domain prefixes — is filtered through the real matcher and compared as a set, so any
|
|
57
|
+
looser matching rule fails the suite. Written test-first: against the old implementation these cases
|
|
58
|
+
fail (`DOM.getDocumentZzz` rode in; `DOM.getAnchorElement` was allowed; the two lists disagreed).
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- **The README update log was missing this version's heading** (docs). The 0.5.0 entry had been filed
|
|
63
|
+
under the 0.4.0 heading, so the first thing a visitor read — in the repository `v0.5.0` points at —
|
|
64
|
+
said the project stopped at 0.4.0 while the release list showed v0.5.0 as latest. The entry now opens
|
|
65
|
+
its own section, and a guard asserts that the opening entry is always the newest minor release
|
|
66
|
+
(`tests/docs-consistency.test.ts`). No runtime code changed for this fix: `src/` and `lib/` are
|
|
67
|
+
identical to the earlier 0.5.0 commit, which is why the tag was moved instead of a 0.5.1 being cut.
|
|
68
|
+
|
|
69
|
+
## 0.4.10
|
|
70
|
+
|
|
71
|
+
一句话摘要(0.4.9):**文档对齐 + 指向配套件** —— README 里那句「出厂 patch 把两个闸门打开」在 0.4.7 之后已经过期(现在包里保持关闭),改成事实;同时指向新的配套仓库 `dsh-browser-toggle`(设置页卡片,那两个开关就在上面)。
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **`SECURITY.md`.** The scanner's output is the first thing a cautious user reads, and it said
|
|
76
|
+
"2 Critical · dynamic code execution" with no context. The file now maps every finding to what it
|
|
77
|
+
actually is and to the gate that decides whether it is reachable at all: both Criticals *are* the
|
|
78
|
+
body of `browser_evaluate`, a tool this package ships switched off, so it is not registered until a
|
|
79
|
+
deployment opts in. It also states plainly what the plugin can and cannot do with your logins (URL
|
|
80
|
+
policy and its limits, dialog parking, the CDP allow-list, where it writes, no telemetry, and that
|
|
81
|
+
the agent never types passwords or 2FA codes), plus how to re-check all of it yourself
|
|
82
|
+
(`npm test` / `npm run verify` / `npm run doctor` / CI / the sandbox records).
|
|
83
|
+
- It says the honest thing twice: the finding count **will not go to zero** (a scanner reads code,
|
|
84
|
+
not runtime gates), and we did not try to hide or suppress it.
|
|
85
|
+
|
|
86
|
+
### Changed
|
|
87
|
+
|
|
88
|
+
- **README first screen rewritten.** It used to open with one 90-word sentence that only a maintainer
|
|
89
|
+
would finish. Now: a one-line "what it does for you" in Chinese and English, the single install
|
|
90
|
+
command, five concrete differences (login reuse, watchable window that does not steal focus,
|
|
91
|
+
stopping for logins/captchas/dialogs, honest stale-ref failure, power tools off), then a pointer to
|
|
92
|
+
`SECURITY.md`. The technical detail is all still below the fold.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- **Two more stale "the shipped layer enables the gates" claims** (README's tool section and its
|
|
97
|
+
mirror `PLUGIN-README.md`) — the third and fourth appearance of that sentence, both now stating
|
|
98
|
+
that the package ships `allowEvaluate: false` / `allowCdp: false` and pointing at `SECURITY.md` §1.
|
|
99
|
+
|
|
100
|
+
## 0.4.9
|
|
101
|
+
|
|
102
|
+
一句话摘要(0.4.8):**设置页能如实显示并可切换两个强力工具** —— 工具族在注册时发布自己真正使用的闸门值,设置页显示的就是事实;配套卡片把用户的选择写进部署自己的配置层。
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- **The READMEs no longer claim the shipped layer enables the gates.** They said the packaged
|
|
107
|
+
`cordis.patch.yml` turns `allowEvaluate`/`allowCdp` **on** ("user choice, 2026-09-04") and even
|
|
108
|
+
flagged the contradiction with the sample above it. 0.4.7 removed that, so the sentence described
|
|
109
|
+
a state that no longer exists — exactly the kind of stale claim a third-party reader would take at
|
|
110
|
+
face value. Both files now state what the package does: keeps them off, because the file travels
|
|
111
|
+
inside the package and anything it enabled would be enabled for every installer.
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
|
|
115
|
+
- **A pointer to the companion package** [`dsh-browser-toggle`](https://github.com/miqian-nomad/dsh-browser-toggle),
|
|
116
|
+
with the reason the card cannot live in this package: `dsh-client-modules` rejects a package mounted
|
|
117
|
+
from multiple loader entries, and this one is mounted as three (`service`, `playwright`, `tool`),
|
|
118
|
+
while a Settings card needs a client module. The card writes the gates into the deployment's
|
|
119
|
+
home-level `cordis.patch.yml`, so what you download is never the thing that decided to enable them.
|
|
120
|
+
|
|
121
|
+
## 0.4.8
|
|
122
|
+
|
|
123
|
+
一句话摘要(0.4.7):**智能模式原来几乎从未真正生效** —— 解析器把 `/placeholder:` 这类属性行当成「格式漂移」,于是**任何带输入框提示语的页面都静默退回兼容模式**;同时「先 flags 后跟冒号」的内联文字被丢掉,导致段落文字整条消失。两个都修了,并给智能模式补上端到端验收与「已回退」明示。另外:**包里不再默认打开 `browser_evaluate` / `browser_cdp`**(那是发给每个安装者的后门),本机授权改由 profile 配置承担。
|
|
124
|
+
|
|
125
|
+
### Added
|
|
126
|
+
|
|
127
|
+
- **`runtime-state.publishGates` / `getGates`.** The tool family publishes the gates it actually
|
|
128
|
+
applied, so a Settings UI can show what is in effect instead of guessing at the package default —
|
|
129
|
+
the value may come from the bundle patch, a profile patch or a home-level patch. Published from the
|
|
130
|
+
same set that decides which tools register, so the two can never disagree. Purely additive: no
|
|
131
|
+
behaviour change on its own (99 tests / 96 pass, verify 57/57, CI green on both runners).
|
|
132
|
+
|
|
133
|
+
## 0.4.7
|
|
134
|
+
|
|
135
|
+
一句话摘要(0.4.6):**日志不再跨标签页混流** —— `browser_console_messages` / `browser_network_requests` 承诺给的是「当前这个标签页」的日志,实际给的是整个会话里所有标签页混在一起的大锅;现在只报当前页,并明确写出「另有 N 条来自其它标签页、已排除」。顺带修掉新开标签页**第一段导航完全没有日志采集**的问题。
|
|
136
|
+
|
|
137
|
+
### Fixed
|
|
138
|
+
|
|
139
|
+
- **Smart mode almost never actually ran, and said nothing.** The aria parser treated any
|
|
140
|
+
attribute line other than `/url:` as format drift; an input with a `placeholder` produces
|
|
141
|
+
`- /placeholder: …`, so the capture layer refused the whole tree and fell back to the legacy
|
|
142
|
+
engine — silently, on most real pages (measured 2026-10-02: the Settings switch said
|
|
143
|
+
"smart mode" while the engine never got to run). Attribute lines carry no tree structure, so
|
|
144
|
+
they are now parsed (`/placeholder` names the input the same way the legacy walker does) and
|
|
145
|
+
unknown ones are ignored instead of refused.
|
|
146
|
+
- **Inline text after flags was dropped, so paragraphs lost their text.** `- paragraph [ref=e5]: (未点)`
|
|
147
|
+
parsed to a nameless, childless node — and nameless childless nodes are pruned, so in smart mode
|
|
148
|
+
every paragraph rendered without its content. The text after the last `]` is now the node's name.
|
|
149
|
+
- **The fallback is no longer silent.** When the configured reader cannot read a page, the snapshot
|
|
150
|
+
carries a `[page reader]` line saying the tree came from the compatible engine (and that refs below
|
|
151
|
+
belong to it). A user who explicitly picked smart mode now sees when it is not what they are reading.
|
|
152
|
+
- **The package no longer ships the power gates on.** `cordis.patch.yml` travels inside the package, so
|
|
153
|
+
the `allowEvaluate: true` / `allowCdp: true` it used to carry (one machine's 2026-09-04 choice) was
|
|
154
|
+
handed to **every installer** — arbitrary page JavaScript plus raw CDP, on by default. Both gates now
|
|
155
|
+
ship `false` (matching the code defaults and this README's own example), and per-deployment opt-in
|
|
156
|
+
lives in that deployment's profile patch. This is also what a third-party scanner reads as
|
|
157
|
+
"dynamic code execution, critical": their finding was right about the capability, and the packaging
|
|
158
|
+
was what made it everyone's default.
|
|
159
|
+
|
|
160
|
+
### Added
|
|
161
|
+
|
|
162
|
+
- **Smart-mode end-to-end acceptance** (`npm run verify`, phase `[8]`): a page with hidden actionable
|
|
163
|
+
elements *before* the form — the exact shape that used to shift every ref — is read with the aria
|
|
164
|
+
engine, then the ref from that snapshot must click the element it names (and must not touch the
|
|
165
|
+
hidden element ahead of it). The acceptance suite is now 57 checks and covers both engines.
|
|
166
|
+
- Guard tests for both parser bugs, plus the earlier ref-alignment guards.
|
|
167
|
+
|
|
168
|
+
## 0.4.6
|
|
169
|
+
|
|
170
|
+
一句话摘要(0.4.5):**修掉智能模式下的 ref 指错元素** —— 它按「位置」把 DOM 里所有可交互元素(含隐藏的)贴到 aria 树里只有可见的节点上,页面上一有隐藏副本就整体错位;模型照着快照里的 ref 操作,可能命中另一个元素。
|
|
171
|
+
|
|
172
|
+
### Fixed
|
|
173
|
+
|
|
174
|
+
- **The diagnostics tools served every tab's entries as if they belonged to the driven tab.** Both
|
|
175
|
+
rings live on the session (deliberately: chronology and eviction stay honest across a tab switch),
|
|
176
|
+
but neither reader scoped its output to the page it was reporting on, while the tool descriptions
|
|
177
|
+
promise "the tab this session is driving". Measured 2026-10-02: sitting on a GitHub OAuth page,
|
|
178
|
+
`browser_console_messages` offered 抖音 CORS errors, B站 warnings and dsh.so failed loads — a
|
|
179
|
+
misdiagnosis one step away for exactly the tool whose job is to answer "the action looked
|
|
180
|
+
successful but nothing changed — was it the page or the request?".
|
|
181
|
+
- Every entry now carries the page that produced it, the readers filter to the page the session is
|
|
182
|
+
driving, and the rendered output discloses the excluded count
|
|
183
|
+
(`[N matching entries from other tabs of the same window not shown …]`). An empty result now says
|
|
184
|
+
"this tab is quiet" instead of implying the whole window is.
|
|
185
|
+
- **Second gap found while testing the first:** `browser_open_tab` never called `trackPage`, so a tab
|
|
186
|
+
opened through the tool had no console/network listeners during its first navigation — its own
|
|
187
|
+
load was invisible. Capture is now wired before that first `goto`.
|
|
188
|
+
- Guarded by a test that drives two tabs, makes each request a URL the other never uses, and asserts
|
|
189
|
+
that the driven tab is reported, the other tab is not, and the exclusion is disclosed rather than
|
|
190
|
+
silent.
|
|
191
|
+
|
|
192
|
+
## 0.4.5
|
|
193
|
+
|
|
194
|
+
一句话摘要(0.4.4):**撤回 0.4.3 加的 SPDX 行** —— 实测它反而让 dsh.so 的「许可证」字段从 `MIT` 变成 `NOASSERTION`(他们的匹配器是整文模板比对,多一行就失配)。`LICENSE` 恢复原样,许可条款从未改变。
|
|
195
|
+
|
|
196
|
+
### Fixed
|
|
197
|
+
|
|
198
|
+
- **Smart mode could hand the model a ref that names one element and resolves to another.** The aria
|
|
199
|
+
engine paired two independently produced lists **by position**: the DOM walk mints a ref for every
|
|
200
|
+
actionable element in document order, while the accessibility tree contains only what a screen
|
|
201
|
+
reader can reach. A single element the tree cannot see (a hidden menu copy, a 0×0 leftover from a
|
|
202
|
+
re-render) shifts every later ref onto a different element — and a click on such a ref can land on
|
|
203
|
+
another element **with no error at all**, which is precisely what this plugin promises never
|
|
204
|
+
happens ("a stale ref can never silently match a different element").
|
|
205
|
+
- Measured 2026-10-02 on `dsh-plugin.market/submit` (a React page): 7 hidden copies of the header
|
|
206
|
+
nav sat ahead of the form, so the tree reported the search box as `…411` while `…411` was an
|
|
207
|
+
invisible 0×0 link and the real input held `…423`. Both symptoms that started this investigation
|
|
208
|
+
— "the element is not visible" and "could not find an unobstructed click point" — were this bug,
|
|
209
|
+
not occlusion.
|
|
210
|
+
- **Fix, part 1 — stop minting refs for elements the tree cannot see.** The DOM-side ref walk now
|
|
211
|
+
skips the hidden attribute, `aria-hidden="true"`, `display:none`/`visibility:hidden` and
|
|
212
|
+
zero-size elements (`display:contents` keeps its slot, because its children lay out normally).
|
|
213
|
+
On the page above that turns 24 minted refs into the 17 the tree actually has, and the two
|
|
214
|
+
sequences line up exactly (verified live, including the textbox landing at slot 11).
|
|
215
|
+
- **Fix, part 2 — verify identity instead of assuming order.** Each consumed ref now carries the
|
|
216
|
+
walker's role and must match the node's role (with aliases for `textarea`→`textbox`,
|
|
217
|
+
`summary`/`image`→`button`), and leftover ref slots are refused when the tree was not truncated.
|
|
218
|
+
Any mismatch discards the capture (`missing`), so the provider falls back to the legacy engine —
|
|
219
|
+
which mints refs while it walks and therefore cannot drift. A ref is never guessed; the worst
|
|
220
|
+
case is an honest fallback.
|
|
221
|
+
- Guarded by four new tests: an aligned list passes, a shifted list is refused, leftover slots are
|
|
222
|
+
refused, and deliberate truncation is **not** mistaken for drift.
|
|
223
|
+
|
|
224
|
+
## 0.4.4
|
|
225
|
+
|
|
226
|
+
Reverted the SPDX line added in 0.4.3. Measured against dsh.so's submission checker it made the
|
|
227
|
+
license field *worse* while leaving the format note untouched, so `LICENSE` is back to the exact
|
|
228
|
+
0.4.2 bytes. No code change, and the license terms never changed at any point.
|
|
229
|
+
|
|
230
|
+
### Fixed
|
|
231
|
+
|
|
232
|
+
- **Reverted the SPDX line added in 0.4.3.** Measured against dsh.so's submission checker on a `v0.4.3` freeze: the license field changed from `MIT` to `NOASSERTION`, while the format note `找到许可证文件,但无法确定 SPDX 标识` stayed exactly as before. Their matcher compares the whole file against known license templates, so one extra leading line makes identification fail. `LICENSE` is byte-identical to the 0.4.2 version again — verified with `git diff v0.4.2 -- LICENSE` (empty).
|
|
233
|
+
- The remaining format note is a quirk of that checker, not a licensing problem: `package.json` declares `"license": "MIT"` and the file is the standard MIT text. It is a warning, not a blocker — the submission was accepted with it in place.
|
|
234
|
+
|
|
235
|
+
## 0.4.3
|
|
236
|
+
|
|
237
|
+
一句话摘要(0.4.3):**给 `LICENSE` 加上 SPDX 标识行** —— dsh.so 的提交检查读许可证文件时报「找到许可证文件,但无法确定 SPDX 标识」;MIT 正文本身是标准的、`package.json` 也写了 `"license": "MIT"`,但那一步要求标识出现在文件里。许可条款没有任何改动。
|
|
238
|
+
|
|
239
|
+
### Fixed
|
|
240
|
+
|
|
241
|
+
- **`LICENSE` now opens with `SPDX-License-Identifier: MIT`.** dsh.so's submission checker reads the
|
|
242
|
+
license file and reported `找到许可证文件,但无法确定 SPDX 标识` while scanning `v0.4.2`; the MIT
|
|
243
|
+
text was already standard and `package.json` already declared `"license": "MIT"`, but its detector
|
|
244
|
+
wants the identifier inside the file. Everything below that line is byte-identical to the previous
|
|
245
|
+
LICENSE.
|
|
246
|
+
|
|
247
|
+
## 0.4.2
|
|
248
|
+
|
|
249
|
+
一句话摘要(0.4.2):**最后一个已知缺陷查清并修掉了** —— 每次操作后的登录态导出会为「访问过但标签已关」的站点临时开一个标签去读 localStorage,而那个标签不带 `background`,于是把用户最小化的窗口拽回桌面。
|
|
250
|
+
|
|
251
|
+
Root cause of the last known defect, found in playwright-core's source and fixed in-plugin: the
|
|
252
|
+
post-operation login-state export used `context.storageState()`, which opens a temporary page for
|
|
253
|
+
every visited origin whose page is gone — a page created without `background`, which activates the
|
|
254
|
+
window.
|
|
255
|
+
|
|
256
|
+
### Fixed
|
|
257
|
+
|
|
258
|
+
- **A minimized window is no longer dragged back onto the screen by the login-state export.**
|
|
259
|
+
`context.storageState()` is not a pure read: for every origin the context has visited whose page is
|
|
260
|
+
since gone, it opens a **temporary page**, navigates it to that origin, reads its storage and closes
|
|
261
|
+
it (`playwright-core@1.62.1 lib/coreBundle.js:51663-51683`). The origin set is accumulated by
|
|
262
|
+
`addVisitedOrigin` on every navigation (`:22352` → `:51634`) and is only ever reset by
|
|
263
|
+
`setStorageState` (`:51737`), so a long-lived context always has leftovers. That page is created
|
|
264
|
+
through `Target.createTarget` **without `background: true`** (`:38340-38342`), and Chromium
|
|
265
|
+
activates the window on every tab creation — the same root as the earlier "tab creation raises the
|
|
266
|
+
window" fix, with a trigger nobody had suspected: Playwright's own storage bookkeeping, running
|
|
267
|
+
after every tool call.
|
|
268
|
+
- **Why the earlier minimal reproduction could not see it:** in a fresh process every visited origin
|
|
269
|
+
is still covered by an open page, so the temporary-page branch is never entered — `storageState()`
|
|
270
|
+
is genuinely safe there. It needs a long-lived context that has navigated away from sites it no
|
|
271
|
+
longer keeps open.
|
|
272
|
+
- **The fix:** when the window is minimized — or its state cannot be read — export **cookies only**
|
|
273
|
+
(`context.cookies()`; cookies never open a page, `:51639`). The full `context.storageState()` is
|
|
274
|
+
used only when the window is positively visible, where a temporary page disturbs nobody.
|
|
275
|
+
- This is strictly better than the mitigation it replaces: the old gate *skipped* the export while
|
|
276
|
+
minimized, so the session-cookie fallback had a hole exactly for the user most likely to close the
|
|
277
|
+
window by hand. The state file is a snapshot, and we no longer write origins we did not read —
|
|
278
|
+
which also removes a subtler hazard: the old gate left the *previous* origins list sitting in the
|
|
279
|
+
file, and `restoreState` re-applies that list with `addInitScript` on the next launch, overwriting
|
|
280
|
+
whatever the profile had stored since. No durable loss either way: localStorage lives in the
|
|
281
|
+
persistent profile.
|
|
282
|
+
- **Guards:** `tests/login-state.test.ts` drives that decision with a fake browser context (no
|
|
283
|
+
browser, no network), pinning which playwright API is used per window state. Measured against the
|
|
284
|
+
previous code it fails 3 of its 4 cases (it catches `storageState()` being reached while minimized,
|
|
285
|
+
and the throttled export being skipped); it passes 4/4 now.
|
|
286
|
+
|
|
287
|
+
### Docs
|
|
288
|
+
|
|
289
|
+
- `FOCUS-STEALING.md` Cause 3 is no longer "only half understood": the four-link mechanism is written
|
|
290
|
+
out with source lines, the "the A/B may have been confounded" alternative is withdrawn, and the
|
|
291
|
+
method table explains why `storageState()` measured *safe* in a fresh process and still raised the
|
|
292
|
+
window on a long-lived instance.
|
|
293
|
+
|
|
294
|
+
## 0.4.1
|
|
295
|
+
|
|
296
|
+
Adopted as the single canonical tree for this repository: the assets that only existed on the
|
|
297
|
+
GitHub side (docs, patches, analysis scripts, license/notice) are now here, `lib/` is committed
|
|
298
|
+
because this repository is the distribution channel, and the tests/verify no longer read the real
|
|
299
|
+
user settings file.
|
|
300
|
+
|
|
301
|
+
### Added
|
|
302
|
+
|
|
303
|
+
- **A settings-page switch for how pages are read** (`dsh-browser-toggle` card, right under
|
|
304
|
+
the existing on/off switch): 兼容模式 (the plugin's own DOM walk) ↔ 智能模式 (the browser's
|
|
305
|
+
official accessibility tree). It is persisted next to the enable flag and applies from the
|
|
306
|
+
next operation — no restart, no config edit. The card shows the mode actually in effect and
|
|
307
|
+
whether it is the user's pick or the deployment default, and the choice is rendered as radio
|
|
308
|
+
rows (the pattern the search-provider card uses for a pick-one setting) rather than a switch.
|
|
309
|
+
Landed after the 0.4.0 tag (`81d8769`), which is why it is listed here and not under 0.4.0.
|
|
310
|
+
- **`patches/`** — `upstream-window-activation.patch` plus its README: the minimized-window
|
|
311
|
+
focus-stealing fix as a patch against upstream, so the diagnosis is reusable outside this fork.
|
|
312
|
+
- **`scripts/analysis/`** — four probes (`repro-minimize`, `hl-compare`, `live-handoff-probe`,
|
|
313
|
+
`needs-human-probe`) used to produce the measured claims in `FOCUS-STEALING.md` and
|
|
314
|
+
`MODE-TRADE-OFFS.md`.
|
|
315
|
+
- **Docs** — `FOCUS-STEALING.md`, `MODE-TRADE-OFFS.md`, `PLUGIN-README.md`, `中文说明.md`,
|
|
316
|
+
`对比与优势.md`; **`LICENSE-DOCS`** and **`NOTICE.md`** (the docs' license and the provenance
|
|
317
|
+
statement). They sit at the repository root, next to the docs this tree already had, and their
|
|
318
|
+
cross-links were rewritten for that move.
|
|
319
|
+
- **`scripts/check-doc-links.py`** — dependency-free checker for every relative markdown link
|
|
320
|
+
(21 checked, 0 broken as of this release). It exists because moving a doc silently breaks
|
|
321
|
+
`../` links; run it after touching docs.
|
|
322
|
+
|
|
323
|
+
### Changed
|
|
324
|
+
|
|
325
|
+
- **`lib/` is committed again** (`.gitignore` now ignores only `lib/*.bak-*`). Reason: this
|
|
326
|
+
repository *is* the distribution channel — `dsh plugin add github:…` clones it, and a clone with
|
|
327
|
+
no build output has no `main` to load.
|
|
328
|
+
- **The `prepare` hook is gone** from `package.json#scripts`. pnpm 11 refuses to run a git
|
|
329
|
+
dependency's build scripts (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`), so keeping `prepare`
|
|
330
|
+
guarantees a one-command install fails on a clean machine. `npm run build` stays.
|
|
331
|
+
- **`lib/` was rebuilt from this tree's `src/`**, not taken from the GitHub side's older artifacts.
|
|
332
|
+
- The three docs that exist on both sides (`codex-rules.md`, `DIALOG-POLICY.md`,
|
|
333
|
+
`SNAPSHOT-RULES.md`) keep **this tree's** version: it already carries the `-codex` name and the
|
|
334
|
+
corrected paths, while the GitHub copies still said `dsh-browser-playwright`.
|
|
335
|
+
|
|
336
|
+
### Fixed
|
|
337
|
+
|
|
338
|
+
- **Tests and the acceptance script no longer read the real user settings file**
|
|
339
|
+
(`~/.dsh/dsh-browser-playwright.state.json`). Their expectations are written against the
|
|
340
|
+
`legacy` engine, so a user who picks 智能模式 (aria) on the settings page turned the suite red
|
|
341
|
+
while the code was untouched — measured 2026-10-01: reading the real state = 10 failures;
|
|
342
|
+
isolated = 0 failures. Both now point `DSH_BROWSER_STATE_FILE` at a non-existent temp path, the
|
|
343
|
+
override `src/runtime-state.ts` already provides for exactly this.
|
|
344
|
+
- **The README install command actually works now.** It said
|
|
345
|
+
`dsh plugin --profile <name> add dsh-browser-playwright-codex`, but this package is not on npm
|
|
346
|
+
(registry returns 404), so that one-liner could never have installed anything. It now shows the
|
|
347
|
+
GitHub specifier this repository is actually distributed by, plus the `link:` form for a local
|
|
348
|
+
checkout, and states that a fresh clone needs no build. The dsh.so install-verification badge is
|
|
349
|
+
back at the top of the README, pointing at [the artifact page](https://www.dsh.so/zh/artifact/dsh-browser-playwright-codex/).
|
|
350
|
+
The version inside that badge URL is the **dsh** version it was verified against, not the plugin
|
|
351
|
+
version — corrected on 2026-10-02, when the badge was moved from `0.1.7-rc.2` to `0.2.0-rc.1`
|
|
352
|
+
(dsh.so's newest verified row, 2026-09-30) and the risk badge was added. dsh.so's L4/L5
|
|
353
|
+
*install* verification artifact is still `v0.3.1` while its security scan already reads `0.4.1`;
|
|
354
|
+
both of those are theirs to refresh.
|
|
355
|
+
|
|
356
|
+
### Notes
|
|
357
|
+
|
|
358
|
+
- Local verification output (`test_*.txt`, `*_alone.txt`, `verify_clean.txt`) is now gitignored:
|
|
359
|
+
it is scratch from a run, not repository content.
|
|
360
|
+
|
|
361
|
+
## 0.4.0
|
|
362
|
+
|
|
363
|
+
Renamed to `dsh-browser-playwright-codex`, localized (Chinese plugin metadata), and given an
|
|
364
|
+
opt-in accessibility-tree snapshot engine plus incremental snapshot diffs — including the first
|
|
365
|
+
version's real defect, found, fixed, and guarded.
|
|
366
|
+
|
|
367
|
+
### Changed
|
|
368
|
+
|
|
369
|
+
- **Package renamed** `dsh-browser-playwright` → `dsh-browser-playwright-codex`. A rename is a
|
|
370
|
+
cross-file operation, so everything that carries the name moved with it: the three `name:`
|
|
371
|
+
specifiers in this package's `cordis.patch.yml`, the `dsh-browser-toggle` host import
|
|
372
|
+
(`…/runtime-state`) together with its `node_modules` link, and the profile link. Miss any one and
|
|
373
|
+
the plugin stops loading with `MODULE_NOT_FOUND` — measured: the old specifier no longer resolves
|
|
374
|
+
in either profile while the new one does.
|
|
375
|
+
- **The persisted switch file keeps its pre-rename name** (`~/.dsh/dsh-browser-playwright.state.json`)
|
|
376
|
+
on purpose: it is user state, and renaming it would silently reset the Settings toggle.
|
|
377
|
+
|
|
378
|
+
### Added
|
|
379
|
+
|
|
380
|
+
- **Plugin metadata localization** (`locale/en.json`, `locale/zh.json`), plus the
|
|
381
|
+
`"./locale/*.json"` export and a `locale` entry in `files` — without the export the harness'
|
|
382
|
+
`require.resolve` fails and the metadata silently falls back to English.
|
|
383
|
+
- **`snapshot.engine: 'aria'`** (default `'legacy'`) — a semantic tree from Playwright's official
|
|
384
|
+
`locator.ariaSnapshot({ mode: 'ai' })`, re-aligned onto the project's stable `data-dsh-ref`
|
|
385
|
+
scheme so refs, `REF_PATTERN` and `refLocator` keep working unchanged.
|
|
386
|
+
- **`snapshot.diff`** (default `false`) — incremental `+ / - / ~` deltas keyed by stable ref, with a
|
|
387
|
+
navigation-reset marker when the ref nonce changes, and full-snapshot degradation when the tree
|
|
388
|
+
was truncated.
|
|
389
|
+
|
|
390
|
+
### Fixed
|
|
391
|
+
|
|
392
|
+
- **The first aria engine was broken on real pages while its tests were green.** The parser assumed
|
|
393
|
+
a hand-written dialect (`- role "name" [flags] -> href`, nothing after the flags); real output ends
|
|
394
|
+
every container line with `:`, carries hrefs on a deeper `- /url:` child line, and writes text as
|
|
395
|
+
`- text: …`. On a 16-node page the parser produced 5 nodes: all structure and **every link** were
|
|
396
|
+
dropped, and the capture still reported success, so nothing fell back. Measured before/after with a
|
|
397
|
+
live `captureAriaSnapshot` probe.
|
|
398
|
+
- **Format drift is now loud.** Unreadable lines are collected (`parseAriaSnapshotWithStats`) and
|
|
399
|
+
refuse the capture (`missing: true`), and `captureAria` falls back to the legacy DOM walker instead
|
|
400
|
+
of handing the model an empty tree.
|
|
401
|
+
- The aria tests now parse **real captured YAML** (playwright-core 1.62, 2026-09-30) rather than a
|
|
402
|
+
fixture written in the parser's own dialect.
|
|
403
|
+
|
|
404
|
+
### Security
|
|
405
|
+
|
|
406
|
+
- **The CDP allow-list is method-explicit.** The family-wide `Input.` prefix is gone in favour of
|
|
407
|
+
`Input.dispatchMouseEvent` / `Input.dispatchKeyEvent` / `Input.dispatchTouchEvent` /
|
|
408
|
+
`Input.insertText`, so `Input.setIgnoreInputEvents`, `Input.synthesizeScrollGesture`,
|
|
409
|
+
`Input.dispatchDragEvent` and future `Input.*` mutations are denied by default; a widening-guard
|
|
410
|
+
test now asserts that **no** entry may end in `.` (the previous guard allowed exactly one).
|
|
411
|
+
|
|
412
|
+
## 0.3.0
|
|
413
|
+
|
|
414
|
+
### Added
|
|
415
|
+
|
|
416
|
+
- **A real source tree** (`src/`, 12 modules) with the compiled `lib/` produced from it
|
|
417
|
+
(`tsconfig.json` is `strict` + `exactOptionalPropertyTypes` + `noUncheckedIndexedAccess`;
|
|
418
|
+
`lib/` is gitignored and rebuilt by `npm run build` / the `prepare` hook).
|
|
419
|
+
- **A test suite** (71 tests: contract/tool-schema, provider, scenario journeys, CDP policy,
|
|
420
|
+
dialog policy, click/tab URL policy, snapshot rendering, descriptions) and a self-contained
|
|
421
|
+
acceptance script `npm run verify` (52 checks) that needs no DSH and no network.
|
|
422
|
+
- **`npm run doctor`** — checks that the harness packages this plugin actually imports still
|
|
423
|
+
export what it uses, plus where `playwright-core` resolves from at deploy time.
|
|
424
|
+
- **`npm run cost`** — resident prompt cost of the tool surface per configuration layer,
|
|
425
|
+
so a change that silently taxes every turn is visible.
|
|
426
|
+
- **`src/contract.ts`** — tool suffixes and failure codes in one place, with a **load-time
|
|
427
|
+
guard**: a tool the contract lists but the code never registers fails loudly at load instead
|
|
428
|
+
of vanishing from the model's view.
|
|
429
|
+
- **`src/compat.ts`** — the boundary adapter for borrowed harness identifiers (the harness
|
|
430
|
+
renames fast; `CallId` became `ToolCallId`).
|
|
431
|
+
- **Two model-facing diagnostics tools**: `browser_console_messages` and
|
|
432
|
+
`browser_network_requests`, with bounded ring buffers (200 entries, explicit dropped count,
|
|
433
|
+
truncated text/URL) and honest notes about what they do not capture.
|
|
434
|
+
|
|
435
|
+
### Fixed
|
|
436
|
+
|
|
437
|
+
- **`browser_cdp` was completely dead.** `CDPSession.send` was pulled off the session into a
|
|
438
|
+
bare function and called unbound, so every call died on `this._channel` and surfaced as a
|
|
439
|
+
misleading `CDP_ERROR: CDP command X failed`. It is bound now, and two gates keep it bound:
|
|
440
|
+
a tool-level round-trip test (`DOM.getDocument`) and a `verify` group that exercises
|
|
441
|
+
`session.cdp()` directly.
|
|
442
|
+
- **Non-persistent mode wrote the login-state export into the process working directory.**
|
|
443
|
+
`stateFile` was `''` while every reader/writer guarded on `undefined`, so `'' + '.tmp'`
|
|
444
|
+
landed in `<cwd>/.tmp` holding plaintext cookies and localStorage — and because the rename
|
|
445
|
+
always threw, the 1-second throttle never engaged and it ran on every tool call. The field
|
|
446
|
+
is `string | undefined` again (regression-tested, and the `.gitignore` line that had
|
|
447
|
+
legitimised the artifact is gone).
|
|
448
|
+
- **`allowedDomains` could be bypassed by `browser_click_at`** — both ref mode and raw
|
|
449
|
+
coordinates delivered the click without asking the policy, while `browser_click` refused it.
|
|
450
|
+
All click paths now share one rule (`assertClickTargetAllowed`).
|
|
451
|
+
- **`javascript:` links were unclickable through `browser_click`.** The policy is about which
|
|
452
|
+
hosts may be visited, but it was applied to every non-http(s) scheme, so ordinary
|
|
453
|
+
`<a href="javascript:void(0)">` links were refused on the guarded path while the unguarded
|
|
454
|
+
`click_at` path clicked them fine. `javascript:` is now exempt (it runs in-page and
|
|
455
|
+
navigates nowhere); `file:` / `data:` / `mailto:` / `tel:` / custom schemes stay refused.
|
|
456
|
+
- **A tab a page opened itself bypassed the allow-list.** `target="_blank"` / `window.open`
|
|
457
|
+
navigations never pass through the plugin, so `browser_switch_tab` now applies the host
|
|
458
|
+
policy before moving: a tab outside the allow-list stays open (the user may be using it) but
|
|
459
|
+
the agent refuses to drive it, and the session stays on the tab it was already on.
|
|
460
|
+
- **`extract.maxInputChars` now actually bounds the page data** handed to the auxiliary model
|
|
461
|
+
(it was read from config and then ignored), with the truncation marked in the payload.
|
|
462
|
+
- Prompt/doc corrections where the text promised behaviour the code did not have: the dialog
|
|
463
|
+
tool's "every other tool returns the pending dialog" (they fail with `DIALOG_PENDING`),
|
|
464
|
+
`browser_click_at`'s landing-note claims, `browser_fill` pointing at tools that may not be
|
|
465
|
+
registered, and persistent-mode `browser_close`.
|
|
466
|
+
|
|
467
|
+
### Changed
|
|
468
|
+
|
|
469
|
+
- Description budgets are pinned by tests (single description ≤ 1650 characters, whole surface
|
|
470
|
+
≤ 11,500) and the click family states each rule once; the wording fixes above added 211
|
|
471
|
+
characters back on purpose — the old text was cheaper because it was wrong.
|
|
472
|
+
- `browser_close` and `browser_switch_tab` semantics in persistent mode are now documented as
|
|
473
|
+
what they are (one shared window; the window closes with the last session).
|
|
474
|
+
|
|
475
|
+
### Compatibility
|
|
476
|
+
|
|
477
|
+
- No API changes: same tool names, same config keys, same failure codes.
|
|
478
|
+
- Two deliberate behaviour changes, both guard tightenings: `javascript:` links are clickable
|
|
479
|
+
again, and `switch_tab` refuses hosts outside `allowedDomains`.
|
|
480
|
+
|
|
481
|
+
## 0.2.0
|
|
482
|
+
|
|
483
|
+
- Codex-inspired layer merged onto upstream `dsh-browser-playwright` v0.1.1: geometric click
|
|
484
|
+
(`browser_click_at`) with landing-element reporting, the CDP policy layer, the persistent
|
|
485
|
+
profile with login-state export, parked native dialogs, snapshot/safety rules.
|
|
486
|
+
- Distributed as compiled output only; the source of that build was not on the machine.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-browser-playwright contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|