dsh-inner-browser-use 0.34.28 → 0.34.96

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.
@@ -4,7 +4,7 @@ Two halves in two processes, and a Chrome that neither of them is.
4
4
 
5
5
  ```
6
6
  server side (index.js, in the DSH process)
7
- sidecar_* tools ──> Sidecar ── CDP over a socket ──> headless Chrome
7
+ browser_* tools ──> Sidecar ── CDP over a socket ──> headless Chrome
8
8
  │
9
9
  HTTP + SSE routes <────┘
10
10
  │
@@ -41,7 +41,7 @@ Other launch flags:
41
41
  | `--use-mock-keychain`, `--password-store=basic` | No keychain prompt on macOS. |
42
42
  | `--remote-allow-origins=*` | The CDP client connects over a plain WebSocket. |
43
43
  | `--disable-blink-features=AutomationControlled` | Sites that sniff for automation get the ordinary path. |
44
- | `--window-size=1440,900`, `--lang=zh-CN` | The viewport the pane mirrors, and a Chinese page locale. |
44
+ | `--window-size=1440,900`, `--lang=zh-CN` | The window the browser starts in, and a Chinese page locale; the layout viewport is set to the column instead. |
45
45
 
46
46
  The binary comes from a fixed list: Google Chrome, Chromium or Canary under `/Applications`,
47
47
  then `/usr/bin/google-chrome` and `/usr/bin/chromium`. Nothing else is probed and there is no
@@ -58,7 +58,7 @@ The browser outlives the plugin:
58
58
  `/json/new?about:blank` as a fallback) and a client connects to it.
59
59
 
60
60
  One page per browser. `apply()` only constructs the `Sidecar`; no process exists until the
61
- first tool call, and `sidecar_state` reports a stopped browser without starting one.
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
64
  the point where Chrome flushes cookies. An idle timer closes the browser after 15 minutes; a
@@ -85,13 +85,16 @@ the CDP socket stays in the server half.
85
85
  ## Mirror
86
86
 
87
87
  The server half starts `Page.startScreencast` with JPEG quality 72, `maxWidth` and `maxHeight`
88
- at 1440×900, `optimizeForSpeed`, `everyNthFrame: 1`, and acknowledges every frame it receives
88
+ at the pane's size, `optimizeForSpeed`, `everyNthFrame: 1`, and acknowledges every frame it receives
89
89
  so Chrome keeps sending. Frames are never queued: with two frames in flight, further frames
90
90
  are dropped and the next one that fits takes their place. A slow reader loses frames, never
91
91
  freshness.
92
92
 
93
- The viewport is pinned with `Emulation.setDeviceMetricsOverride` to the same 1440×900, so a
94
- frame is the whole page as laid out, not a crop.
93
+ The layout viewport follows the column: the pane measures itself, posts the size to
94
+ `/sidecar/viewport` a beat after the resize settles, and the server pins
95
+ `Emulation.setDeviceMetricsOverride` to that size and restarts the screencast at it. So a frame
96
+ is the whole page as laid out at the shape it is shown in — no letterbox bands, no crop. Sizes
97
+ below 320 px or above 1920×1600 are ignored rather than applied as a sliver.
95
98
 
96
99
  If `Browser.getVersion` reports `HeadlessChrome`, the user agent is overridden with an ordinary
97
100
  Chrome string, `acceptLanguage: 'zh-CN,zh;q=0.9,en;q=0.8'` and `platform: 'MacIntel'`, so a site
@@ -133,7 +136,7 @@ browser without pulling the column away from the person reading it.
133
136
 
134
137
  `sidecarTools()` in `tools.js` builds the 28 definitions and `index.js` registers them on the
135
138
  plugin's tool scope. All of them return one text block. A page read numbers the controls and
136
- keeps those numbers across later reads, so `sidecar_click` by ref means the element the model
139
+ keeps those numbers across later reads, so `browser_click` by ref means the element the model
137
140
  last saw; navigation drops them and the model gets `REF_GONE` rather than a wrong click.
138
141
 
139
142
  Before a ref click or a ref focus, one page-side probe asks what is on top of that control.
@@ -145,7 +148,7 @@ reported, never accepted on the person's behalf. The whole exchange is one `Runt
145
148
  and a possible `mousePressed`/`mouseReleased` pair on an already-open CDP connection, so an
146
149
  uncovered click pays nothing.
147
150
 
148
- `sidecar_do` runs the same probe before a step that names a control — by `ref`, by
151
+ `browser_do` runs the same probe before a step that names a control — by `ref`, by
149
152
  text, or by field label — so a banner the first step clears is gone for the rest of
150
153
  the batch, and the step's own line says it in one clause (`after Accept on a banner,`).
151
154
  A batch therefore pays for one overlay, not one per step.
@@ -154,7 +157,7 @@ An action answers with one line — address, title, focused field — and reads
154
157
  `read: true` is passed. That is what keeps a long batch cheap (see `BENCH.md`).
155
158
 
156
159
  Calls that touch the browser are marked not concurrency-safe; reading state, console and failed
157
- requests is. `sidecar_open`, `sidecar_view`, `sidecar_wait` and `sidecar_shot` carry longer
160
+ requests is. `browser_open`, `browser_view`, `browser_wait` and `browser_shot` carry longer
158
161
  timeouts than the rest.
159
162
 
160
163
  Sign-in state lives in the profile, and session cookies — the kind a sign-in usually sets,
@@ -169,7 +172,7 @@ without asking first when someone wants something done on a site.
169
172
  `Network.enable` is already on for the mirror. A request is kept when it is
170
173
  sent and settled when its response arrives, so the list holds method, status,
171
174
  type and address of what the page actually got — 120 rows, oldest dropped.
172
- `sidecar_network` reads that list without touching the page, which is why it
175
+ `browser_network` reads that list without touching the page, which is why it
173
176
  can answer while a dialog is open.
