dsh-inner-browser-use 0.45.0 → 0.47.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/README.md +17 -2
- package/README.zh.md +16 -2
- package/docs/ARCHITECTURE.md +24 -3
- package/docs/BENCH.md +71 -0
- package/docs/MATURITY.md +52 -0
- package/docs/RESOURCES.md +92 -0
- package/docs/VERIFY.md +43 -5
- package/docs/measure-competitors-2026-11.json +523 -0
- package/package.json +8 -3
- package/rest.js +124 -0
- package/scripts/bench-resource.mjs +173 -0
- package/scripts/check-rest.mjs +91 -0
- package/scripts/soak.mjs +25 -5
- package/sidecar.js +352 -21
- package/tools.js +14 -2
package/README.md
CHANGED
|
@@ -76,8 +76,23 @@ sign-in here is still there the next day.
|
|
|
76
76
|
- **Harness restart.** The same directory is opened again, so cookies, `localStorage` and
|
|
77
77
|
passwords that Chrome saved all survive.
|
|
78
78
|
- **Shutdown.** The browser is asked to close rather than killed, which is when Chrome writes
|
|
79
|
-
the last of its cookies
|
|
80
|
-
|
|
79
|
+
the last of its cookies.
|
|
80
|
+
|
|
81
|
+
## What it costs
|
|
82
|
+
|
|
83
|
+
Measured on the same machine with the same harness as Playwright MCP and Chrome
|
|
84
|
+
DevTools MCP: idle RSS **551 MB** (1414 / 1280), idle CPU **0.03–0.32%**
|
|
85
|
+
(38.1% / 36.3%), six tabs in four renderer processes, and a wake in
|
|
86
|
+
**0.84–0.86 s** instead of a 4.6–5.1 s restart. `npm run bench` reproduces it.
|
|
87
|
+
|
|
88
|
+
## When nobody is looking
|
|
89
|
+
|
|
90
|
+
A browser used every two seconds rests after two minutes; one used every ninety seconds waits
|
|
91
|
+
nine. The window is p95 of the person's own last 64 gaps, × 6 for rest and × 2 for a reversible
|
|
92
|
+
pause, so it is learned rather than configured. Close the column and the rest is one minute
|
|
93
|
+
away; leave it open and the page is frozen instead (DOM, scroll and sign-ins kept, screenshot
|
|
94
|
+
stream stopped). A wake is 0.3–1.1 s and lands on the same address. `npm run rest` checks the
|
|
95
|
+
whole chain headlessly; `docs/RESOURCES.md` has the memory ladder and how the neighbours do it.
|
|
81
96
|
|
|
82
97
|
Screenshots are the only thing written outside the profile:
|
|
83
98
|
`~/.dsh-inner-browser-use/screenshots/`.
|
package/README.zh.md
CHANGED
|
@@ -68,8 +68,22 @@ profile 是独立目录:`~/.dsh/inner-browser-use/chrome/default`。它在产
|
|
|
68
68
|
|
|
69
69
|
- **插件重载。** 重载会接上仍在运行的那个 Chrome,页面也还在原处。
|
|
70
70
|
- **Harness 重启。** 重新打开同一个目录,cookie、`localStorage` 和 Chrome 存下的密码都留着。
|
|
71
|
-
- **退出。** 关浏览器是请它自己关(`Browser.close`),Chrome 在这一步才把 cookie
|
|
72
|
-
|
|
71
|
+
- **退出。** 关浏览器是请它自己关(`Browser.close`),Chrome 在这一步才把 cookie 落盘。
|
|
72
|
+
|
|
73
|
+
## 常驻成本
|
|
74
|
+
|
|
75
|
+
同机、同量法(Playwright MCP 与 Chrome DevTools MCP 用同一套 harness):空闲 RSS
|
|
76
|
+
**551 MB**(对方 1414 / 1280)、空闲 CPU **0.03–0.32%**(38.1% / 36.3%)、6 个页签共用
|
|
77
|
+
4 个渲染进程;休息后唤醒 **0.84–0.86 s** 回到原页,对方要整棵重启 4.6–5.1 s。
|
|
78
|
+
`npm run bench` 可复现,对照明细在 `docs/BENCH.md`。
|
|
79
|
+
|
|
80
|
+
## 没人看的时候
|
|
81
|
+
|
|
82
|
+
每两秒用一次的人,两分钟后就休息;每九十秒用一次的人能等九分钟——窗口取自这个人最近 64 次
|
|
83
|
+
调用间隔的 95 分位,休息乘 6、可逆停驻乘 2,是学出来的不是配出来的。右栏关掉后一分钟就真的
|
|
84
|
+
释放(内存归零,cookies 先存好);右栏还开着只是没人动,就走停驻——DOM、滚动位置、登录态都
|
|
85
|
+
留着,截图流停掉。唤醒 0.3–1.1 秒,回到同一个地址。无头验证跑 `npm run rest`,内存阶梯与
|
|
86
|
+
竞品对照在 `docs/RESOURCES.md`。
|
|
73
87
|
|
|
74
88
|
profile 之外只写一样东西:`~/.dsh-inner-browser-use/screenshots/` 里的截图。
|
|
75
89
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -61,9 +61,30 @@ One page per browser. `apply()` only constructs the `Sidecar`; no process exists
|
|
|
61
61
|
first tool call, and `browser_state` reports a stopped browser without starting one.
|
|
62
62
|
|
|
63
63
|
Shutdown asks Chrome to close (`Browser.close`), waits briefly, then sends `SIGTERM`, which is
|
|
64
|
-
the point where Chrome flushes cookies.
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
the point where Chrome flushes cookies.
|
|
65
|
+
|
|
66
|
+
Lifetime is a ladder rather than one timer, because a browser that is being watched and one
|
|
67
|
+
that was forgotten deserve different answers (`docs/RESOURCES.md` has the measurements and the
|
|
68
|
+
comparison with other projects):
|
|
69
|
+
|
|
70
|
+
| step | trigger | effect |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| awake | a tool call, or a mirror watching | none |
|
|
73
|
+
| quiet | no call for `quietAfter()` and a mirror is still open | screencast stopped, page frozen |
|
|
74
|
+
| rest | no call for `restAfter()` and nothing watching | cookies saved, whole tree closed |
|
|
75
|
+
|
|
76
|
+
`rest.js` derives both windows from the gap between the person's own calls: p95 of the last 64
|
|
77
|
+
gaps × 6 for rest, × 2 for quiet, each clamped (`90 s – 15 min`, `20 s – 2 min`). A fresh
|
|
78
|
+
process has no history and uses the floors. `decide()` is pure, so the clock is testable
|
|
79
|
+
without a browser, and the watched count always beats the deadline: a page someone can see is
|
|
80
|
+
frozen at most, never closed.
|
|
81
|
+
|
|
82
|
+
Closing is single-flight (`shutdown()` returns the promise already in flight) and `launch()`
|
|
83
|
+
waits for the outgoing browser before spawning the next one — without that, a rest that fires
|
|
84
|
+
as a call arrives closes the socket the call is about to use. Every 30 s the tree is weighed
|
|
85
|
+
against `DSH_INNER_BROWSER_MEMORY_MB` (default 2400): over budget, idle tabs are reclaimed by
|
|
86
|
+
LRU and casts back off; over again with nobody watching, the browser rests on the spot. Three
|
|
87
|
+
crashes inside ten minutes stop the restart loop and say why instead of trying forever.
|
|
67
88
|
|
|
68
89
|
## Routes
|
|
69
90
|
|
package/docs/BENCH.md
CHANGED
|
@@ -1030,3 +1030,74 @@ not.
|
|
|
1030
1030
|
| frame size, JPEG q72 | 96–103 KB |
|
|
1031
1031
|
| mirror SSE | 15 fps(66 ms 一轮),4 s 收 60 帧,均值 97.8 KB |
|
|
1032
1032
|
| switch cost | 一次浏览器重启(`browser_open({flash:true})` → `browser_open({flash:false})`) |
|
|
1033
|
+
|
|
1034
|
+
## Phase 25 — 和竞品同机同法的资源对照,0.47.0
|
|
1035
|
+
|
|
1036
|
+
竞品数字不是估的:`@playwright/mcp` 0.0.84 与 `chrome-devtools-mcp` 1.12.0 用同一个
|
|
1037
|
+
`/tmp/compbench/bench.mjs`、同一台机器(macOS 27.2 / arm64 / Node 24.16 / Chrome
|
|
1038
|
+
156.0.8078.80,无头、`--use-mock-keychain`)、同一目标页(example.com),每项重复取
|
|
1039
|
+
min/median/max。原始记录(含每进程 RSS、失败点、方法说明)在
|
|
1040
|
+
`docs/measure-competitors-2026-11.json`,可直接对账。
|
|
1041
|
+
|
|
1042
|
+
本插件一侧由 `scripts/bench-resource.mjs`(`npm run bench`)产出,同一套量法:真实进程树
|
|
1043
|
+
RSS、`ps -o time` 累计 CPU、同一页的读写往返。本地页面 /page(40 段正文+60 个链接)。
|
|
1044
|
+
|
|
1045
|
+
| what | this plugin 0.47.0 | Playwright MCP 0.0.84 | Chrome DevTools MCP 1.12.0 |
|
|
1046
|
+
| --- | --- | --- | --- |
|
|
1047
|
+
| 冷启动到可驱动页 | **1703–1757 ms**(机器忙时 2411) | 2464 / 2755 / 2887 | 1596 / 2185 / 2337 |
|
|
1048
|
+
| 读一页 | **2–3 ms**,2174 字 | 工具往返 24 ms(示例页) | 工具往返 15 ms |
|
|
1049
|
+
| 点/执行一次 | **0–1 ms** | 未单独计时 | 未单独计时 |
|
|
1050
|
+
| 空闲 RSS(整棵进程树) | **551 MB** / 8 进程 | 1414 MB / 9 进程(Chrome 1290) | 1280 MB / 8 进程(Chrome 1109) |
|
|
1051
|
+
| 空闲 CPU(单核占比) | **0.03–0.32%**(30 s 均值) | 38.1%(0–150 s 均值) | 36.3% |
|
|
1052
|
+
| 冻结(有人看、没人用) | CPU 0%,RSS 511–550 MB,唤醒 5–15 ms | 无此状态 | 无此状态 |
|
|
1053
|
+
| 休息(没人看) | **RSS 0**,唤醒 838–860 ms 回原页原地址 | 整棵关掉后冷启动 5133 ms | 无空闲回收 |
|
|
1054
|
+
| 页签切换 | 一个 `browser_tabs` 调用 | 3416 ms(其中 3392 是打开 wikipedia) | 2918 ms(2903 是打开) |
|
|
1055
|
+
|
|
1056
|
+
读法:
|
|
1057
|
+
|
|
1058
|
+
- 冷启动和 Chrome DevTools MCP 同一量级(中位 1703 vs 2185 ms),比 Playwright MCP 快一
|
|
1059
|
+
档;它最低一档 1596 ms 仍比我们的中位低,这一项不能算全面领先。
|
|
1060
|
+
- 空闲 CPU 差约两个数量级,来源是它默认不做任何节流,而我们把后台网络、组件更新、崩溃
|
|
1061
|
+
上报、翻译/媒体路由全关掉,再把页面冻结。
|
|
1062
|
+
- 空闲内存大约是一半:`--renderer-process-limit=4` 让 6 个页签共享 4 个渲染进程。
|
|
1063
|
+
- 休息后的唤醒 838–860 ms,对方恢复要整棵重启 4.6–5.1 s;但 Playwright MCP 确实有
|
|
1064
|
+
`--idle-timeout`(无头默认一小时),只是不区分有没有人在看、也不先冻结再关。这一条是
|
|
1065
|
+
「更细」而不是「只有我们有」。
|
|
1066
|
+
|
|
1067
|
+
### 这一版为数字做的改动
|
|
1068
|
+
|
|
1069
|
+
| change | measured effect |
|
|
1070
|
+
| --- | --- |
|
|
1071
|
+
| `--renderer-process-limit=4` | 空闲 RSS 660 → 551 MB;冷启动 2186 → 1703 ms |
|
|
1072
|
+
| 关掉后台网络 / 组件更新 / 崩溃上报 / 扩展 / 默认应用,功能表减去 5 项 | 空闲 CPU 1.87% → 0.03–0.32% |
|
|
1073
|
+
| `shed()`:冻结前 `gc()` + `Memory.simulatePressureNotification('critical')` | 冻结后 CPU 恒为 0;RSS 在有的轮次 812 → 511 MB,有的轮次不掉——掉多少取决于页面攒了多少缓存,不承诺固定回收量 |
|
|
1074
|
+
| 复用路径统一 `thaw()` | 冻结页被 `tabFor` / `openTab` / 并发合并任一路径拿到时都会先醒,不再当成活页读 |
|
|
1075
|
+
| 与 `--disable-gpu`、`--process-per-site`、`--renderer-process-limit=2/3` 对比 | gpu 关掉内存反升到 737 MB、空闲 CPU 8%;process-per-site 686 MB;limit=2 577 MB;limit=3 548 MB;limit=4 **540–551 MB** 且冷启动最好 |
|
|
1076
|
+
|
|
1077
|
+
### 方法上的边界
|
|
1078
|
+
|
|
1079
|
+
- 竞品的 RSS 是逐进程相加,共享页会重复计一次;我们同一量法,所以对比成立,绝对值偏高。
|
|
1080
|
+
- 竞品的空闲 CPU 高,一部分是 Chrome 自己在后台干活;用 `sleep 8` 走同一量法得 0.00
|
|
1081
|
+
CPU-s、再用 `top -l 2` 交叉验证过,不是量法造成的。
|
|
1082
|
+
- 页签切换那两行主要花在 wikipedia 加载上(同一工具换 example.com 是 65 / 151 ms),
|
|
1083
|
+
不是工具开销,别拿去当工具性能比较。
|
|
1084
|
+
|
|
1085
|
+
## Phase 24 — 长期运行的资源回收,0.46.0
|
|
1086
|
+
|
|
1087
|
+
一台窗口隐藏的 Chrome 空转一晚就是几百 MB。这一版把「多久收」交给用法自己决定:记住这个人
|
|
1088
|
+
最近 64 次调用之间的间隔,取 95 分位,休息乘 6、可逆停驻乘 2,各自夹在上下限里;有人看时
|
|
1089
|
+
永不关闭、只冻结。关闭与启动串行,避免休息和下一次调用撞在一起。
|
|
1090
|
+
|
|
1091
|
+
| what | measured |
|
|
1092
|
+
| --- | --- |
|
|
1093
|
+
| 20 次调用、间隔 20 秒 | p95 20 s → 休息 120 s,停驻 40 s |
|
|
1094
|
+
| 30 次调用、间隔 90 秒 | p95 90 s → 休息 540 s,停驻 180 s |
|
|
1095
|
+
| 没有历史的新进程 | 用下限:休息 90 s,停驻 20 s |
|
|
1096
|
+
| 右栏关掉(`/sidecar/forget`) | 60 s 后休息,内存归零 |
|
|
1097
|
+
| 唤醒 | 326–1010 ms,回到同一页同一地址 |
|
|
1098
|
+
| soak 24 轮+3 次真实休息 | 40.1 s,内存 1427 → 956 MB,页数始终 1 |
|
|
1099
|
+
| soak 16 轮+2 次真实休息 | 26.8 s,内存 1308 → 900 MB |
|
|
1100
|
+
| 休息与调用相撞 | 关闭单飞、启动等待;0.45.0 会在第 8 轮把新连接关掉(已复现、已修) |
|
|
1101
|
+
| 内存预算 | 2400 MB / 30 s 一次称重;超预算先收 LRU 页签、降码率,无人看才休息 |
|
|
1102
|
+
| 崩溃循环 | 10 分钟内 3 次 → 拒绝启动并说明原因 |
|
|
1103
|
+
| suite | 42 pass(sidecar)· rest 9 pass · soak/rest 端到端全过 |
|
package/docs/MATURITY.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 成熟度(0.47.0,2026-11)
|
|
2
|
+
|
|
3
|
+
结论:**可日常用,边界清楚**。能力面已经越过「能开网页」的层次,长跑与资源回收这条链路是本版
|
|
4
|
+
重点并且有实测;短板集中在跨平台验证(只有 macOS 真机跑过)和少数只有真机才能验的交互(Flash、
|
|
5
|
+
镜像上的手感)。
|
|
6
|
+
|
|
7
|
+
## 交付面
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 版本 / 许可 | 0.47.0 / MIT |
|
|
12
|
+
| 仓库 | github.com/regrevia/dsh-inner-browser-use(main 与远端一致) |
|
|
13
|
+
| npm | `dsh-inner-browser-use@0.47.0` |
|
|
14
|
+
| 代码 | 约 11.9k 行(index 956 · sidecar 3683 · tools 4111 · read 2039 · rest 124 · client 954) |
|
|
15
|
+
| 工具 | 39 个 `browser_*`,模型侧零配置 |
|
|
16
|
+
| 文档 | README ×2、ARCHITECTURE、VERIFY(10 节)、BENCH(24 个阶段)、RESOURCES、ARTIFACTS、COMPATIBILITY、FLASH、ROADMAP |
|
|
17
|
+
| 验证脚本 | 27 个 `scripts/`,含 live / live-safety / breadth / soak / rest / doctor |
|
|
18
|
+
|
|
19
|
+
## 证据链
|
|
20
|
+
|
|
21
|
+
| 项 | 结果 |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| 单元测试 | `node --test test/*.test.mjs` → **305 项,305 通过,0 失败** |
|
|
24
|
+
| 资源对照 | 同机同法:冷启动中位 1703 ms、空闲 RSS 551 MB、空闲 CPU 0.03–0.32%、休息后 838–860 ms 唤醒;竞品 Playwright MCP 2755 ms / 1414 MB / 38.1%、Chrome DevTools MCP 2185 ms / 1280 MB / 36.3%(`docs/BENCH.md`、`docs/measure-competitors-2026-11.json`) |
|
|
25
|
+
| 长跑 | soak 24 轮 + 3 次真实休息:40.3 s,内存 **1183 → 912 MB**,页数始终 1,24 次读全部有答案 |
|
|
26
|
+
| 休息/唤醒 | `npm run rest`:休息后内存 0、状态说 `stopped`,再读自愈回原页,唤醒 838–860 ms |
|
|
27
|
+
| 资源策略 | p95 学习窗口(90 s–15 min / 20 s–2 min)、内存预算 2400 MB、页签 LRU 10 min、崩溃循环守卫 |
|
|
28
|
+
| 串行化 | 关闭单飞 + 启动等待;旧版「休息与调用相撞」的 `CDP session is closed` 已复现并修复,soak 不再失败 |
|
|
29
|
+
| 不变量 | 一个 chat 一页;两点钟不会被别人抢;右栏只有本会话能开;镜像外会话一律 403 |
|
|
30
|
+
|
|
31
|
+
## 已经比同类强的地方
|
|
32
|
+
|
|
33
|
+
- **冻结而不是关闭**:有人看但没人用时停驻页面(DOM/滚动/登录态保留),不是一刀切关掉
|
|
34
|
+
- **查看器感知**:没人看才真释放;有人看最多停驻
|
|
35
|
+
- **时间自己学**:不是拍一个固定超时,是这个人用法的 95 分位
|
|
36
|
+
- **内存当策略**:超预算先收 LRU 页签、降码率,再决定休息
|
|
37
|
+
- **唤醒回原页**:0.84–0.86 s(竞品恢复是整棵重启 4.6–5.1 s),接着原地址,不是冷启动白页
|
|
38
|
+
- **常驻成本**:6 个页签共用 4 个渲染进程,空闲 RSS 551 MB、空闲 CPU 0.03–0.32%;按同机同法测,竞品是 1280–1414 MB 和 36–38%
|
|
39
|
+
- 竞品对照与出处见 `docs/RESOURCES.md` 与 `docs/BENCH.md`(Playwright MCP 有 `--idle-timeout` 但只按时辰、Chrome DevTools MCP 没有、browser-use 只有 `keep_alive`、Stagehand 反而关掉 Chrome 节流)
|
|
40
|
+
|
|
41
|
+
## 短板(如实)
|
|
42
|
+
|
|
43
|
+
1. **跨平台**:只在 macOS + Chrome 上跑过;Windows/Linux 的进程树回收、`taskkill` 分支有代码但没实机验证(`COMPATIBILITY.md`)。
|
|
44
|
+
2. **真机交互**:Flash 分支需要本机 Electron 运行时可验证;镜像上的拖拽手感受帧率影响,只有 `adb`/人眼类检查,没有自动化回归。
|
|
45
|
+
3. **多 agent 隔离**:一个进程一台 Chrome、按 chat 分页;没有「每个 agent 独立浏览器」的强隔离档位(竞品同样没有做得更好的)。
|
|
46
|
+
4. **内存预算**:是策略而非硬限制;极端站点仍可能超过预算后再回收,不是提前拒绝。
|
|
47
|
+
5. **市场收录**:插件市场条目来自每日构建的 catalog,发版后自动跟上,无需 PR;本条目不代表示例/评分。
|
|
48
|
+
|
|
49
|
+
## 结论
|
|
50
|
+
|
|
51
|
+
从「能不能用」到「长期开着放不放心」这条线上,本版补齐了最后一段:没人用就真的不占资源,有人用就
|
|
52
|
+
立刻回来。剩下的风险主要是平台覆盖面与真机交互回归,不在核心链路。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Long-term resources: what keeps a browser cheap when nobody is looking
|
|
2
|
+
|
|
3
|
+
A sidecar is only worth keeping if leaving it open overnight costs nothing. This
|
|
4
|
+
document describes the ladder a browser steps down when it goes unused, how the
|
|
5
|
+
timings are chosen, and how the same problem is solved by the four projects a
|
|
6
|
+
person is most likely to compare this plugin to.
|
|
7
|
+
|
|
8
|
+
## The ladder
|
|
9
|
+
|
|
10
|
+
| step | when | what happens | cost while parked |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| `awake` | calls are arriving, or someone is watching the mirror | nothing | one headless Chrome |
|
|
13
|
+
| `quiet` | no call for a while, but a pane still shows the page | screencast stopped, page frozen (`Page.setWebLifecycleState`) | JS/timers paused, DOM and cookies kept, still one process tree |
|
|
14
|
+
| `rest` | no call for a while and no pane watching | cookies saved, `Browser.close`, whole tree gone | zero |
|
|
15
|
+
|
|
16
|
+
The rest step is what the pane closing at `x` earns: the plugin tells the
|
|
17
|
+
sidecar the mirror is gone, and if nobody else is watching it arms a one-minute
|
|
18
|
+
rest. A chat that never closed its pane still gets the quiet step, which is
|
|
19
|
+
reversible and keeps the page exactly where the person left it.
|
|
20
|
+
|
|
21
|
+
## Choosing the timings without guessing
|
|
22
|
+
|
|
23
|
+
Fixed timeouts are wrong in both directions: too short and every pause pays a
|
|
24
|
+
cold start, too long and an abandoned browser outlives the work. The rest and
|
|
25
|
+
quiet windows are therefore derived from what the user actually did in this
|
|
26
|
+
process. `rest.js` keeps the last 64 gaps between calls, takes the 95th
|
|
27
|
+
percentile, and multiplies it:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
rest = clamp(p95(gaps) × 6, 90 s, 15 min)
|
|
31
|
+
quiet = clamp(p95(gaps) × 2, 20 s, 2 min)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A person clicking every two seconds gets a two-minute rest; one coming back
|
|
35
|
+
every ninety seconds gets nine minutes. Gaps longer than the maximum rest are
|
|
36
|
+
treated as a new session, not as evidence. With no history at all the floors
|
|
37
|
+
apply, so a fresh process parks quickly instead of waiting for a pattern that
|
|
38
|
+
does not exist yet.
|
|
39
|
+
|
|
40
|
+
The decision itself is a pure function of four numbers — now, last use, the
|
|
41
|
+
rest deadline, and how many mirrors are watching — which is what makes it
|
|
42
|
+
testable without a browser (`test/rest.test.mjs`). The watched count wins over
|
|
43
|
+
the deadline: a pane on screen always means `quiet` at most, never `rest`.
|
|
44
|
+
|
|
45
|
+
## Memory pressure
|
|
46
|
+
|
|
47
|
+
Timings are the first line; memory is the second. Every 30 s the sidecar weighs
|
|
48
|
+
the whole process tree and compares it with a budget (2400 MB by default,
|
|
49
|
+
`DSH_INNER_BROWSER_MEMORY_MB`). Over budget while a pane is open, it stops
|
|
50
|
+
screencasts and reclaims unused tabs (LRU, pinned pages excluded). Over budget
|
|
51
|
+
again with nobody watching, it puts the browser away on the spot rather than
|
|
52
|
+
waiting for the clock. Being over once is not enough to close a browser the
|
|
53
|
+
person is looking at.
|
|
54
|
+
|
|
55
|
+
## What breaks a long run, and what is done about it
|
|
56
|
+
|
|
57
|
+
| failure | response |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| a rest and the next call arrive together | `shutdown()` is single-flight and `launch()` awaits the outgoing browser, so a fresh socket is never closed by the one leaving |
|
|
60
|
+
| the browser dies three times in ten minutes | the crash-loop guard refuses to start it again and says why, instead of spinning |
|
|
61
|
+
| a tab is left in a chat that stopped using it | 10-minute LRU, skipped while it streams or holds a popup |
|
|
62
|
+
| the last call was minutes ago but the pane is open | freeze, not close: scroll position, DOM and sign-ins are still there on wake |
|
|
63
|
+
|
|
64
|
+
## Neighbours
|
|
65
|
+
|
|
66
|
+
Compared with the projects this one is usually put next to (`--idle-timeout`,
|
|
67
|
+
`keep_alive`, and their documentation). The numbers come from the same harness
|
|
68
|
+
on the same machine; the raw record is
|
|
69
|
+
`docs/measure-competitors-2026-11.json` and the method is in `docs/BENCH.md`.
|
|
70
|
+
|
|
71
|
+
| project | idle handling | idle CPU / RSS | what a wake costs | notes |
|
|
72
|
+
| --- | --- | --- | --- | --- |
|
|
73
|
+
| Playwright MCP 0.0.84 | `--idle-timeout`, default one hour for headless, whole browser closed | 38.1% / 1414 MB | cold start, 5.1 s | does release, but never looks at whether anyone is watching and never parks a page first; each client gets its own Chrome |
|
|
74
|
+
| Chrome DevTools MCP 1.12.0 | none — the browser lives until the process exits | 36.3% / 1280 MB | n/a | one dispose chain, no idle clock, screencast never pauses for a detached viewer |
|
|
75
|
+
| browser-use | `keep_alive` flag, no timeout value | not measured | n/a | teardown skipped while `keep_alive`; no idle timer, no memory policy |
|
|
76
|
+
| Stagehand | none | not measured | n/a | deliberately disables Chrome's background throttling, then tears down and removes the profile |
|
|
77
|
+
| this plugin 0.47.0 | viewer-aware ladder, timings learned from use, memory pressure, single-flight close | 0.03–0.32% / 551 MB | 0.84–0.86 s, same page and address | freeze keeps the DOM; rest keeps the cookies; nothing cold-starts unless the process is new |
|
|
78
|
+
|
|
79
|
+
Playwright MCP's idle timeout was measured working (`--idle-timeout=6000` left no
|
|
80
|
+
Chrome after 14 s idle while the MCP process stayed up), so "we are the only one
|
|
81
|
+
that releases" would be false. What differs is the shape of the ladder: it is
|
|
82
|
+
one timer and one action, while this plugin never closes a page someone is
|
|
83
|
+
watching, freezes before it closes, and derives both windows from how often the
|
|
84
|
+
person actually calls.
|
|
85
|
+
|
|
86
|
+
The gaps that remain in the field, and that this document treats as the design
|
|
87
|
+
brief: nobody freezes a page instead of closing it; nobody pauses a screencast
|
|
88
|
+
because the viewer looked away; cold restart is the documented recovery
|
|
89
|
+
everywhere; tab caps and LRU are left to the caller; memory is a diagnostic
|
|
90
|
+
rather than a policy; multi-agent isolation is all-or-nothing. Those are the
|
|
91
|
+
five places where this plugin is deliberately ahead rather than equal — see
|
|
92
|
+
`docs/ARCHITECTURE.md` and `docs/BENCH.md` for the measured side of it.
|
package/docs/VERIFY.md
CHANGED
|
@@ -132,11 +132,31 @@ than the page's: a cut must not shrink the site for the next visit.
|
|
|
132
132
|
npm run soak
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
Twenty-four cycles of open, read, snapshot, re-read through one headless browser
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
Twenty-four cycles of open, read, snapshot, re-read through one headless browser,
|
|
136
|
+
and every eighth cycle the browser is really put away and woken again.
|
|
137
|
+
Expected: `soak: ok · 24 cycles` with `pages 1`, memory lower at the end than at
|
|
138
|
+
the start, and a summary naming at least one rest (`3 rests` here, 1427 → 956 MB
|
|
139
|
+
in 40.1 s). Memory climbing with the cycle count, a second page appearing, or a
|
|
140
|
+
rest that leaves memory held means something is kept per read rather than per
|
|
141
|
+
page, or the rest does not close the whole tree. The run is bounded on purpose —
|
|
142
|
+
under a minute, no internet.
|
|
143
|
+
|
|
144
|
+
## 10. It comes back where it was
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm run rest
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A headless sidecar is told to rest 1.2 s after the last call. Expected chain:
|
|
151
|
+
`browser_state` says `rests in`, then `stopped` with `memory 0 MB` once the rest
|
|
152
|
+
lands; touching the browser again wakes it on its own, `pageFor(session).url` is
|
|
153
|
+
still the page that was open, the text is either the same paragraph or
|
|
154
|
+
`nothing changed since the last read`, and the wake takes under 4 s. `rest and
|
|
155
|
+
wake hold.` is the bar.
|
|
156
|
+
|
|
157
|
+
A rest that never happens while nobody is watching means the watched count is
|
|
158
|
+
not reaching the clock; a wake that lands on a blank tab means the page-per-chat
|
|
159
|
+
map was dropped by the close instead of being restored by the launch.
|
|
140
160
|
|
|
141
161
|
## Live
|
|
142
162
|
|
|
@@ -214,3 +234,21 @@ navigation — as a session that is not the owner. Both must answer 403, and the
|
|
|
214
234
|
owner's stream must answer 200. Three `true` lines is the bar; `open · owner
|
|
215
235
|
none` as the first line only means nothing is on screen, and the two refusals
|
|
216
236
|
still have to hold.
|
|
237
|
+
|
|
238
|
+
## 11. Compare against the neighbours
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm run bench # writes JSON to stdout, exit 1 if a check fails
|
|
242
|
+
npm run bench -- --json /tmp/bench.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Cold start, one read, one action, 30 s idle, freeze, rest, wake — each with the
|
|
246
|
+
real process tree's RSS and CPU seconds. Add flags for an A/B without editing
|
|
247
|
+
the source:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
DSH_INNER_BROWSER_CHROME_FLAGS="--renderer-process-limit=2" npm run bench
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The competitor side of the comparison and its raw record live in
|
|
254
|
+
`docs/BENCH.md` and `docs/measure-competitors-2026-11.json`.
|