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 +7 -3
- package/README.zh.md +3 -1
- package/docs/ARCHITECTURE.md +15 -1
- package/docs/BENCH.md +224 -5
- package/docs/ROADMAP.md +129 -3
- package/docs/VERIFY.md +69 -2
- package/index.js +3 -0
- package/package.json +9 -3
- package/read.js +981 -0
- package/sidecar.js +42 -1
- package/tools.js +1749 -0
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
|
-
| `
|
|
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.
|
|
98
|
-
dsh plugin install ./dsh-inner-browser-use-0.
|
|
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
|
-
| `
|
|
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
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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
|
|
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–
|
|
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
|
-
|
|
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
|
|
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 |
|
|
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.
|
|
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:
|
|
88
|
-
|
|
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.
|
|
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.
|
|
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":
|
|
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"
|