174
177
 
175
178
  ## Downloads
@@ -177,7 +180,7 @@ can answer while a dialog is open.
177
180
  Chrome is pointed at `<profile>/downloads` when the page starts, so a click
178
181
  that saves a file writes it there instead of nowhere. The folder belongs to
179
182
  the profile: it is the same one on the next launch, and nothing outside the
180
- profile is touched. `sidecar_downloads` lists what is on disk, newest first;
183
+ profile is touched. `browser_downloads` lists what is on disk, newest first;
181
184
  a `.crdownload` still in flight is not listed.
182
185
 
183
186
  ## Frames and popups
@@ -215,4 +218,4 @@ and there is still one page to drive.
215
218
  | `locale/*.json` | Page: pane labels. |
216
219
 
217
220
  Data on disk is confined to `~/.dsh/inner-browser-use/`: `chrome/<profile>` for the browser,
218
- `screenshots/` for `sidecar_shot`.
221
+ `screenshots/` for `browser_shot`.
package/docs/ARTIFACTS.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  - **项目**:会话工作目录的文件夹名,加路径哈希前 6 位,例如 `dsh-inner-browser-use-a1b2c3`。同名文件夹、不同路径不会混。
10
10
  - **会话**:会话 id 去掉 `session-` 后的前 8 位,例如 `72557d6e`。同一项目里两个会话各存各的。
11
- - **种类**:`shots/` 是 `sidecar_shot` 的截图,`pictures/` 是 `sidecar_grab` 抓的图,`pdf/` 是 `sidecar_pdf` 打印的文档。
11
+ - **种类**:`shots/` 是 `browser_shot` 的截图,`pictures/` 是 `browser_grab` 抓的图,`pdf/` 是 `browser_pdf` 打印的文档。
12
12
 
13
13
  文件名默认带时间戳(`shot-20261028-124500.png`),给了 `name` 就用它;名字里的分隔符、连续点、开头点都会被去掉,跳不出这个目录。
14
14
 
