dsh-inner-browser-use 0.13.0 → 0.27.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 CHANGED
@@ -12,7 +12,8 @@ live picture of that browser, and your own cursor keeps working on the picture.
12
12
  | --- | --- |
13
13
  | `sidecar_open` | Starts the browser (or reuses the running one) and opens an address. `headless: false` shows a real window on the desktop instead. |
14
14
  | `sidecar_view` | Returns the address, the title, every control as `1 … 120`, and the visible text. |
15
- | `sidecar_click` | Clicks a numbered control, or an `x`/`y` point. `double: true` double-clicks. |
15
+ | `sidecar_extract` | Returns the items a CSS selector matches, one compact line each with its link, instead of the whole page. |
16
+ | `sidecar_click` | Clicks a numbered control, or an `x`/`y` point. `double: true` double-clicks. A cookie banner or modal over the control is cleared first, then the click happens. |
16
17
  | `sidecar_type` | Types text into the focused field, or clicks a numbered control first. Chinese goes in as one string. `submit: true` presses Enter. |
17
18
  | `sidecar_key` | Presses one key: Enter, Tab, Escape, Backspace, Delete, ArrowUp/Down, Home, End, PageUp, PageDown. |
18
19
  | `sidecar_scroll` | Scrolls the wheel at the centre of the page, or at `x`/`y`. `dy: 600` is about a screen down. |
@@ -94,8 +95,8 @@ In the app: **Plugins → Install from folder**, and pick the clone. To install
94
95
  tarball instead, build it from that directory and install the file:
95
96
 
96
97
  ```bash
97
- npm pack # writes dsh-inner-browser-use-0.13.0.tgz
98
- dsh plugin install ./dsh-inner-browser-use-0.13.0.tgz
98
+ npm pack # writes dsh-inner-browser-use-0.16.0.tgz
99
+ dsh plugin install ./dsh-inner-browser-use-0.16.0.tgz
99
100
  ```
100
101
 
101
102
  The plugin appears under **Plugins** as *Background browser*. It needs Google Chrome or
@@ -118,6 +119,9 @@ opens before a call: a chat that never asked for a page keeps its column as it w
118
119
  profile folder, and `sidecar_downloads` names it, its size and when it arrived.
119
120
  - **The network is visible.** `sidecar_network` lists what the page asked for, newest first,
120
121
  so a page that fills itself in from an address can be understood without a screenshot.
122
+ - **An overlay does not eat the action.** A cookie banner over the button is cleared the way a
123
+ person would clear it, then the click lands — in the same call, and in every step of a
124
+ `sidecar_do` batch. Anything offering to delete, pay or sign out is reported instead.
121
125
 
122
126
  ## What it does not do
123
127
 
package/README.zh.md CHANGED
@@ -11,7 +11,8 @@ profile,不是产品自带的那个浏览页签:右栏放的是它的画面
11
11
  | --- | --- |
12
12
  | `sidecar_open` | 启动浏览器(已在跑就复用)并打开一个地址。传 `headless: false` 会改为在桌面上开真实窗口。 |
13
13
  | `sidecar_view` | 返回地址、标题、编号成 `1 … 120` 的控件,以及可见文字。 |
14
- | `sidecar_click` | 点编号控件,或点 `x`/`y` 坐标。`double: true` 双击。 |
14
+ | `sidecar_extract` | 按 CSS 选择器把列表项读成一行一条、带链接,比整页读省得多。 |
15
+ | `sidecar_click` | 点编号控件,或点 `x`/`y` 坐标。`double: true` 双击。控件被横幅或弹层挡着时,先关掉它再点。 |
15
16
  | `sidecar_type` | 往焦点输入框输入文字,也可以先点一个编号控件。中文整段送进去。`submit: true` 回车提交。 |
16
17
  | `sidecar_key` | 按一个键:Enter、Tab、Escape、Backspace、Delete、上下左右、Home、End、PageUp、PageDown。 |
17
18
  | `sidecar_scroll` | 在页面中心滚轮,也可以指定 `x`/`y`。`dy: 600` 约一屏。 |
@@ -102,6 +103,7 @@ dsh plugin install ./dsh-inner-browser-use-0.12.1.tgz
102
103
  - **iframe 能读,跨站也能。** 不管是和页面共用进程,还是 Chrome 单独给一个进程,里面的控件都和页面自己的控件一样有编号;点击按框架在页面上的实际位置落点。
103
104
  - **下载会落到磁盘。** 点一下就下载的文件写在这个浏览器自己的 profile 目录里,`sidecar_downloads` 报出文件名、大小和到达时间。
