dsh-browser-playwright-codex 0.7.8 → 0.8.0
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 +64 -0
- package/NOTICE.md +26 -5
- package/README.md +27 -4
- package/lib/browser-lifecycle.js +27 -1
- package/lib/client.js +1219 -647
- package/lib/config.d.ts +26 -0
- package/lib/config.js +20 -0
- package/lib/page-actions.js +32 -1
- package/lib/provider.js +8 -0
- package/lib/read-deadline.d.ts +5 -1
- package/lib/read-deadline.js +10 -3
- package/lib/runtime-state.d.ts +26 -0
- package/lib/runtime-state.js +100 -0
- package/lib/session.d.ts +11 -0
- package/lib/session.js +25 -2
- package/lib/settings-card.js +123 -0
- package/lib/swarm-contract.d.ts +226 -0
- package/lib/swarm-contract.js +209 -0
- package/lib/swarm-curtain.d.ts +22 -0
- package/lib/swarm-curtain.js +115 -0
- package/lib/swarm-intro.d.ts +66 -0
- package/lib/swarm-intro.js +827 -0
- package/lib/tool.js +14 -4
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
+
## 0.8.0 — 2026-11-10
|
|
2
|
+
|
|
3
|
+
一句话摘要:**动效资产被移出 npm 发布包,swarm 默认关掉**——这是主动选的合规路径,不是修 bug:那套资产的授权状态不允许再分发,与其在授权上纠缠,不如不让它随包出去。代码一行没删,本地开发者把资产放回 `assets/` 打开开关就还是完整功能;代价是 npm 用户升级后默认看不到这个效果。顺带把 0.7.9 那批修复一起封进来——npm 上的 `latest` 一直停在 0.7.8,`v0.7.9` 只是本地 tag,所以从 0.7.8 直接升上来的用户会一次拿到两个版本的全部内容。
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- **`swarm.enabled` 默认值 `true` → `false`**:出厂不再播动效。资产不在包里的时候,默认开等于让每个新用户在"文件根本不存在"的状态下开箱即撞。
|
|
8
|
+
- **`assets/` 不再进入 npm 包**:`package.json` 的 `files` 移除了 `assets` 与 `!assets/swarm-rebuild.upstream.js`。仓库里两个文件都还在(`swarm-rebuild.upstream.js` 原始基线 + `swarm-rebuild.js` 补丁后产物),`npm run swarm:check` 的 SHA 校验照旧在本地生效,只是发布出去的 tarball 里没有了。
|
|
9
|
+
- **资产缺失现在静默降级,不是崩溃**:`swarmBootScript()` 读资产改成 `try/catch`,读不到就返回一段空注释脚本。这一点是本次改动的**安全底线**——它的两个调用点都长在 `await ctx.addInitScript({ content: swarmBootScript() })` 上,原来裸读会让资产缺失直接把上下文创建打断,浏览器连开都开不了,等于把"动效没了"放大成"插件挂了"。
|
|
10
|
+
- **`playSwarm` 的旧兜底继续有效**:`readScript()` 早就是 `catch → return`(资源缺失就当没这个功能),这次没有动它。
|
|
11
|
+
|
|
12
|
+
### 迁移
|
|
13
|
+
|
|
14
|
+
| 你的情况 | 升级后表现 | 要做的事 |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| 只是装包用浏览器 | 动效不再出现,其余功能不变 | 无需操作 |
|
|
17
|
+
| 想要动效 | 默认关,开了也看不到(包里没资产) | 把 `assets/swarm-rebuild.js` 放进包根 `assets/`,再把设置卡里 swarm 的 `enabled` 打开 |
|
|
18
|
+
| 本地从仓库开发 | 资产就在仓库里,开关一打开即恢复 | 无 |
|
|
19
|
+
|
|
20
|
+
以下这批是 0.7.9 的内容:**已经写好、但从未在 npm 上发布过**(`latest` 一直是 0.7.8,`v0.7.9` 只有本地 tag),随本版首次交付。
|
|
21
|
+
|
|
22
|
+
一句话摘要:**0.7.9 的行为对了,话没跟上**——超时之后实际做的是"把那一页关掉",报错文案却还在承诺"下一次调用会重开会话";用户会莫名其妙发现标签页少了一个,而且页面上没提交的内容跟着没了,全程没人告诉他。顺手补上真正走完整链路的回归测试:0.7.9 那三条是单元级假绿,删掉 `capture()` 里那行关页调用照样全过。
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **超时文案和真实行为对齐**:`withDeadline()` 新增 `consequence` 参数,由调用方写明超时之后**真的发生了什么**。`capture()` 现在报的是:那一页已经关掉、填了没提交的内容丢失、如果那是唯一标签则从空白页开始。默认值也去掉了"下一次调用会重开会话"这句不再成立的承诺。
|
|
27
|
+
- **关页代价写进代码注释**:`discardStuckPage()` 明说 `runBeforeUnload:false` 意味着不弹确认、未提交内容直接丢弃——对真卡死的页这笔账划算,但必须让人知道(现在由报错文案负责告知)。
|
|
28
|
+
|
|
29
|
+
### Tests
|
|
30
|
+
|
|
31
|
+
- **新增 `tests/read-timeout-e2e.test.ts`(真端到端)**:真浏览器打开一页 → 主线程真死循环 → 读内容真超时 → 断言 ① 报错是 `ACTION_TIMEOUT` 且文案写明页被关、未提交内容丢失;② 那张卡死的页 `isClosed()`;③ 下一次 `acquire()` 拿到的不是同一页,且读得动(活页)。变异验证:把 `capture()` 里 `await this.discardStuckPage(page)` 注释掉,这条用例在"the wedged page must actually be closed"处红;恢复后绿(约 4.5 秒)。
|
|
32
|
+
- **诚实标注旧用例的覆盖面**:`tests/regressions.test.ts` 里那三条(两条直调私有方法塞假页、一条正则读源码)注释里写明只是单元级、`capture()` 不调它就失效,端到端证据在 `read-timeout-e2e.test.ts`。
|
|
33
|
+
|
|
34
|
+
### Known trade-offs(未在本次行为里改动,仅记录)
|
|
35
|
+
|
|
36
|
+
- **导航默认不等 `load`**:首屏全靠脚本渲染的页面,快照可能拍到还没长出来的空壳;后面有"等页面稳定"兜着,影响被削弱。
|
|
37
|
+
- **启动锁重试的正则放宽**:任何启动超时都会触发"清一次锁再重试",卡住的原因与锁无关时(杀软拦截等)会白多等一个 30 秒窗口。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 0.7.9 — 2026-11-10
|
|
42
|
+
|
|
43
|
+
一句话摘要:**0.7.8 的硬期限只治了"读内容"那一步,四周还留着四个口子** ——最要紧的是读内容超时之后,下一次调用拿回来的还是同一个卡死的页面,所以 0.7.8 承诺的"自动换一个干净的句柄"从来没有真的发生。本版补上这一处,另外三处是顺势加固:启动阶段原本一次超时都没传、配置锁的重试一直在等一个不会出现的错误形状、导航默认等 `load` 让长连接页面白等到超时。这四项里只有第一项对得上 issue #5 报的"自愈无效",其余三项不是报告人提的,也不是原判定的缺口。发版自查时另外揪出一处 0.7.8 自己的毛病:期限定时器被 `unref`,进程里只剩它一个句柄时事件循环直接收工,那条 `ACTION_TIMEOUT` 永远发不出来——顺带让 7 条回归测试变成"未运行",而报告仍是 `fail 0`(详见下面第二条)。
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **超时之后把卡死的页面原样发回来**:`invalidateOwner()` 只把条目从表里删掉,持久化模式下 `ensurePage()` 下一次又按索引把同一个页面交回去,于是"下一次调用重开会话"这句承诺落空,页面继续烂在原地。现在超时后由 `discardStuckPage()` 真正关闭那一页——带期限、尽力而为,关不掉也不拖住报错路径——下一个 `acquire()` 从活着的标签页开始。
|
|
48
|
+
- **期限定时器不该 `unref`**:0.7.8 写的 `timer.unref?.()` 会让"到点必须断"变成"到点看心情"——若这是进程里最后一个句柄,事件循环判定无事可做直接退出,调用方等不到超时、也等不到别的东西。同样因为这个,`tests/read-deadline.test.ts` 那 7 条在孤立运行时全部被打成 `cancelledByParent`(`Promise resolution is still pending but the event loop has already resolved`),而 `node --test` 只报 `fail 0`、不报错,看起来是一片绿。所以 0.7.8 记的"通过 278 / 跳过 3 / 失败 0"里,实际只有 271 条真的跑过。去掉 `unref` 后 7 条全部实跑通过。
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- **启动阶段补上限**:`launchPersistentContext` 原来没传 timeout,配置目录被占用、单例握手卡住、GPU 初始化挂死都会让 `acquire()` 无限等下去。现在传 `max(navigationTimeoutMs, 30s)`——`navigationTimeoutMs` 允许低到 1 秒,冷启动浏览器根本不够用,所以另设 30 秒地板。
|
|
53
|
+
- **锁重试认错了错误形状**:原逻辑只匹配锁文件名,而 Playwright 把同一种情况报成 `Timeout 30000ms exceeded`,等于这段重试从来没触发过。现在两种形状都认。
|
|
54
|
+
- **导航默认不再等 `load`**:`navigate` / `open_tab` 的默认 `waitUntil` 从 `load` 改成 `domcontentloaded`。长轮询、流式、下载类页面的 `load` 永远不来,导航超时全花在一个早就可用的页面上。
|
|
55
|
+
- **README / PLUGIN-README 的 token 表按新测量重抄**:11,283 / 15,886 字符。
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
|
|
59
|
+
- 新增 3 条回归:卡死页只关一次、`close()` 不返回也不能再把错误路径挂住、两个默认值钉死。
|
|
60
|
+
- 全套 **305 条**:通过 302、跳过 3、失败 0,**302 + 3 = 305,一条没漏**。这次特意核对总数而不是只看 `fail 0`——0.7.8 就是在这个位置看走眼的。
|
|
61
|
+
- 复跑方式(本机 Node 22 需要显式打开类型剥离,否则 `npm test` 会以 `Unknown file extension ".ts"` 全崩):`set NODE_OPTIONS=--experimental-strip-types && npm test`。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
1
65
|
## 0.7.8 — 2026-11-10
|
|
2
66
|
|
|
3
67
|
一句话摘要:**工具调用不会再无限挂着** —— 打开网页之后"读页面内容"这一步一直没有超时上限,也不接取消信号,页面自己的脚本把浏览器主线程占住时,整个工具调用就停在那里等(GitHub issue #5 报的就是这个,实测能卡到分钟级)。现在读内容有硬期限、能按取消退出,超时后自动换一个干净的会话句柄。
|
package/NOTICE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# NOTICE — licensing split, provenance, and third-party content
|
|
2
2
|
|
|
3
|
-
This repository mixes work from
|
|
3
|
+
This repository mixes work from four different origins. They are **not** under one licence, and the split is deliberate.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本仓库的内容来自四个不同来源,**不是单一许可**,切分如下。
|
|
6
6
|
|
|
7
7
|
## 1. The plugin — MIT (`./LICENSE`)
|
|
8
8
|
|
|
@@ -24,15 +24,36 @@ Covers only the two analysis documents produced *for this repository*:
|
|
|
24
24
|
|
|
25
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).
|
|
26
26
|
|
|
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).
|
|
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). As of 0.8.0 the third-party file described in §3 is **not** in the tarball either (see §3), so everything the package does ship is MIT.
|
|
28
28
|
|
|
29
|
-
## 3.
|
|
29
|
+
## 3. The intro effect asset — third-party code, no longer distributed in the npm package
|
|
30
|
+
|
|
31
|
+
`assets/swarm-rebuild.js` (**as of 0.8.0 no longer packed into the npm tarball** — `package.json#files` no longer lists `assets/`; it lives in the repository only) is **not** this project's work. The position, stated plainly:
|
|
32
|
+
|
|
33
|
+
1. **We hold written permission from Joel Califa for the effect and its choreography** (2026-11-08, on file; his one condition was that he be credited). That credit is in [`README.md`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/README.md#credits--the-intro-effect) and in the settings card.
|
|
34
|
+
2. **The specific script comes from a repository that declares no licence**, and its author has been asked and had not replied as of 2026-11-09 (issue #12). His permission covers his own work; it does not license someone else's code.
|
|
35
|
+
3. **This project claims and grants no rights in that file.** It stopped travelling with the npm package in 0.8.0 for exactly this reason. If either author objects to it remaining in the repository, the file — and the feature that injects it — will be removed on request.
|
|
36
|
+
|
|
37
|
+
| Origin | What | Status |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| [califa/swarm-site](https://github.com/califa/swarm-site) — **Joel Califa** | The effect itself: the page wiped white, the swarm of cursors, and the choreography that draws boxes, text and images back. The published copy declares no licence. | Written permission, 2026-11-08, condition: credit. Done. |
|
|
40
|
+
| [DhvanilPatel/swarm-cursors](https://github.com/DhvanilPatel/swarm-cursors) — **Dhvanil Patel** | `swarm-rebuild/swarm.js` at commit `3d49e0ac` (2026-11-06). That repository declares no licence ("None yet") and its README says the original Swarm was released without one. | Asked (issue #12), no reply as of 2026-11-09. |
|
|
41
|
+
|
|
42
|
+
**Modified, and the modification is disclosed.** `assets/swarm-rebuild.js` is that upstream file **with seven changes**, applied by [`scripts/swarm-patch.mjs`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/scripts/swarm-patch.mjs): (1) the vendor's own bottom control bar is removed; (2) its four DOM fields are pointed at off-screen placeholders so the rest of the code keeps working; (3) its per-frame HUD refresh returns early; (4) the end-of-playback button updates are null-guarded — that last one is a real crash fix: without it the asset throws as soon as the animation completes; (5) scanning waits for the plugin's go-ahead (so the snapshot is taken before the page is scrolled); (6) the white background no longer fades in over 0.5 s, and on early starts the scroll environment is re-detected once after the page loads; (7) its final self-start is deferred until `<html>` exists, so the whole engine can run from `addInitScript` before the page's own scripts. The pristine upstream file ships **in the repository only** (`assets/swarm-rebuild.upstream.js`, SHA-256 `6a30f19d…c7bfe`), as does the patched `assets/swarm-rebuild.js` itself since 0.8.0 — neither reaches npm — so anyone can diff what we changed and regenerate the patched file with `npm run swarm:patch` — or check that it still matches with `npm run swarm:check`.
|
|
43
|
+
|
|
44
|
+
**运行方式也被改了**:本项目不是等网页加载完再运行这份第三方脚本,而是通过 Playwright 的 `addInitScript` 把它放到**网页自己的脚本之前**执行(只在顶层文档、且带一个 30 秒内有效的 `window.name` 标记时启动),启动时机再由 `window.__dshSwarmGo` 放行控制。也就是说,第三方作品的启动流程、注入时机和运行上下文都由本项目重新安排过;原作者的授权仍在问询中,尚未回复。
|
|
45
|
+
|
|
46
|
+
**中文**:`assets/swarm-rebuild.js` **改过**(0.8.0 起不再随 npm 包分发,只留在仓库),改动共七处 —— ①删掉原版自带的底部控制条;②把它的四个界面字段指向离屏占位元素(否则下游代码会抛错);③让每帧的界面刷新提前返回;④播完那一下设置按钮显示状态的代码加空值保护(这一处是**真崩溃修复**:不改的话动画一播完就抛错);⑤扫描改成等插件放行、开场至少停 2.8 秒(拍快照前不滚页面);⑥白底去掉 0.5 秒淡入、第一帧就盖死,提前启动时页面加载完再重认一次滚动环境;⑦结尾自启动改成「等 `<html>` 出现」,这样整台引擎能在网页脚本之前就跑起来。两个文件都只放在仓库里(原始基线 `assets/swarm-rebuild.upstream.js` 与补丁产物,SHA-256 可核对),npm 包内一个都没有,所以任何人都能 diff 出我们改了什么,用 `npm run swarm:patch` 复原生成、用 `npm run swarm:check` 校验。
|
|
47
|
+
|
|
48
|
+
What this means for you: §1 (MIT) does **not** cover `assets/swarm-rebuild.js`. If you re-use that file, ask the two authors yourself — nothing here gives you a licence to it.
|
|
49
|
+
|
|
50
|
+
## 4. Third-party content that is **not** redistributed here
|
|
30
51
|
|
|
31
52
|
- **No OpenAI code is included in this repository.** Nothing from the bundled Codex browser plugin (`extension-host.exe`, the Chrome extension, `browser-client.mjs`, its `node_modules`) is copied, linked, decompiled or redistributed here.
|
|
32
53
|
- `codex-rules.md` is an **independently written Chinese summary of browser-automation behaviour**, produced by studying publicly observable behaviour and a locally installed copy of that plugin's documentation. It contains no verbatim reproduction of that documentation; what it does contain is a *source map* (file names such as `docs/browser-safety.md`) so readers can consult the originals themselves. Facts and ideas are not copyrightable; the expression here is this project's own.
|
|
33
54
|
- **Rights in the original Codex documentation and browser plugin remain with their owner (OpenAI).** If you are that owner and you object to the source map or to any phrasing here, open an issue and it will be removed.
|
|
34
55
|
- `openai/codex` (the CLI) is Apache-2.0. It is mentioned for context only; no code from it is included.
|
|
35
56
|
|
|
36
|
-
##
|
|
57
|
+
## 5. If you need a different split
|
|
37
58
|
|
|
38
59
|
Want the analysis prose under a permissive licence, or the whole thing MIT? Open an issue — that licence is a choice, not a constraint. The plugin's licence is not (see §1).
|
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ dsh plugin --profile <name> add github:miqian-nomad/dsh-browser-playwright-codex
|
|
|
41
41
|
silently clicking a different element.
|
|
42
42
|
- **强力工具默认关闭** — `browser_evaluate` / `browser_cdp` are **not registered at all** unless your
|
|
43
43
|
deployment opts in; this package ships both off.
|
|
44
|
+
- **第一次打开一个网站时,页面会自己「长」出来** — 整页被抹白,然后一群光标把方框、文字、图片一笔一笔画回来,播完自动退回真实页面(可关,设置卡里可调速度/时长)。动效与编排出自 **Joel Califa** 的 [Swarm](https://github.com/califa/swarm-site) — 他 2026-11-08 书面同意本项目使用,条件是**署名**,见 [Credits](#credits--the-intro-effect)。
|
|
45
|
+
The page is wiped white, then a swarm of cursors draws it back — boxes, text and images, one stroke at a time, before handing the real page over. The effect and its choreography are **Joel Califa's** [Swarm](https://github.com/califa/swarm-site), used with his written permission on the one condition that he is credited ([Credits](#credits--the-intro-effect)).
|
|
44
46
|
|
|
45
47
|
**安全一览 / Security at a glance** — dsh.so's scanner flags this repository's **one Critical finding**, and that finding **is** the
|
|
46
48
|
implementation of `browser_evaluate` (its dynamic evaluation), which this package ships switched off.
|
|
@@ -380,9 +382,9 @@ Measured with ``npm run cost`` (per-turn, resident in the system prompt):
|
|
|
380
382
|
|
|
381
383
|
| Surface | Tools | Schema characters | ≈ tokens/turn |
|
|
382
384
|
|---|---|---|---|
|
|
383
|
-
| Schema defaults (`Config({})`, no patch layer) | 16 | 11,
|
|
384
|
-
| Shipped bundle layer (this package's `cordis.patch.yml`: both power gates off) | 16 | 11,
|
|
385
|
-
| Everything registered (`registerDisabledTools: true`, plus `extract`) | 21 | 15,
|
|
385
|
+
| Schema defaults (`Config({})`, no patch layer) | 16 | 11,283 | ≈ 3,134 |
|
|
386
|
+
| Shipped bundle layer (this package's `cordis.patch.yml`: both power gates off) | 16 | 11,283 | ≈ 3,134 |
|
|
387
|
+
| Everything registered (`registerDisabledTools: true`, plus `extract`) | 21 | 15,886 | ≈ 4,413 |
|
|
386
388
|
|
|
387
389
|
This paragraph describes the 0.6.0 change; the table above is today's measurement. Three things changed
|
|
388
390
|
there, and only one of them was about size. (1) The click family was rewritten so each rule is stated once
|
|
@@ -460,9 +462,22 @@ The scenario suites originally surfaced four hazards, now fixed and regression-t
|
|
|
460
462
|
- **Idle disposal defers during operations** — a navigation slower than `idleTimeoutMs` completes normally; disposal only fires when the session is truly idle.
|
|
461
463
|
- **Mid-navigation snapshots settle** — a `browser_snapshot` racing a JS navigation waits for the new document and retries instead of surfacing a raw "Execution context was destroyed" error.
|
|
462
464
|
|
|
465
|
+
## Credits — the intro effect
|
|
466
|
+
|
|
467
|
+
The first-open effect (the page is wiped white, then a swarm of cursors draws the boxes, text and images back) is **not this project's work**, and its code is one third-party file — **repository-only since 0.8.0, no longer in the npm package**:
|
|
468
|
+
|
|
469
|
+
- **The effect and its choreography** — [Swarm](https://github.com/califa/swarm-site) by **Joel Califa** ([swarm.joelcalifa.com](https://swarm.joelcalifa.com)). Used with his written permission (2026-11-08), on his one condition: **credit him** — this section is that credit.
|
|
470
|
+
- **The code** — `assets/swarm-rebuild.js`, from [`DhvanilPatel/swarm-cursors`](https://github.com/DhvanilPatel/swarm-cursors) at commit `3d49e0ac` (2026-11-06, file `swarm-rebuild/swarm.js`), **with seven changes** (all in [`scripts/swarm-patch.mjs`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/scripts/swarm-patch.mjs): the vendor's own control bar removed, its four DOM fields retargeted at off-screen placeholders, the per-frame HUD refresh short-circuited, the end-of-playback updates null-guarded — that one fixes a crash the removal would otherwise cause — scanning gated on the plugin's go-ahead, the 0.5 s white fade-in dropped, and its self-start deferred until `<html>` exists. The pristine baseline stays in the repository as `assets/swarm-rebuild.upstream.js`, SHA-256 `6a30f19d…c7bfe`).
|
|
471
|
+
|
|
472
|
+
The exact licensing split, and what is still outstanding, is recorded in [`NOTICE.md`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/NOTICE.md).
|
|
473
|
+
|
|
474
|
+
**中文**:首次打开网页的「进场特效」——页面被抹白,再由一群光标把方框、文字、图片一笔一笔画回来——**不是本项目自己的作品**。动效与编排出自 **Joel Califa** 的 [Swarm](https://github.com/califa/swarm-site),他于 2026-11-08 书面同意本项目使用,唯一条件是**署名**,此节即为署名;代码取自 [DhvanilPatel/swarm-cursors](https://github.com/DhvanilPatel/swarm-cursors) 的 `assets/swarm-rebuild.js`(改过七处)。**从 0.8.0 起这份资产只在仓库里,不进 npm 包**,也默认关闭。许可切分与仍在等的事见 `NOTICE.md`。
|
|
475
|
+
|
|
463
476
|
## License
|
|
464
477
|
|
|
465
|
-
MIT
|
|
478
|
+
MIT — **except `assets/swarm-rebuild.js`** (repository-only, not in the npm package as of 0.8.0), which is third-party work: see [Credits](#credits--the-intro-effect) above and [`NOTICE.md`](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/NOTICE.md) §3. This project grants no rights in that file.
|
|
479
|
+
|
|
480
|
+
**中文**:插件本体与随包文档是 MIT;**`assets/swarm-rebuild.js` 除外**(它只在仓库里,0.8.0 起不进 npm 包),它是第三方作品,本项目不对它主张也不授予任何权利,详见上文 Credits 与 `NOTICE.md` §3。
|
|
466
481
|
|
|
467
482
|
|
|
468
483
|
<!-- changelog:today -->
|
|
@@ -470,6 +485,14 @@ MIT
|
|
|
470
485
|
|
|
471
486
|
(此列表只记 **minor 里程碑**;每个补丁版本见 [CHANGELOG.md](https://github.com/miqian-nomad/dsh-browser-playwright-codex/blob/main/CHANGELOG.md)。)
|
|
472
487
|
|
|
488
|
+
### 0.8.0(2026-11-10)
|
|
489
|
+
|
|
490
|
+
- **动效资产移出发布包(破坏性)**:`assets/swarm-rebuild.js` 不再随 npm 包分发,`package.json` 的 `files` 移除了 `assets` 相关两条。原因是那套资产是第三方作品、授权状态不允许再分发 —— 与其在授权上纠缠,不如不让它进 tarball。仓库里文件仍在,`npm run swarm:check` 的 SHA 校验照旧有效。
|
|
491
|
+
- **`swarm.enabled` 出厂默认改为 `false`(破坏性)**:包内没有资产的前提下默认开,等于让每个新用户开箱撞一个不存在的文件。所有开关、代码路径一律保留,不动一行行为逻辑。
|
|
492
|
+
- **资产缺失改为静默降级**:`swarmBootScript()` 读资产包了 `try/catch`,读不到就注入一段空注释脚本。它挂在上下文创建的 `addInitScript` 上,裸抛会把"动效没了"放大成"浏览器打不开",这条是本次改动的安全底线。
|
|
493
|
+
- **想要动效的人**:把 `assets/swarm-rebuild.js` 放进包根 `assets/`,再在设置卡里打开 swarm 开关即可恢复,功能与 0.7.x 完全一致。
|
|
494
|
+
- **0.7.9 一并交付**:该版只有本地 tag,npm 上一直停在 0.7.8,从 0.7.8 升级会一次拿到 0.7.9 + 0.8.0 的全部内容,细节见 CHANGELOG。
|
|
495
|
+
|
|
473
496
|
### 0.7.0(2026-10-19)
|
|
474
497
|
|
|
475
498
|
- **两个插件合成一个(破坏性)**:设置卡片并入本包(`dsh.client` + `./client`),`dsh-browser-toggle` 退役;`cordis.patch.yml` 从三行 loader 收成**一行** —— `- id: browser` 加嵌套的 `service` / `playwright` / `tool`。这是硬约束:`dsh-client-modules` 拒绝一个包被多个 loader 行挂载,所以带 client 的包只能有一个入口。
|
package/lib/browser-lifecycle.js
CHANGED
|
@@ -9,6 +9,8 @@ import fs from 'node:fs';
|
|
|
9
9
|
import { AUTO_CHANNELS, HUMANIZED_LAUNCH } from "./config.js";
|
|
10
10
|
import { SNAPSHOT_SCRIPT } from "./injected.js";
|
|
11
11
|
import { BrowserError, launchFailed } from "./errors.js";
|
|
12
|
+
import { forgetOnBrowserClose, showWelcome } from "./swarm-intro.js";
|
|
13
|
+
import { swarmBootScript } from "./swarm-curtain.js";
|
|
12
14
|
/**
|
|
13
15
|
* Ensure the shared persistent context is alive (persistent mode). A
|
|
14
16
|
* profile-backed window is launched once and reused until closed by the
|
|
@@ -50,8 +52,16 @@ async function launchContext(provider) {
|
|
|
50
52
|
const headless = provider.desiredHeadless();
|
|
51
53
|
const headful = !headless;
|
|
52
54
|
const attempts = [];
|
|
55
|
+
// launchPersistentContext waits forever by default (issue #5): a profile another
|
|
56
|
+
// process is holding, a stalled singleton handshake or a wedged GPU init hangs
|
|
57
|
+
// acquire() with no deadline at all. navigationTimeoutMs is the only time scale
|
|
58
|
+
// the user can tune, but it may legitimately be as low as 1s (navigation), which
|
|
59
|
+
// is far too short for a cold Chrome start — so the launch deadline never drops
|
|
60
|
+
// below 30s.
|
|
61
|
+
const launchTimeoutMs = Math.max(provider.config.launch.navigationTimeoutMs, 30_000);
|
|
53
62
|
const baseOptions = {
|
|
54
63
|
headless,
|
|
64
|
+
timeout: launchTimeoutMs,
|
|
55
65
|
// Headful windows must render like a normal Chrome the user opened
|
|
56
66
|
// by hand: the page viewport equals the real window size and the
|
|
57
67
|
// system DPI scaling (e.g. 150%) is honoured. Playwright's default
|
|
@@ -87,7 +97,11 @@ async function launchContext(provider) {
|
|
|
87
97
|
// A stale Chrome singleton lock from a dirty shutdown breaks a relaunch
|
|
88
98
|
// into the same profile; clear the lock files and retry once before
|
|
89
99
|
// surfacing the failure.
|
|
90
|
-
|
|
100
|
+
// Playwright reports the same situation as a timeout ("browserType.launchPersistentContext:
|
|
101
|
+
// Timeout 30000ms exceeded") rather than as a lock message, so match both shapes
|
|
102
|
+
// or the retry never fires against the profile lock it was written for.
|
|
103
|
+
if (error instanceof Error &&
|
|
104
|
+
/user data directory|SingletonLock|ProcessSingleton|Timeout \d+ms exceeded/i.test(error.message)) {
|
|
91
105
|
await provider.clearChromeLocks();
|
|
92
106
|
provider.context = await chromium.launchPersistentContext(provider.profileDir, { ...baseOptions, ...extra });
|
|
93
107
|
provider.launchedHeadless = headless;
|
|
@@ -100,6 +114,8 @@ async function launchContext(provider) {
|
|
|
100
114
|
// External close (user closed the window, crash): forget every
|
|
101
115
|
// session handle bound to this context; the next acquire relaunches.
|
|
102
116
|
void ctx.on('close', () => {
|
|
117
|
+
// 整个插件浏览器窗口关掉:默认忘掉这趟播过哪些站,下次重开全部重播。
|
|
118
|
+
forgetOnBrowserClose(ctx);
|
|
103
119
|
if (provider.context !== ctx)
|
|
104
120
|
return;
|
|
105
121
|
provider.context = undefined;
|
|
@@ -122,11 +138,21 @@ async function launchContext(provider) {
|
|
|
122
138
|
// page cannot leave the list by itself (navigation-guard.ts).
|
|
123
139
|
await provider.installHostGuard(ctx);
|
|
124
140
|
await ctx.addInitScript(SNAPSHOT_SCRIPT);
|
|
141
|
+
// 共享窗口(persistent)这条路也要注册「同一个引擎」的提前启动脚本。
|
|
142
|
+
// 之前只加在 provider.ts 的 newContext 分支,用户实际用的是这里,
|
|
143
|
+
// 第一帧脚本根本没注册 —— 标记留在 window.name 里没被消费(2026-11-09 实测)。
|
|
144
|
+
await ctx.addInitScript({ content: swarmBootScript() });
|
|
125
145
|
// The initial blank page predates addInitScript: reload so the
|
|
126
146
|
// snapshot engine and restored localStorage apply on first use.
|
|
127
147
|
for (const page of ctx.pages()) {
|
|
128
148
|
await page.reload({ waitUntil: 'domcontentloaded' }).catch(() => { });
|
|
129
149
|
}
|
|
150
|
+
// 刚开的窗口停在 about:blank —— 一张纯白占位页。换成启动卡片,并让光标把
|
|
151
|
+
// 它画一遍。只处理还没去任何网址的初始页,不动已恢复的标签。
|
|
152
|
+
const firstPage = ctx.pages()[0];
|
|
153
|
+
if (firstPage !== undefined && firstPage.url() === 'about:blank') {
|
|
154
|
+
await showWelcome(firstPage, provider.config);
|
|
155
|
+
}
|
|
130
156
|
return ctx;
|
|
131
157
|
};
|
|
132
158
|
if (executablePath !== undefined) {
|