package/docs/BENCH.md CHANGED
@@ -1,8 +1,172 @@
1
+ ## Flash 镜像按栏改窗口(0.34.48,Electron Flash 运行时,右栏 747×1196)
2
+
3
+ Flash 路径原来采集一个固定的 1440×900 窗口,竖高右栏里上下留黑边。现在 `setViewport` 直接改窗口大小,采集源与栏同比例。
4
+
5
+ | 状态 | 窗口 | 画面占栏 | 单帧 |
6
+ |---|---|---|---|
7
+ | 0.34.47 | 1440×900 | 约 39% | 126 KB |
8
+ | 0.34.48 | 747×1196 | 100% | 111 KB |
9
+
10
+ 轮询帧率 9 fps 不变(Flash 桥上限),改的是画面比例与单帧体积。
11
+
12
+ ## 动画页下行节流(0.34.47,真实 Chrome headless,720×880,canvas 60 fps 动画)
13
+
14
+ | 状态 | 帧率 | 单帧 | 下行带宽 |
15
+ | --- | --- | --- | --- |
16
+ | 0.34.46 全推 | 60 fps | 11 KB | 654 KB/s |
17
+ | 0.34.47 空闲节流 | 30 fps | 11 KB | 326 KB/s |
18
+ | 0.34.47 指针活动(1.5 s 内) | 58 fps | 11 KB | 631 KB/s |
19
+
20
+ 节流只作用于下行:`latest`、`seq`、`still()` 与输入路径不变;`browser_shot` 仍走独立整帧截图。
21
+
22
+ ## 坏页读取成本(0.34.46,构造页:`querySelectorAll` 抛错被读 150 字符)
23
+
24
+ | 读取结果 | 字符 | 约 token |
25
+ | --- | --- | --- |
26
+ | 0.34.46 一行故障说明 | 150 | 45 |
27
+ | 0.34.45 同一页读成 0 控件 0 字 | 20 | 6(但模型会重试或猜内容,整轮更贵) |
28
+
29
+ ## 点击与多步延迟(0.34.45,headless Chrome,本地页)
30
+
31
+ | 场景 | 0.34.45 | 0.34.44 |
32
+ | --- | --- | --- |
33
+ | `browser_do` 单步 | 73 / 83 ms | 98 / 112 ms |
34
+ | 遮挡点击(自动清掉横幅) | 72 / 77 ms | 97 / 112 ms |
35
+ | `browser_do` 20 步 | 2035 ms(102 ms/步) | 2151 ms |
36
+ | 崩溃恢复(换新标签页) | 1390 ms | ~1.5 s |
37
+
38
+ 做法:`Page.frameStartedLoading` / `frameStoppedLoading` 打时间戳,点击后若页面已 `complete` 且没有新加载开始,`settle()` 一次求值就返回;预算不变(点击 ≤ 220、遮挡点击 ≤ 240、单步 ≤ 240、每步 ≤ 280、崩溃恢复 ≤ 2600 ms)。
39
+
40
+ ## 真实站点读页成本(0.34.44,headless Chrome,720×880 栏宽,`scripts/readcost.mjs`)
41
+
42
+ | 页面 | 读法 | 字符 | 估算 token | 控件 |
43
+ | --- | --- | --- | --- | --- |
44
+ | GitHub 仓库首页(rust-lang/rust) | text | 2562 | 754 | 90 |
45
+ | 同上 | links | 3763 | 1107 | 90 |
46
+ | GitHub Topics · computer-use | links | 2590 | 762 | 90 |
47
+ | 同上 | text | 1939 | 570 | 90 |
48
+ | GitHub 登录表单 | forms | 218 | 64 | 4 |
49
+ | 同上 | text | 533 | 157 | 13 |
50
+
51
+ - 读页本身 1–21 ms;`max_chars` 只对长页生效(links 4600→2200:3763→2800 字符,1107→824 token),短页两种预算结果一致。
52
+ - 空读取重试:内容在 700 ms 后才写入的页面,一次 `browser_view` 用 614 ms 拿到内容(此前直接读到空页)。正常页面不进入该分支,延迟与 token 不变。
53
+
54
+ ## 故障归因的 token 成本(0.34.43)
55
+
56
+ | 场景 | 读取结果多出的内容 | 代价 |
57
+ | --- | --- | --- |
58
+ | 页面正常 | 0 字符 | 0 |
59
+ | 1 个 404 请求 + 1 个脚本错误 | 1 行,约 130 字符 | ≈33 token,替代 2 次排查调用(通常 ≥600 字符) |
60
+ | 同一页面再读 | 0 字符(已报过) | 0 |
61
+
62
+ ## 点击与多步延迟(0.34.42,headless Chrome,本地页)
63
+
64
+ `node scripts/bench.mjs` 复现;末行 `budget` 是纳入退出码的预算断言。
65
+
66
+ | 场景 | 0.34.42 | 0.34.40 |
67
+ | --- | --- | --- |
68
+ | `browser_click` 普通页 | 97 ms | 439 ms |
69
+ | `browser_click` 横幅遮挡、自动清掉 | 107 ms | 593 ms |
70
+ | 被弹窗拦住的点击(拒绝) | 2 ms | — |
71
+ | `browser_do` 单步 | 95 ms | 440 ms |
72
+ | `browser_do` 20 步 | 2151 ms(108 ms/步) | 9067 ms |
73
+ | 崩溃恢复(换新标签页) | ~1.5 s | — |
74
+
75
+ 预算:点击 ≤ 220 ms、遮挡点击 ≤ 240 ms、单步 ≤ 240 ms、20 步每步 ≤ 280 ms、崩溃恢复 ≤ 2600 ms。
76
+
77
+ ## 点击与多步延迟(0.34.41,headless Chrome,本地页)
78
+
79
+ | 场景 | 耗时 |
80
+ | --- | --- |
81
+ | `browser_click` 普通页 | 141 ms |
82
+ | `browser_click` 横幅遮挡、自动清掉 | 235 ms |
83
+ | 被弹窗拦住的点击(拒绝) | 2 ms |
84
+ | `browser_do` 单步 | 131 ms |
85
+ | `browser_do` 20 步 | 3073 ms(154 ms/步) |
86
+ | 跟随点击开出的弹窗 | 22 ms |
87
+
88
+ `node scripts/bench.mjs` 复现;对照 0.34.40 的 439 / 593 / 440 / 9067 ms。
89
+
90
+ ## 广度回归(0.34.40,headless Chrome,本地构造页)
91
+
92
+ `node scripts/breadth.mjs` — 66 项操作全通过:shadow DOM 读/点、表单输入/退格/清空、按字段名输入、下拉、勾选、提交、只读变化、画布编号与点选、截图尺寸、遮挡弹窗拒绝、失效 ref 跟随、断线恢复。
93
+
94
+ ## 真实站点读页成本(0.34.39,headless Chrome,720×880 栏宽)
95
+
96
+ | 页面 | 字符 | 估算 token | 控件 |
97
+ | --- | --- | --- | --- |
98
+ | example.com | 976 | 305 | 1 |
99
+ | GitHub Topics · computer-use | 约 1.5k | 约 470 | 90 |
100
+
101
+ 读页脚本此前因正则转义缺失整段无法解析,同一测例读出 0 控件 0 字符;修好后为上面数字。
102
+
103
+ ## 读页 token 成本(0.34.38,构造页:300 行链接,字符预算 24000 以排除截断)
104
+
105
+ | 读法 | 字符 | 估算 token |
106
+ | --- | --- | --- |
107
+ | 列出全部控件(旧) | 9153 | 2860 |
108
+ | 控件折叠到 140 行(新) | 4277 | 1337 |
109
+
110
+ 正文不再被编号挤到预算之外;被折叠的控件滚动后重新出现。
111
+
112
+ ## 长文本输入延迟(0.34.37,真实 Chrome headless)
113
+
114
+ | 目标 | 方式 | 314 字符耗时 | 文本一致 |
115
+ | --- | --- | --- | --- |
116
+ | `<textarea>` | 逐键 `dispatchKeyEvent` | 1686 ms | 是 |
117
+ | `<textarea>` | 一次 `Input.insertText` | 2 ms | 是 |
118
+ | `contenteditable` | 一次 `Input.insertText` | 13 ms | 是 |
119
+
120
+ ## 右栏尺寸下的镜像带宽(0.34.36,真实 Chrome headless,720×880)
121
+
122
+ | 页面 | 3–4 s 内帧数 | 帧率 | 平均单帧 | 带宽 |
123
+ | --- | --- | --- | --- | --- |
124
+ | 静态真实站点(example.com) | 3 | <1 fps | 47 KB | 47 KB/s |
125
+ | 注入 `requestAnimationFrame` 动画 | 181 / 3 s | ~60 fps | 15 KB | 875 KB/s |
126
+
127
+ 结论:静止页面几乎不花带宽(Chrome 的 screencast 只在合成器变化时发帧);满帧动画按列宽排版后单帧只有 15 KB,60 fps 总计约 875 KB/s,走本机 SSE 无压力,不设帧率上限以免牺牲延迟。「同一张图不重复发」这道闸真正省掉的是 Flash 轮询分支与 `still()` 连发(单测:3 次连发 → 1 张)。
128
+
129
+ ## 镜像按右栏尺寸排版(0.34.35,真实 Chrome,`--headless=new`)
130
+
131
+ 页面不再按 1440×900 排版,而是按右栏实测的尺寸排版;下表的 `inner` 是页面自己报告的 `innerWidth×innerHeight`,证明不是缩放图片而是重新排版。
132
+
133
+ | 请求尺寸 | 页面 inner | `Emulation.setDeviceMetricsOverride` + 重开 screencast | 一张整帧 `captureScreenshot` | 整帧 JPEG(纯色测试页) |
134
+ | --- | --- | --- | --- | --- |
135
+ | 720×880 | 720×880 | 41 ms | 39 ms | 11.7 KB |
136
+ | 520×1000 | 520×1000 | 34 ms | 33 ms | 11.3 KB |
137
+ | 1440×900 | 1440×900 | 49 ms | 47 ms | 17.4 KB |
138
+
139
+ 冷启动到可排版 827 ms。点击链路在同一尺寸下实测:两次 `Input.dispatchMouseEvent`(按下+抬起)13 ms,页面 `mousedown` 处理器已计数,订阅后下一帧 8 ms 内到达。
140
+
141
+ ## Phase 46 — 开玩前的体检,0.34.30
142
+
143
+ `npm run ready [url]` 跑新工具 `browser_prepare`,整条链路一次调用,自带 Chrome、跑完关掉:
144
+
145
+ | 页面 | 调用耗时 | 找到的输入面 | 帧率 | 按键 |
146
+ | --- | --- | --- | --- | --- |
147
+ | 4399《怪物小汽车》 | 3297 ms | frame 748×336(在屏幕外,先滚进来再点) | 页面 58 fps / 框架 59 fps | 没进到框架,工具直说「先点进框架一次」 |
148
+ | 奥拉星 `http://aoqi.100bt.com/` | 573 ms | — | — | —,地址已连不上,返回 Chrome 错误页说明 |
149
+
150
+ `browser_prepare` 的判断顺序:连不上就直说 → 读页面 → 挑画布/框架(优先屏幕内的,否则滚进视野)→ 有 Flash 影片就说这个浏览器放不了 → 数帧率(页面与最大的框架各数一遍)→ 派一个真键看它是否真的到达框架 → 一行结论。
151
+
152
+ ## Phase 45 — 两种操作方式的实际差别,0.34.29
153
+
154
+ 同一张本地页(按钮 + 输入框,页面在捕获阶段记下每个事件的类型和 `isTrusted`),`npm run modes`:
155
+
156
+ | 路径 | 受信任事件 | pointerdown | click | keydown | input | 写进值 |
157
+ | --- | --- | --- | --- | --- | --- | --- |
158
+ | human 点按钮 | 3 | 1 | 1 | – | – | – |
159
+ | dom 点按钮 | 0 | 1 | 1 | – | – | – |
160
+ | human 输入 abc | 11 | – | – | 3 | 3 | abc |
161
+ | dom 输入 abc | 0 | – | – | 0 | 1 | abc |
162
+
163
+ `trustedEvents` 是这张表里唯一真正的分界:真人路径全为 true,`dom` 路径全为 false。要过反自动化、要看指针轨迹、要按键有 `code` 的游戏,只有 human 能走;被浮层盖住、被脚本接管了按键的登录框,`dom` 一次就过。
164
+
1
165
  ## Phase 44 — 真站页游场地,0.34.28