104
105
  - **能看到网络。** `sidecar_network` 倒序列出页面请求了什么,自己用接口填内容、屏幕上又看不出原因的页面,不用截图去猜。
106
+ - **遮罩不吞动作。** 盖在按钮上的 cookie 横幅会按人的做法先关掉再点下去,同一次调用里完成;`sidecar_do` 的每一步都一样。写着删除、支付、退出的遮罩只报告,不代按。
105
107
 
106
108
  ## 不做什么
107
109
 
@@ -131,11 +131,25 @@ browser without pulling the column away from the person reading it.
131
131
 
132
132
  ## Tool surface
133
133
 
134
- `sidecarTools()` in `tools.js` builds the 25 definitions and `index.js` registers them on the
134
+ `sidecarTools()` in `tools.js` builds the 28 definitions and `index.js` registers them on the
135
135
  plugin's tool scope. All of them return one text block. A page read numbers the controls and
136
136
  keeps those numbers across later reads, so `sidecar_click` by ref means the element the model
137
137
  last saw; navigation drops them and the model gets `REF_GONE` rather than a wrong click.
138
138
 
139
+ Before a ref click or a ref focus, one page-side probe asks what is on top of that control.
140
+ When something is, and that something carries its own safe way out (a banner with Accept /
141
+ Close, a modal with 同意 / 关闭), the probe's answer names that button's point, the driver takes
142
+ it, checks the control is clear, and then performs the action the model asked for. The result
143
+ says both things. An overlay whose wording involves deleting, paying or signing out is only
144
+ reported, never accepted on the person's behalf. The whole exchange is one `Runtime.evaluate`
145
+ and a possible `mousePressed`/`mouseReleased` pair on an already-open CDP connection, so an
146
+ uncovered click pays nothing.
147
+
148
+ `sidecar_do` runs the same probe before a step that names a control — by `ref`, by
149
+ text, or by field label — so a banner the first step clears is gone for the rest of
150
+ the batch, and the step's own line says it in one clause (`after Accept on a banner,`).
151
+ A batch therefore pays for one overlay, not one per step.
152
+
139
153
  An action answers with one line — address, title, focused field — and reads the page only when
140
154
  `read: true` is passed. That is what keeps a long batch cheap (see `BENCH.md`).
141
155
 
package/docs/BENCH.md CHANGED
@@ -41,7 +41,7 @@ the default budget (4600 chars of page text).
41
41
  | refs across reads | new numbers every read | **stable** (same refs, ticker-only text changes) |
42
42
  | click round trip | 1 ms | 1 ms |
43
43
  | key round trip | 13 ms | 0–2 ms |
44
- | launch → first frame | 506 ms | 129–492 ms (cold/warm profile) |
44
+ | launch → first frame | 506 ms | 129–539 ms (cold/warm profile) |
45
45
  | steady mirror | 60 fps, 72 KB/frame | 60 fps, 76 KB/frame |
46
46
  | screenshot | 82 ms / 133 KB | 79–86 ms / 115 KB |
47
47
 
@@ -98,21 +98,41 @@ answer), and a read now also asks for the frame tree.
98
98
  | key round trip | 13 ms | 2 ms |
99
99
  | screenshot | 82 ms, 133 KB | 77 ms, 113 KB |
100
100
 
101
- Breadth run (`npm run breadth`), 25 checks, all true: open shadow DOM read and
101
+ `sidecar_extract` on a 30-row list: 1106 chars for all 30 rows (≈330 tokens),
102
+ against 1291 chars for the same page read as text — and at a 500-char read
103
+ budget the text read stops around row 12 while the list still returns 30.
104
+
105
+ Live run (`npm run live`), 8 checks, all true on the open internet: open
106
+ example.com in 566 ms, read it for 785 chars, click the language link on
107
+ wikipedia.org and land on en.wikipedia.org in 2.3 s, go back to
108
+ www.wikipedia.org, extract ten list rows from the Web scraping article in 3 ms
109
+ (1813 chars), visit a host that cannot resolve (1.3 s to the browser's own
110
+ error page, where a read, a click and a batch all refuse instead of quoting it),
111
+ recover from there to a real page, and type into a search box and reach a
112
+ results URL in 1.3 s. These are wall-clock end to end including the site's own
113
+ load; the local numbers below are where the driver's own cost lives. Two hosts
114
+ were unusable from this machine and are not plugin problems: Hacker News (12 s
115
+ connect timeout, confirmed with curl) and Wikipedia's search redirect, which
116
+ answers on the home page and times out on the query — the search check runs on
117
+ Bing now, driven by the box's words rather than a control number.
118
+
119
+ Breadth run (`npm run breadth`), 33 checks, all true: open shadow DOM read and
102
120
  click, form typing, select, checkbox, submit, `confirm()` seen and accepted,