2
166
 
3
167
  `node scripts/check-game.mjs <url>`,真 Chrome、自己的 profile、跑完自己关掉。两个真站:
4
168
 
5
- | 站点 | 空闲时页面刷新 | 全屏截图(`sidecar_shot` 同类)| 按住 `d` 200 ms 到达页面 |
169
+ | 站点 | 空闲时页面刷新 | 全屏截图(`browser_shot` 同类)| 按住 `d` 200 ms 到达页面 |
6
170
  | --- | --- | --- | --- |
7
171
  | 奥拉星 `http://aoqi.100bt.com/` | **27 fps** | 中位数 **68 ms** | 是 |
8
172
  | 4399 `https://www.4399.com/` | **59.8 fps** | 中位数 **148 ms** | 是 |
@@ -17,10 +181,10 @@
17
181
 
18
182
  | 项 | 数字 |
19
183
  | --- | --- |
20
- | `sidecar_key` 带 `hold_ms: 300` 整次调用 | **305 ms**(含 300 ms 按住,收尾 5 ms) |
184
+ | `browser_key` 带 `hold_ms: 300` 整次调用 | **305 ms**(含 300 ms 按住,收尾 5 ms) |
21
185
  | 页面测到的按住时长 | **301 ms** |
22
186
  | `ArrowLeft+ArrowUp` | 两键都收到 `keydown`,都收到 `keyup`,顺序为倒序松开 |
23
- | `sidecar_click` 带 `hold_ms: 200` 页面测到 | **211 ms** |
187
+ | `browser_click` 带 `hold_ms: 200` 页面测到 | **211 ms** |
24
188
 
25
189
  对照 0.34.25 之前:同样的按住被 `settle` 拖到 727 ms,等于 0.7 s 才走一步——页游里这就是掉帧。
26
190
 
@@ -50,14 +214,14 @@
50
214
  | 镜像延迟 | 中位 8 ms / 最大 8 ms |
51
215
  | 遮挡检测 | 中位 3 ms / 最大 15 ms |
52
216
  | 点一下(普通页 / 有遮罩,含读回) | 439 / 590 ms,遮罩那次 `cleared: true` |
53
- | 一步 `sidecar_do` | 440 / 590 ms |
217
+ | 一步 `browser_do` | 440 / 590 ms |
54
218
  | 截图 | 73 ms / 115 KB |
55
219
  | 读 dense 页 text 档 | 6 ms / 1484 字 / ≈436 token |
56
220
  | 读 dense 页 forms 档 | 1 ms / 432 字 / ≈127 token |
57
221
  | 再读同一页 | 1–2 ms,给出「没有变化」一行 |
58
222
  | `changed_only` | 58 字 / ≈17 token(整页 1505 字) |
59
223
 
60
- 这轮 bench 的作用不是数字,而是**它第一次跑到 `sidecar_view` 就报 `ReferenceError` 退出**:那个工具在真实会话里同样会失败,而单测只测了它的纯函数部分。修完之后 bench 一次性跑完,数字在上面。
224
+ 这轮 bench 的作用不是数字,而是**它第一次跑到 `browser_view` 就报 `ReferenceError` 退出**:那个工具在真实会话里同样会失败,而单测只测了它的纯函数部分。修完之后 bench 一次性跑完,数字在上面。
61
225
 
62
226
  ## Phase 40 — the poll that names its chat, 0.34.19
63
227
 
@@ -106,9 +270,9 @@ video, a cross-origin frame — nothing in the text said "there is something her
106
270
  you cannot read". A model then planned as if the page were fully described.
107
271
 
108
272
  - `readPage` counts the unreadable visible things on the page and in frames, and `renderPage` prints exactly one line when the count is non-zero; zero adds no line (measured token cost of the feature is 0 on pages that have nothing to hide).
109
- - `chrome-error://` is no longer rendered as an empty page: it says the page did not load and names `sidecar_nav`.
273
+ - `chrome-error://` is no longer rendered as an empty page: it says the page did not load and names `browser_nav`.
110
274
  - `navigate` retries that error page once (300 ms + 8 s cap). Cold launch on a fresh profile: first navigation recovered on the retry; an unreachable host still returns `bad` in well under the cap.
111
- - `sidecar_shot` without an image channel appends what the result is missing and which tool fills it; file path alone is no longer the whole answer.
275
+ - `browser_shot` without an image channel appends what the result is missing and which tool fills it; file path alone is no longer the whole answer.
112
276
  - Suite: 101 tests, 0 failures.
113
277
 
114
278
  ## Phase 36 — a reload must not cost the browser, 0.34.14
@@ -121,7 +285,7 @@ you cannot read". A model then planned as if the page were fully described.
121
285
  ## Phase 35 — a review round that can be checked off, 0.34.13## Phase 35 — a review round that can be checked off, 0.34.13
122
286
 
123
287
  - `markRound()` 追加 verdict 行,`lastRound()` 折回 `marks`;`brief({already})` 只保留 open 条目,实测提问里出现「Already handled from your previous review」,已处理点不再重复。
124
- - 工具面新增 `sidecar_review_verdict`(`mark: [{point,status,evidence}]`,状态枚举 fixed/verified/invalid/open);越界编号丢弃、未知状态记为 open。
288
+ - 工具面新增 `browser_review_verdict`(`mark: [{point,status,evidence}]`,状态枚举 fixed/verified/invalid/open);越界编号丢弃、未知状态记为 open。
125
289
  - 测试 92 → 96 条全绿。
126
290
 
127
291
  ## Phase 34 — one gate in front of every write, 0.34.12## Phase 34 — one gate in front of every write, 0.34.12
@@ -217,7 +381,7 @@ in one place.
217
381
 
218
382
  ## Phase 26 — a screenshot the model can see, 0.34.3
219
383
 
220
- Observation was structured text only: `sidecar_shot` wrote a PNG and returned
384
+ Observation was structured text only: `browser_shot` wrote a PNG and returned
221
385
  a path, which is a file name the model cannot look at. Codex and Claude Code
222
386
  both hand screenshots back as image content, and that is the difference
223
387
  between planning from the picture and guessing.
@@ -333,7 +497,7 @@ answer), and a read now also asks for the frame tree.
333
497
  | key round trip | 13 ms | 2 ms |
334
498
  | screenshot | 82 ms, 133 KB | 77 ms, 113 KB |
335
499
 
336
- `sidecar_extract` on a 30-row list: 1106 chars for all 30 rows (≈330 tokens),
500
+ `browser_extract` on a 30-row list: 1106 chars for all 30 rows (≈330 tokens),
337
501
  against 1291 chars for the same page read as text — and at a 500-char read
338
502
  budget the text read stops around row 12 while the list still returns 30.
339
503
 
@@ -383,10 +547,10 @@ frame's corner is one `DOM.getFrameOwner` plus one call on the owner element.
383
547
  | What | Number |
384
548
  | --- | --- |
385
549
  | the "what is on top of this control" probe | **1 ms median, 2 ms max** (5 samples) — the same `Runtime.evaluate` hop, no round trip added |
386
- | `sidecar_click` on an uncovered control | 460 ms median (3 runs, page-load settle included) |
387
- | `sidecar_click` on the same control under a cookie banner | 630 ms median (3 runs) — the banner's own Accept click, a 150 ms beat, a re-probe, then the intended click |
550
+ | `browser_click` on an uncovered control | 460 ms median (3 runs, page-load settle included) |
551
+ | `browser_click` on the same control under a cookie banner | 630 ms median (3 runs) — the banner's own Accept click, a 150 ms beat, a re-probe, then the intended click |
388
552
  | the same under a modal that offers to delete the account | 456 ms — reported, nothing pressed |
389
- | one `sidecar_do` step on an uncovered control | 458 ms median (3 runs) |
553
+ | one `browser_do` step on an uncovered control | 458 ms median (3 runs) |
390
554
  | the same step under the cookie banner | 633 ms median (3 runs) — cleared, then the step ran |
391
555
 
392
556
  A batch pays it once per step that has something over it, not once per call: a
@@ -411,10 +575,10 @@ is now done inside the call, by looking the control up by what it is.
411
575
 
412
576
  | What | Number |
413
577
  | --- | --- |