103
121
  iframe read, iframe typing, iframe click, cross-site frame read and click,
104
122
  popup followed, download saved, network responses listed, a file uploaded
105
123
  into a file input, the page printed to a PDF, a changed-only read, names from `for=` and `aria-labelledby`, a click found by its words, a login filled by field names in one call,
106
- stale ref refused, a crashed renderer picked back up on the same page.
124
+ stale ref refused, a crash picked back up on the same page, a list read as 30 rows, a click under a full-screen cookie banner clears the banner and lands on the
125
+ control, typing into a field under that banner works the same way, and an
126
+ overlay that offers to delete the account is reported but never accepted.
107
127
 
108
128
  ## Phase 4 — what a change costs
109
129
 
110
130
  | What | Number |
111
131
  | --- | --- |
112
- | change in the page → frame ready for the column | 9 ms median, 17 ms max (12 samples) |
132
+ | change in the page → frame ready for the column | 7 ms median, 7 ms max (12 samples) |
113
133
  | `changed_only` after a field changes, dense page | 58 chars ≈ **17 tokens** (whole page: 444) |
114
134
  | read: text page, `changed_only` | 42 chars ≈ 12 tokens (whole page: 1517) |
115
- | renderer crashed → readable page again | **1.4 s** (detect, fresh tab, same address) |
135
+ | renderer crashed → readable page again | **1.39 s** (detect, fresh tab, same address) |
116
136
 
117
137
  The mirror's delay is measured from the moment the page is told to change to
118
138
  the frame object the column would draw — capture, JPEG, and the CDP hop. It is
@@ -122,3 +142,202 @@ browser's compositing and the SSE hop, both smaller.
122
142
  A cross-site frame (Chrome runs it in its own process) costs one more round
123
143
  trip than a same-process one: its controls arrive in the same read, and the
124
144
  frame's corner is one `DOM.getFrameOwner` plus one call on the owner element.
145
+
146
+ ## Phase 5 — what an overlay costs
147
+
148
+ | What | Number |
149
+ | --- | --- |
150
+ | 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 |
151
+ | `sidecar_click` on an uncovered control | 460 ms median (3 runs, page-load settle included) |
152
+ | `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 |
153
+ | the same under a modal that offers to delete the account | 456 ms — reported, nothing pressed |
154
+ | one `sidecar_do` step on an uncovered control | 458 ms median (3 runs) |
155
+ | the same step under the cookie banner | 633 ms median (3 runs) — cleared, then the step ran |
156
+
157
+ A batch pays it once per step that has something over it, not once per call: a
158
+ banner cleared by step 1 is gone for steps 2..n, so a five-step flow through a
159
+ cookie wall costs one extra step's worth of time, not five.
160
+
161
+ The probe costs nothing when nothing is in the way, which is the common case;
162
+ the covered case pays about 170 ms to clear the banner and land the click in
163
+ the same call, instead of making the model spend a whole extra turn (a read, a
164
+ click, another read) telling it to close the banner first.
165
+
166
+ ## Phase 6 — what a stale number costs, 0.16.0
167
+
168
+ A ref is a number for an element. A page that redraws a list, a table, a card
169
+ grid — React, Vue, Svelte, or plain `innerHTML` — throws every element away and
170
+ makes new ones, so the numbers from the last read stop pointing anywhere. The
171
+ control is still on screen, saying the same words, usually in the same place.
172
+
173
+ Before this phase that cost two round trips: the call came back `That control is
174
+ gone`, the model read the page again to get a new number, then used it. The work
175
+ is now done inside the call, by looking the control up by what it is.
176
+
177
+ | What | Number |
178
+ | --- | --- |
179
+ | `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 |
180
+ | `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 |
181
+ | stale refs recovered this way | **8/8** |
182
+ | a control that really left the page | still refused, `Call sidecar_view and use the new number` — 2/2 in the breadth suite |
183
+
184
+ So the recovery costs about **17 ms** when it is needed and nothing when it is
185
+ not, against a model round trip of two calls plus the tokens of a fresh read.
186
+
187
+ What counts as the same control: the same words and the same kind. A button does
188
+ not become a field; a field never passes for the button beside it; a link keeps
189
+ its destination, because the same words pointing somewhere else is a different
190
+ thing to click. The words have to appear **exactly once** on the page — two
191
+ "Delete" buttons in two rows are a choice only the person can make, so that case
192
+ still asks for a read.
193
+
194
+ The call says what it did: `(the page redrew, so "Learn more" has a new number — used where it is now)`, so the model knows its old number is not the one to keep
195
+ using. On the live network the same thing happened on example.com: a link
196
+ replaced by an identical clone, one click, 4.4 s including the iana.org page
197
+ load, straight to `https://www.iana.org/help/example-domains`.
198
+
199
+ ## Phase 7 — what changing the shape of the browser costs, 0.17.0
200
+
201
+ A site that refuses a windowless browser is the one case where the shape has to
202
+ change mid-session. Changing it restarts the browser, so it costs a cold start:
203
+
204
+ | what | median | notes |
205
+ | --- | --- | --- |
206
+ | windowless → windowed, same profile and page | 21.6 s | one process, cold start |
207
+ | windowed → windowless, same profile and page | 22.1 s | one process, cold start |
208
+ | asking for the shape it already has | 0 ms | nothing is restarted |
209
+
210
+ Measured through the tool layer (`sidecar_open` → `sidecar_state` → `ps` spawn
211
+ args), profile `probe-tool`, 1 run each; the same-shape row is the third call in
212
+ the same run. A browser start on this machine is 20–22 s either way, so the
213
+ switch is not what costs — the process is.
214
+
215
+ Reading a check page costs nothing: the sentence is built from the title already
216
+ in the read, and a normal page gets `''` for one comparison.
217
+
218
+ ## Phase 8 — saying it when nobody is drawing, 0.18.0
219
+
220
+ The mirror is drawn by the tab in the chatting column, and that tab attaches
221
+ while the page is loading. Waiting for it costs nothing when it arrives, and a
222
+ bounded wait is the difference between a silent `viewers 0` and a sentence:
223
+
224
+ | what | cost |
225
+ | --- | --- |
226
+ | `sidecar_open` with a tab watching | 0 ms |
227
+ | `sidecar_open` with nothing watching | ≤ 1.2 s, and the line says so |
228
+ | `sidecar_open` when the chat has no column to draw into | 0 ms (no wait) |
229
+ | building the sentence on a normal state read | 0 ms, one comparison |
230
+
231
+ Measured with `/tmp/probe-unseen.mjs` (tool layer, example.com, profile
232
+ `probe-tool`): 1344 ms with nothing watching and no column asked for, 3236 ms in
233
+ the first version of the same wait that did not skip the case where no column was
234
+ ever requested — the whole difference was the wasted 1.2 s.
235
+
236
+ ## Phase 9 — finding the field behind the words, 0.19.0
237
+
238
+ A read of a `/bare` form (two div labels, two bare inputs, no aria anywhere on
239
+ the fields) and one `{field: "Work email"}` step:
240
+
241
+ | what | result |
242
+ | --- | --- |
243
+ | the click lands on the field, not on the div | yes, value `me@x.y` in `input[name=wk]` |
244
+ | the second, untouched field | still empty |
245
+ | the tool's own words | `typed into "Work email"` |
246
+ | worst case when nothing matches | one extra in-page query, then the old behaviour |
247
+
248
+ The query walks the open shadow roots (capped at 400) and, per visible editable
249
+ element, builds a list of candidate names from real labels, `aria-labelledby`,
250
+ `aria-label`, `placeholder`, `title`, `name`, `label[for]`, the element right
251
+ before it, and its wrapper's text — then scores exact (3) / contains (2) /
252
+ reverse-contains (1).
253
+
254
+ ## Phase 10 — typing by name, 0.20.0
255
+
256
+ | what the caller has | what it used to take | now |
257
+ | --- | --- | --- |
258
+ | a name for the field, no number | read the page, find the number, then type | one `sidecar_type` with `field`, one round trip |
259
+ | 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"` |
260
+ | a banner over the field | typed under the banner | banner cleared first, then typed |
261
+
262
+ Breadth check `type-by-words` on the `/bare` form: value lands in
263
+ `input[name=wk]`, the page does not move (no stray Enter).
264
+
265
+ ## Phase 11 — choosing a dropdown by name, 0.21.0
266
+
267
+ `/bare` now carries a `select[name=seats]` whose only name on screen is a div
268
+ saying `Seats`. One `sidecar_select` with `field: "Seats"`, `value: "Two"`:
269
+
270
+ | what | result |
271
+ | --- | --- |
272
+ | the value after the call | `2` |
273
+ | the tool's own words | `chose Two in Seats` (on-screen name, not `name="seats"`) |
274
+ | a wrong option | answered with the first 12 options instead of a page read |
275
+ | round trips to do it | one; no read beforehand, no read after |
276
+
277
+ The naming rules (`labelSpot` and `selectSpot`) now live in one shared in-page
278
+ block, so the two steps cannot drift apart.
279
+
280
+ ## Phase 12 — words split across nodes, 0.22.0
281
+
282
+ `/words` now carries `<button><span>Sign</span><span>in</span></button>`, which
283
+ the page reports as `Signin`.
284
+
285
+ | what | result |
286
+ | --- | --- |
287
+ | `{text: "Sign in"}` before | not found; caller had to fall back to a number |
288
+ | `{text: "Sign in"}` now | clicked, title changes, `ok click "Sign in"` |
289
+ | extra work | none: the same single control list, one comparison changed |
290
+
291
+ Exact equality still wins over the squeezed form, so `Sign in` never lands on
292
+ `Sign up` when both exist.
293
+
294
+ ## Phase 13 — the field behind the box, 0.23.0
295
+
296
+ `/bare` gains the date-picker shape: a visible rounded box with a transparent
297
+ `<input>` laid over it, its words in a div two levels up.
298
+
299
+ | what | before | now |
300
+ | --- | --- | --- |
301
+ | `{field: "Birthday"}` | not found, fell back to clicking the div | found the hidden input, clicked its visible box |
302
+ | value after one `sidecar_do` step | whatever already had focus | `2001-02-03` in `#bd`, `typed into "Birthday"` |
303
+ | extra round trips | read the page, guess a number | none |
304
+
305
+ The visible-box rule: a candidate is usable when it, or its nearest visible
306
+ ancestor, has a real box — so a 0-opacity input still counts if it covers
307
+ something the person can see.
308
+
309
+ ## Phase 15 — the answer is the picture, 0.25.0
310
+
311
+ `/pic` is one 140x90 PNG and nothing else.
312
+
313
+ | what | result |
314
+ | --- | --- |
315
+ | `sidecar_grab` | 6 ms, `picture-check.png`, 140x90 |
316
+ | before | three round trips: list the images, ask for the full address, fetch it |
317
+
318
+ A picture page has no text at all, so a read of it costs one line and no tokens;
319
+ the picture comes back as a file path the person can open.
320
+
321
+ ## Phase 16 — say what the picture looks like, 0.26.0
322
+
323
+ Two 140x90 PNGs: one a gradient, one a single colour.
324
+
325
+ | page | what `sidecar_grab` answers | time |
326
+ | --- | --- | --- |
327
+ | `/pic` | `... 140x90 a picture with detail` | 21 ms (first, page cold) |
328
+ | `/flat` | `... 140x90 mostly one colour` | 4 ms |
329
+
330
+ The sampling is 32x32 inside the page, so it costs no round trip and its result
331
+ travels in the same line as the path.
332
+
333
+ ## Phase 17 — the answer is a picture, in the read itself, 0.27.0
334
+
335
+ | page | read line | extra round trips |
336
+ | --- | --- | --- |
337
+ | `/pic` (gradient, no words) | `picture 140x90 a picture with detail "Chart" — sidecar_grab saves it as a file` | 0 |
338
+ | `/flat` (single colour) | `picture 140x90 mostly one colour "Chart" — …` | 0 |
339
+ | `/words` (text page) | no picture line | 0 |
340
+
341
+ The sampling is 32x32 inside the page, in the same call that reads the page, so a
342
+ blank chart is visible before anything is saved.
343
+
package/docs/ROADMAP.md CHANGED
@@ -84,14 +84,25 @@ stealing the column from other chats.
84
84
 