414
- | `sidecar_click` on a control the page just redrew | **462 ms median, 483 ms max** (8 samples) — one extra read of the page's visible text, 600 chars, to find the control again |
415
- | `sidecar_click` on a control that was not redrawn | 445 ms median, 446 ms max (8 runs) — the probe costs nothing when the number is still good |
578
+ | `browser_click` on a control the page just redrew | **462 ms median, 483 ms max** (8 samples) — one extra read of the page's visible text, 600 chars, to find the control again |
579
+ | `browser_click` on a control that was not redrawn | 445 ms median, 446 ms max (8 runs) — the probe costs nothing when the number is still good |
416
580
  | stale refs recovered this way | **8/8** |
417
- | a control that really left the page | still refused, `Call sidecar_view and use the new number` — 2/2 in the breadth suite |
581
+ | a control that really left the page | still refused, `Call browser_view and use the new number` — 2/2 in the breadth suite |
418
582
 
419
583
  So the recovery costs about **17 ms** when it is needed and nothing when it is
420
584
  not, against a model round trip of two calls plus the tokens of a fresh read.
@@ -442,7 +606,7 @@ change mid-session. Changing it restarts the browser, so it costs a cold start:
442
606
  | windowed → windowless, same profile and page | 22.1 s | one process, cold start |
443
607
  | asking for the shape it already has | 0 ms | nothing is restarted |
444
608
 