85
85
  ## Phase 5 — reliability and safety
86
86
 
87
- - Human takeover: typing in the pane pauses the agent's next action and says so;
88
- a resume action exists.
87
+ - Human takeover (0.17.1): real input in the pane (click / text / wheel / key)
88
+ holds the agent's next action for 6 s and says so; hover and move do not count;
89
+ reads stay open; `sidecar_resume` takes the page back at once.
89
90
  - Guarded actions (upload, download, close tab, buy-ish submits) can require a
90
- confirm flag.
91
+ confirm flag. Shipped in 0.14.0 for the narrow case that matters most: an
92
+ overlay in the way is cleared only when its own way out is a safe word
93
+ (accept / agree / close / 同意 / 关闭 …) and the overlay says nothing about
94
+ deleting, paying or signing out; otherwise it is reported, never taken.
95
+ Since 0.15.0 the same clearing runs before every control-naming step of a
96
+ `sidecar_do` batch, so a multi-step flow through a cookie wall fails zero times.
91
97
  - Crash recovery: attach to a still-running Chrome, rebuild the page, keep the
92
98
  profile; a killed host never leaves a zombie Chrome.
93
99
  - Every failure names what happened, what state the page is in, and the next
94
100
  call to make.
101
+ - A failure that is the browser's, not the page's, is said in those words: on
102
+ Chrome's own error page a read, a click and a batch step all refuse with the
103
+ `ERR_…` code and the way out, while the tab list, console, failed requests and
104
+ address still answer, because those are how a stuck person finds out what
105
+ happened.
95
106
 
96
107
  ## Phase 6 — proof and packaging
97
108
 
@@ -100,9 +111,124 @@ stealing the column from other chats.
100
111
  - `docs/VERIFY.md` walk-through that a stranger can run; `scripts/doctor.mjs`
101
112
  green; 3 linters zero errors; tests covering every new tool.
102
113
  - Packed tarball, CHANGELOG per release, `0.x` bumps that match behaviour.
114
+ 0.13.4 and 0.14.0 shipped tarballs without `tools.js` and `read.js` — a clone
115
+ hid it because the dev profile symlinks the repo. Since 0.15.0 a test walks the
116
+ import graph against `files`, and another unpacks the tarball and imports it.
117
+
118
+ ## Phase 7 — recovery inside the call, 0.16.0
119
+
120
+ The measured weak spot after Phase 5 was not speed but turns spent recovering:
121
+ every framework redraw invalidated the numbers from the last read, and the model
122
+ paid a full read plus a turn to get them back. 0.16.0 moves that into the call by
123
+ finding the control again by identity (kind + exact words + link destination),
124
+ refusing when the words are not unique. Measured at 462 ms against 445 ms for a
125
+ fresh control — 17 ms when needed, nothing when not.
126
+
127
+ Since 0.16.1 the count is visible: `sidecar_state` reports `refs followed N`,
128
+ and breadth asserts it moved after a redraw, so the next rounds can tell from
129
+ real sites whether redrawing is frequent enough to justify more work here.
130
+ Next in this direction: the same identity recovery for `sidecar_do` steps that
131
+ name a field by its label when the label element is rebuilt (today the step
132
+ falls back to a fresh read).
103
133
 
104
134
  ## Order of work
105
135
 
106
136
  1. Phase 0 baseline. 2. Phase 1 + 2 together (they share the read model).
107
137
  3. Phase 3 breadth. 4. Phase 4 numbers. 5. Phase 5 hardening. 6. Phase 6 proof,
108
138
  then repeat the loop with whatever the numbers say is worst.