445
- Measured through the tool layer (`sidecar_open` → `sidecar_state` → `ps` spawn
609
+ Measured through the tool layer (`browser_open` → `browser_state` → `ps` spawn
446
610
  args), profile `probe-tool`, 1 run each; the same-shape row is the third call in
447
611
  the same run. A browser start on this machine is 20–22 s either way, so the
448
612
  switch is not what costs — the process is.
@@ -458,9 +622,9 @@ bounded wait is the difference between a silent `viewers 0` and a sentence:
458
622
 
459
623
  | what | cost |
460
624
  | --- | --- |
461
- | `sidecar_open` with a tab watching | 0 ms |
462
- | `sidecar_open` with nothing watching | ≤ 1.2 s, and the line says so |
463
- | `sidecar_open` when the chat has no column to draw into | 0 ms (no wait) |
625
+ | `browser_open` with a tab watching | 0 ms |
626
+ | `browser_open` with nothing watching | ≤ 1.2 s, and the line says so |
627
+ | `browser_open` when the chat has no column to draw into | 0 ms (no wait) |
464
628
  | building the sentence on a normal state read | 0 ms, one comparison |
465
629
 
466
630
  Measured with `/tmp/probe-unseen.mjs` (tool layer, example.com, profile
@@ -490,7 +654,7 @@ reverse-contains (1).
490
654
 
491
655
  | what the caller has | what it used to take | now |
492
656
  | --- | --- | --- |
493
- | a name for the field, no number | read the page, find the number, then type | one `sidecar_type` with `field`, one round trip |
657
+ | a name for the field, no number | read the page, find the number, then type | one `browser_type` with `field`, one round trip |
494
658
  | the field's words live in a div | click landed on the div, text went wherever focus was | resolved to the field, `typed into "Work email"` |
495
659
  | a banner over the field | typed under the banner | banner cleared first, then typed |
496
660
 
@@ -500,7 +664,7 @@ Breadth check `type-by-words` on the `/bare` form: value lands in
500
664
  ## Phase 11 — choosing a dropdown by name, 0.21.0
501
665
 
502
666
  `/bare` now carries a `select[name=seats]` whose only name on screen is a div
503
- saying `Seats`. One `sidecar_select` with `field: "Seats"`, `value: "Two"`:
667
+ saying `Seats`. One `browser_select` with `field: "Seats"`, `value: "Two"`:
504
668
 
505
669
  | what | result |
506
670
  | --- | --- |
@@ -534,7 +698,7 @@ Exact equality still wins over the squeezed form, so `Sign in` never lands on
534
698
  | what | before | now |
535
699
  | --- | --- | --- |
536
700
  | `{field: "Birthday"}` | not found, fell back to clicking the div | found the hidden input, clicked its visible box |
537
- | value after one `sidecar_do` step | whatever already had focus | `2001-02-03` in `#bd`, `typed into "Birthday"` |
701
+ | value after one `browser_do` step | whatever already had focus | `2001-02-03` in `#bd`, `typed into "Birthday"` |
538
702
  | extra round trips | read the page, guess a number | none |
539
703
 
540
704
  The visible-box rule: a candidate is usable when it, or its nearest visible
@@ -547,7 +711,7 @@ something the person can see.
547
711
 
548
712
  | what | result |
549
713
  | --- | --- |
550
- | `sidecar_grab` | 6 ms, `picture-check.png`, 140x90 |
714
+ | `browser_grab` | 6 ms, `picture-check.png`, 140x90 |
551
715
  | before | three round trips: list the images, ask for the full address, fetch it |
552
716
 
553
717
  A picture page has no text at all, so a read of it costs one line and no tokens;
@@ -557,7 +721,7 @@ the picture comes back as a file path the person can open.
557
721
 
558
722
  Two 140x90 PNGs: one a gradient, one a single colour.
559
723
 
560
- | page | what `sidecar_grab` answers | time |
724
+ | page | what `browser_grab` answers | time |
561
725
  | --- | --- | --- |
562
726
  | `/pic` | `... 140x90 a picture with detail` | 21 ms (first, page cold) |
563
727
  | `/flat` | `... 140x90 mostly one colour` | 4 ms |
@@ -569,7 +733,7 @@ travels in the same line as the path.
569
733
 
570
734
  | page | read line | extra round trips |
571
735
  | --- | --- | --- |
572
- | `/pic` (gradient, no words) | `picture 140x90 a picture with detail "Chart" — sidecar_grab saves it as a file` | 0 |
736
+ | `/pic` (gradient, no words) | `picture 140x90 a picture with detail "Chart" — browser_grab saves it as a file` | 0 |
573
737
  | `/flat` (single colour) | `picture 140x90 mostly one colour "Chart" — …` | 0 |
574
738
  | `/words` (text page) | no picture line | 0 |
575
739
 
@@ -597,20 +761,20 @@ A changed scroll offset is still reported, so "nothing changed" is never a lie.
597
761
 
598
762
  ## Phase 20 — one call carries the question and the answer, 0.30.0
599
763
 
600
- | step | before | `sidecar_ask` |
764
+ | step | before | `browser_ask` |
601
765
  | --- | --- | --- |
602
- | type the message | `sidecar_type` + `submit` | same, inside the call |
603
- | read the page | `sidecar_view` | — |
604
- | read it again until the answer stops growing | 2–6 more `sidecar_view` calls | — |
766
+ | type the message | `browser_type` + `submit` | same, inside the call |
767
+ | read the page | `browser_view` | — |
768
+ | read it again until the answer stops growing | 2–6 more `browser_view` calls | — |
605
769
  | **turns for a chat reply** | 3–7 | **1** |
606
- | measured `sidecar_ask` on `/chat` (answer over 2 arrivals, 700 ms tail) | — | 2.5 s, one turn |
770
+ | measured `browser_ask` on `/chat` (answer over 2 arrivals, 700 ms tail) | — | 2.5 s, one turn |
607
771
 
608
772
  ## Phase 21 — a review round that closes the loop, 0.31.0
609
773
 
610
- | step | before | `sidecar_review` |
774
+ | step | before | `browser_review` |
611
775
  | --- | --- | --- |
612
776
  | write the request (repo, commit, focus) | by hand, every round | built from three fields |
613
- | send it, wait for the answer | `sidecar_ask` | same call |
777
+ | send it, wait for the answer | `browser_ask` | same call |
614
778
  | turn the answer into a list | read it, retype it | parsed, numbering kept |
615
779
  | know what the last round was about | — | round log, with the commit and the time |
616
780
  | **turns for a review round** | 3–7 + bookkeeping | **1** |
@@ -686,7 +850,7 @@ session), three were real.
686
850
  | --- | --- | --- |
687
851
  | popup lookup | any non-main tab, then navigated or closed | only a target that is neither this page nor another chat's (`isForeign`) |
688
852
  | focused password in a result line | echoed the `.value` (`DUMMY_SECRET_123`) | name only, value always empty |
689
- | `sidecar_do` on a step that did not land | kept going, typed into the wrong page | stops at that step, says to re-plan |
853
+ | `browser_do` on a step that did not land | kept going, typed into the wrong page | stops at that step, says to re-plan |
690
854
  | overlay that would not clear | clicked the control under it anyway | refuses the click/typing, names the overlay |
691
855
  | `changed_only` twice | repeated the same change | diff becomes the new baseline |
692
856
  | `/sidecar/state` | top-level `url` could be another chat's tab | `?session=` adds `chat.url/title/viewers` (live: doubao page, viewers 1) |
@@ -717,3 +881,14 @@ not.
717
881
  | human typing, >160 chars | 8-char bursts, 16–58 ms gaps, ≈6 ms per char |
718
882
  | headless UA | desktop Chrome UA, no `HeadlessChrome` |
719
883
  | suite | 70 pass, breadth 66, ui-copy clean |
884
+
885
+ ## Flash 分支(Electron 11.3.0 / Chromium 87,1440×900,Apple 芯片 + Rosetta)
886
+
887
+ | what | measured |
888
+ | --- | --- |
889
+ | cold start to a driven page | 1389 ms(热启 545–927 ms) |
890
+ | first painted frame | 8–10 s(SWF 自己的加载;之前是 11 KB 白帧) |
891
+ | one frame via capturePage | 16 ms |
892
+ | frame size, JPEG q72 | 96–103 KB |
893
+ | mirror SSE | 15 fps(66 ms 一轮),4 s 收 60 帧,均值 97.8 KB |
894
+ | switch cost | 一次浏览器重启(`browser_open({flash:true})` → `browser_open({flash:false})`) |
package/docs/FLASH.md ADDED
@@ -0,0 +1,84 @@
1
+ # Flash 之后:把兼容性放在正确的位置上
2
+
3
+ Chrome 88(2021 年 1 月)拿掉 Flash,不是商业决定,是插件模型的失败:一个能直接操作音频、图形、网络和本地文件的第三方二进制,安全面等于浏览器本身,而它承担的内容又都可以用开放标准重写。结论对自研同样成立:**不要再造插件槽位,要造内容层的运行时。**
4
+
5
+ ## 兼容的到底是什么
6
+
7
+ 一个 SWF 只有四样东西是「约定」:
8
+
9
+ 1. **标签流**——Shape/Sprite/Bitmap/Font/Sound/Frame 的二进制结构,决定画面怎么组出来;
10
+ 2. **时间线**——`ShowFrame` 与帧率构成保留模式动画,帧可以跳、可以循环;
11
+ 3. **AVM1 / AVM2 语义**——脚本看到的 DisplayObject、事件、`MovieClip`、`SharedObject`;
12
+ 4. **安全模型**——跨域、socket 策略、本地存储。
13
+
14
+ 只要这四样对得上,游戏就能跑。渲染用 Canvas2D 还是 WebGPU、时钟是实时还是虚拟、存储落 IndexedDB 还是宿主磁盘,**游戏看不见**。这就是「参考原理重做一个更好的」的立足点:兼容性锁在 AVM + 标签流,创新空间在其余全部。
15
+
16
+ ## 分层设计
17
+
18
+ ```
19
+ SWF ──▶ 摄入(读取标签流, 建 IR) ──▶ 显示列表 + 时间线 ──▶ 渲染(WebGPU, 损伤跟踪)
20
+ │
21
+ ├─ AVM 兼容核(Ruffle wasm / 自研 AVM1 解释器)
22
+ ├─ 虚拟时钟(固定步长, 输入带时间戳)
23
+ ├─ 能力沙箱(网络/存储/剪贴板 走宿主代理, 可录可回放)
24
+ └─ 对象图内省(每帧把状态压成 JSON 差异, 给 agent 读)
25
+ ```
26
+
27
+ 四层的分工决定了「比 Flash 好」不是同一维度的竞速:
28
+
29
+ | 维度 | Flash 的做法 | 这里的做法 | 可测指标 |
30
+ |---|---|---|---|
31
+ | 启动 | 装插件、NPAPI/PPAPI 握手 | 页面内 wasm,零安装 | 首帧 ms |
32
+ | 安全 | 插件进程 + 沙箱补丁 | 无原生代码,网络存储全代理 | 无提权面 |
33
+ | 确定性 | 真实时钟,帧率随机器 | 固定步长虚拟时钟 + 输入时间戳 | 同输入同结果 |
34
+ | 内省 | 无(黑盒像素) | 显示列表与 AVM 状态可导出 | 状态 → JSON 字节数 |
35
+ | 传输 | 屏幕像素 | 损伤区域 + 增量 | 每帧 KB |
36
+ | 回放 | 无 | 事件日志可重放 | 重放一致率 |
37
+
38
+ ## 已落地:PPAPI 分支做成插件里的开关(本版)
39
+
40
+ 「兼容层」和「能立刻玩」不是二选一。插件现在有两套运行时,同一个 CDP 管线、同一个镜像、同一套工具,切换只是一次重启:
41
+
42
+ ```
43
+ browser_open({ flash: true, url: 'http://aola.100bt.com/play/play.html?from_h5=true' })
44
+ ```
45
+
46
+ 开的时候宿主用 Electron 11.3.0(Chromium 87,最后一个还认 PPAPI 的 Chromium)当浏览器,关的时候切回 Chrome。输入注入、导航、读页面、镜像全都不用改,因为两边都说 CDP。
47
+
48
+ 三条必须同时成立的条件(缺一条就报「Flash 缺失」,且症状会骗人):
49
+
50
+ 1. `--ppapi-flash-path` 指向 bundle **里的 Mach-O 可执行文件** `PepperFlashPlayer.plugin/Contents/MacOS/PepperFlashPlayer`,不是 `.plugin` 目录。指错目录时插件报 `No PPP_GetInterface in plugin library`——那是假象,不是导出缺失。
51
+ 2. 必须加 `--no-sandbox`。否则 ppapi 进程报 `dlopen(...) (file system sandbox blocked open())`,页面提示没有 Flash。沙箱只对这一个隐藏的独立 profile 关,正常浏览器不关。
52
+ 3. 宿主必须是 x86_64(Apple 芯片跑 Rosetta):插件只有 i386/x86_64 切片,arm64 Electron 装不进它。所以下载固定取 `darwin-x64`。
53
+
54
+ Electron 11 的两个真实限制,代码里已按它们写:
55
+
56
+ - 它不会用当前 Chrome 的 profile 目录(会降级并上锁),Flash 分支固定用 `<profile>-flash` 独立目录;两边登录态各自独立。
57
+ - `/json/new` 在 Chromium 87 上是 404,单窗口应用也没有第二个窗口,所以 Flash 分支不建新页签:会话要页签时把唯一那个窗口导航过去。
58
+
59
+ 实测数字(Apple 芯片 + Rosetta,1440×900):
60
+
61
+ | 指标 | 值 |
62
+ |---|---|
63
+ | 冷启动到能驱动页面 | 1389 ms(首次含解压;同机热启 545–927 ms) |
64
+ | 页面首帧出图 | 约 8–10 s(SWF 自己的加载),之前是 11 KB 白帧 |
65
+ | 单帧 JPEG q72 | 96–103 KB |
66
+ | `capturePage` 取一帧 | 16 ms |
67
+ | 镜像 SSE 节奏 | 15 fps(66 ms 一轮),4 s 收 60 帧,均值 97.8 KB |
68
+ | PC 版判定 | 页面里出现 `EMBED .../weblogin/20171026_1.swf`、登录框是「多多号/手机/邮箱」而不是 H5 的样式 |
69
+
70
+ **不能随包分发 Adobe Flash Player**:插件本体留在本机状态目录(`~/.dsh/inner-browser-use/flash/plugin/`),由 `npm run flash:setup` 从本机已有的安装里找/拷,找不到就明说;Ruffle 那条路(MIT/BSD)仍是可分发的那条。
71
+
72
+ ## 分阶段
73
+
74
+ - **M0 摄入(已发布)**:读 SWF 标签流,给出尺寸/帧率/AVM/资源构成。`runtime/swf.mjs` + `npm run swf`。
75
+ - **MP 立刻能玩(本版)**:PPAPI 分支 + 插件开关,见上节;`npm run flash:setup` 备运行时。
76
+ - **M1 能跑**:自托管 Ruffle + 宿主路由 + `embed/object` 自动 polyfill,在一个真实 SWF 上量首帧、fps、字节数。
77
+ - **M2 能读**:把 Ruffle 的 player 对象与显示列表接到 `browser_eval`,agent 直接读游戏状态,不用截图。
78
+ - **M3 更快**:损伤跟踪 + 增量帧传输;对同一游戏给出「Flash 版 vs 这里」的毫秒与 CPU 对比。
79
+
80
+ ## 诚实边界
81
+
82
+ - AVM2 的兼容上限由兼容核决定;Ruffle 官方口径是「多数游戏能玩,仍有 API 缺口」,具体游戏要实测。
83
+ - Stage3D、加密 SWF、依赖本地插件的网游登录器,不在 M1–M3 的承诺内。
84
+ - ZWS(LZMA)本版明确拒绝;补 LZMA 是一天的事,但先要有真实影片来验证。