139
+
140
+ ## Phase 8 — the shape of the browser, 0.17.0
141
+
142
+ Two things were wrong at the edge of the tool surface, and both were only
143
+ reachable on real sites.
144
+
145
+ The first was that the shape of the browser could not change. `sidecar_open`
146
+ accepted `headless: false` and answered success; when a browser was already
147
+ running nothing happened, because the flag was only read at launch. A site that
148
+ refuses a windowless browser (chatgpt.com on this machine: a "just a moment"
149
+ page forever, versus a login page in a windowed one) therefore had no way out
150
+ except killing a process by hand. Since 0.17.0 a request for the other shape
151
+ restarts the browser on the same profile, cookie jar and address — 21.6 s
152
+ windowless → windowed, 22.1 s back, 0 ms when the shape already matches — and
153
+ both `sidecar_open` and `sidecar_state` say which shape is in front of the
154
+ person.
155
+
156
+ The second was silence on a check page. A site asking for proof of a person is
157
+ not broken and not an error page, so every read returned it as content. Now
158
+ every read of it carries the way past: `headless false` on the same profile.
159
+
160
+ Next in this direction: a site that only accepts a window is still discovered by
161
+ the model, not by the plugin. The plugin could try the page in a window by itself
162
+ when the read shows a challenge, but that puts a window on the person's screen
163
+ without being asked — the current rule is that a person's screen is theirs.
164
+
165
+ ## Phase 9 — the mirror has to exist before it can be seen, 0.18.0
166
+
167
+ Everything the plugin does is worth nothing if the person cannot see it, and the
168
+ failure is silent: the browser runs, reads work, and the column is empty because
169
+ the tab that draws it was never loaded. 0.18.0 makes that state say itself out
170
+ loud, with the one action that fixes it, and only pays the wait when a column was
171
+ actually requested.
172
+
173
+ Next in this direction: the drawing half could re-attach by itself. A tab that
174
+ reloads re-subscribes on its own, but a tab that never loaded the half cannot be
175
+ told to — that is a load-order problem, so the fix belongs in how the panel half
176
+ is delivered, not in the wait.
177
+
178
+ ## Phase 10 — words are not where the field is, 0.19.0
179
+
180
+ The tool used to treat "the control that says Email" as "the field called
181
+ Email". Real forms separate the two, so the writing step now resolves the words
182
+ to the element that takes text, and says which one it used.
183
+
184
+ Next in this direction: the same resolution for `sidecar_type` when the caller
185
+ names a field by its words instead of a ref, and for date/time pickers where the
186
+ visible box is a div and the real input is hidden beside it.
187
+
188
+ ## Phase 11 — say where, not which number, 0.20.0
189
+
190
+ Typing by name is in. The same resolution is what other steps should be able to
191
+ use when the caller knows the destination but not the number — the next place
192
+ worth doing this is `sidecar_select` (choose an option by the field it belongs
193
+ to), and after that the date/time case where the visible box is a div and the
194
+ real input sits hidden beside it.
195
+
196
+ ## Phase 12 — the same words, the same place, 0.21.0
197
+
198
+ `sidecar_select` joined `sidecar_type` and `sidecar_do` on the shared resolver.
199
+ Left in this direction: the date/time case where the box the user sees is a div
200
+ and the real input is hidden beside it, and `sidecar_click` on a control whose
201
+ words are split across sibling nodes (`<span>Sign</span><span>in</span>`).
202
+
203
+ ## Phase 13 — the page's spelling is not the caller's, 0.22.0
204
+
205
+ Whitespace is normalised in both halves now (tool side and in-page side). Next
206
+ in this line: the date/time box where the visible box is a div and the real
207
+ input is hidden beside it, which needs the resolver to accept a hidden target
208
+ and click the thing in front of it.
209
+
210
+ ## Phase 14 — behind the box, 0.23.0
211
+
212
+ Both halves now resolve a name to a place: visible field, hidden field behind a
213
+ visible box, words split over nodes, words outside the wrapper. Next: a page
214
+ whose visible box is a `<canvas>` or an `<iframe>` — nothing in the tree to
215
+ name, so the fallback has to be a picture plus a click, not a name lookup.
216
+
217
+ ## Phase 16 — a file, not a path, 0.25.0
218
+
219
+ `sidecar_grab` returns a path. Next: say one line about the picture (size, and
220
+ whether it is mostly one colour) so a model that cannot open files still learns
221
+ "the chart is blank" without a full read.
222
+
223
+ ## Phase 17 — a picture nobody can see, 0.26.0
224
+
225
+ The description is coarse on purpose. Next: when a page's answer is a picture,
226
+ let a read say so in one line ("the answer is a picture, and it is blank") so a
227
+ model that cannot open files does not need the tool at all to notice a failure.
228
+
229
+ ## Phase 18 — a picture that never arrived, 0.27.0
230
+
231
+ A picture is only an answer if it loaded. Next: report a large picture element
232
+ whose source failed (`naturalWidth === 0`) as a broken picture rather than
233
+ staying silent, since that is the commonest way a generated image "disappears".
234
+
package/docs/VERIFY.md CHANGED
@@ -87,12 +87,79 @@ still hold the cookies.
87
87
  内置浏览器 means the page half is stale: reload the GUI page.
88
88
  - Guest-era logins are not migrated; sign in once in the new profile.
89
89
 
90
+ ## Live
91
+
92
+ `node scripts/live.mjs` does the same work on the open internet, with no local
93
+ fixture: it opens `example.com` (566 ms), reads it, clicks the language link on
94
+ `wikipedia.org` and lands on `en.wikipedia.org` (2.3 s, the page's own load
95
+ included), goes back (URL back to `wikipedia.org`), reads ten list items out of
96
+ the Web scraping article in one call (3 ms, 1813 chars), visits a host that
97
+ cannot resolve, and types into a search box then presses Enter (1.3 s to a
98
+ results URL). Eight JSON lines; eight `true` is the bar. A site being
99
+ unreachable is reported as such and is not a plugin failure — the run says
100
+ which site and how long it waited.
101
+
102
+ Two of those checks are about failure rather than success. `live-dead-host`
103
+ opens `https://never-resolves-this.invalid/`, waits for the browser to give up,
104
+ then asks three tools — a read, a click, a batch — what the page says: all
105
+ three must answer "the network could not reach that address", and none may
106
+ quote the browser's own `ERR_…` text or `This site can't be reached` back as
107
+ page content. `live-dead-host-recovers` navigates from there to a real address
108
+ and requires the real page. The search check drives Bing by the box's words
109
+ (`field: '搜索词'`) rather than a control number, because a search box is a
110
+ `combobox` on one engine and an `input` on another; it used to use Wikipedia,
111
+ whose home page answers while its search redirect times out on this network —
112
+ three runs in a row failed there and said nothing about the tools.
113
+
114
+ None of the numbers are the plugin's own latency: they are dominated by the
115
+ site and the network, which is the point of having them next to the local
116
+ measurements in [BENCH.md](BENCH.md).
117
+
118
+ ## The packed file is whole
119
+
120
+ A clone works even when `package.json`'s `files` list is wrong, because the repo
121
+ is right there. A tarball does not. After `npm pack`, the listing must contain
122
+ the four modules the host half imports:
123
+
124
+ ```bash
125
+ tar tzf dsh-inner-browser-use-*.tgz | grep -E 'package/(tools|read)\.js'
126
+ ```
127
+
128
+ Two lines, or the install will fail with `Cannot find module` on first call.
129
+ `node --test test/*.test.mjs` covers both halves of this: one test walks the
130
+ import graph and fails when an imported file is not in `files`, another unpacks
131
+ the tarball and imports it from outside the repo.
132
+
90
133
  ## Breadth
91
134
 
92
135
  `node scripts/breadth.mjs` drives a shadow-DOM component, a form (type, select,
93
136
  checkbox, submit), a native dialog, an iframe (read, type, click), a click that
94
137
  opens a tab, and a dead ref against local pages, and prints one JSON line per
95
- check. Twenty-seven `true` lines is the bar; a `false` line names the check that
138
+ check. Forty-two `true` lines is the bar; a `false` line names the check that
96
139
  broke. Two of them cover a cross-site frame, which Chrome runs in its own
97
- process.
140
+ process. Three cover a control under an overlay: the click clears a cookie
141
+ banner and lands (`under clicked`), typing into a field under the same banner
142
+ works, and a modal that offers to delete the account is reported without
143
+ anything being pressed. Two more drive the same overlay through `sidecar_do`:
144
+ a batch step clears the banner before it clicks, and a batch step fills a field
145
+ that was under it. One more counts the recoveries: after the redraw check, the
146
+ state line says at least one ref was followed to its new number. Three cover a
147
+ hand on the mirror: while the person is using it a read still works
148
+ (`hold-allows-read`), an action is refused and names both ways out
149
+ (`hold-refuses-action`), and `sidecar_resume` hands the page back so the same
150
+ click goes through (`resume-lets-go`).
151
+
152
+ ## Who the mirror serves
153
+
154
+ The bar that matters most cannot be reached from `node --test`, because it is
155
+ about a column that belongs to one conversation. With DSH running:
156
+
157
+ ```sh
158
+ node scripts/live-safety.mjs http://127.0.0.1:49552
159
+ ```
98
160
 
161
+ It reads the state, then tries the two ways in — a frame stream and a
162
+ navigation — as a session that is not the owner. Both must answer 403, and the
163
+ owner's stream must answer 200. Three `true` lines is the bar; `open · owner
164
+ none` as the first line only means nothing is on screen, and the two refusals
165
+ still have to hold.
package/index.js CHANGED
@@ -285,6 +285,9 @@ export function apply(ctx) {
285
285
  await page.key('keyDown', spec)
286
286
  await page.key('keyUp', { ...spec, text: undefined })
287
287
  }
288
+ /* Hovering and moving are looking, not acting: only real input counts as
289
+ the person taking the page. */
290
+ if (kind !== 'hover' && kind !== 'move') sidecar.humanUse({ kind, x, y })
288
291
  json(200, cursor === undefined ? { ok: true } : { ok: true, cursor })
289
292
  } catch (error) {
290
293
  json(500, { ok: false, text: String(error?.message ?? error) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-inner-browser-use",
3
- "version": "0.13.0",
3
+ "version": "0.27.0",
4
4
  "description": "An agent-owned Chromium sidecar mirrored into the DeepSeek Harness right column: the agent drives it, the person watches and can take the mouse, and logins survive restarts.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -14,6 +14,8 @@
14
14
  "files": [
15
15
  "index.js",
16
16
  "sidecar.js",
17
+ "tools.js",
18
+ "read.js",
17
19
  "client.js",
18
20
  "locale/*.json",
19
21
  "cordis.patch.yml",
@@ -42,10 +44,14 @@
42
44
  "scripts": {
43
45
  "test": "node --test test/*.test.mjs",
44
46
  "doctor": "node scripts/doctor.mjs",
45
- "breadth": "node scripts/breadth.mjs"
47
+ "breadth": "node scripts/breadth.mjs",
48
+ "live": "node scripts/live.mjs"
46
49
  },
47
50
  "license": "MIT",
48
- "author": "regrevia",
51
+ "author": {
52
+ "name": "regrevia",
53
+ "url": "https://github.com/regrevia"
54
+ },
49
55
  "repository": {
50
56
  "type": "git",
51
57
  "url": "git+https://github.com/regrevia/dsh-inner-browser-use.git"