dsh-inner-browser-use 0.11.44 → 0.18.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 +112 -75
- package/README.zh.md +90 -45
- package/client.js +473 -3276
- package/docs/ARCHITECTURE.md +196 -150
- package/docs/BENCH.md +234 -0
- package/docs/COMPATIBILITY.md +72 -102
- package/docs/ROADMAP.md +176 -0
- package/docs/VERIFY.md +131 -415
- package/index.js +230 -1117
- package/locale/en.json +11 -22
- package/locale/zh.json +11 -22
- package/package.json +12 -5
- package/read.js +718 -0
- package/scripts/doctor.mjs +89 -0
- package/sidecar.js +1364 -0
- package/tools.js +1652 -0
- package/docs/upstream/cookie-persistence.md +0 -52
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -1,104 +1,74 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
What this plugin assumes, and what it
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
|
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
##
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
plugin then has only what the page already holds, so ask the page for the file
|
|
76
|
-
through its own UI.
|
|
77
|
-
- A file streams only while the page keeps answering. A page that navigates away
|
|
78
|
-
mid-save ends the transfer, and the call says so instead of writing half a file.
|
|
79
|
-
|
|
80
|
-
## Checking an install
|
|
81
|
-
|
|
82
|
-
Run these in the chat whose tab is open, in this order. Each line is what a
|
|
83
|
-
working install answers.
|
|
84
|
-
|
|
85
|
-
1. `browser_navigate` to `https://example.com/` → title `Example Domain`, one control.
|
|
86
|
-
2. `browser_view` → the same page, so the tab is the one this chat owns.
|
|
87
|
-
3. `browser_script` adding a button, then `browser_click` it → `Clicked #2 [button] clicked at 120,160`.
|
|
88
|
-
4. `browser_screenshot` → a path under `.dsh-inner-browser-use/`, or the sentence
|
|
89
|
-
naming the tab to bring forward.
|
|
90
|
-
5. Restart the app once if `browser_selection` answers `unknown tool`: the tool
|
|
91
|
-
list is read at start.
|
|
92
|
-
|
|
93
|
-
## A tab that is not the active one
|
|
94
|
-
|
|
95
|
-
The strip mounts only the tab it shows, and after a restart the restored tab is
|
|
96
|
-
not the active one. Nothing in the tab face switches it: `openTabIn` with
|
|
97
|
-
`activate`/`focus`, `focus(id)`, `focus(session, id)`, `active(id)`,
|
|
98
|
-
`tab.activate` and `tab.focus` were each tried and left the active tab
|
|
99
|
-
unchanged; the tab bar's controls are not inside `sidebarRight.host`, so there is
|
|
100
|
-
no element to click.
|
|
101
|
-
|
|
102
|
-
A call therefore never depends on that switch. On load, the chat on screen gets
|
|
103
|
-
its own tab back and the page it had; every other chat opens nothing, because a
|
|
104
|
-
tab lands in the column that is showing.
|
|
3
|
+
What this plugin assumes, and what it does when an assumption fails.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Harness | 0.2.0 or newer, with the right column |
|
|
8
|
+
| Runtime | Node 20 or newer |
|
|
9
|
+
| Browser | Google Chrome, Chromium or Canary, 112 or newer |
|
|
10
|
+
| System | macOS 12 or newer; Linux x86_64 or arm64 |
|
|
11
|
+
| License | MIT |
|
|
12
|
+
|
|
13
|
+
## Where Chrome is looked for
|
|
14
|
+
|
|
15
|
+
The plugin starts a browser it finds at one of these paths, in this order:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
19
|
+
/Applications/Chromium.app/Contents/MacOS/Chromium
|
|
20
|
+
/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary
|
|
21
|
+
/usr/bin/google-chrome
|
|
22
|
+
/usr/bin/chromium
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That is the whole list. A Chromium kept elsewhere (snap, flatpak, a home directory, a bundled
|
|
26
|
+
runtime) is not found, and there is no setting to point at one. Installing Chrome the ordinary
|
|
27
|
+
way, or placing the binary at `/usr/bin/chromium`, is the supported arrangement.
|
|
28
|
+
|
|
29
|
+
If nothing in the list exists, the first tool call returns `sidecar: no Chrome found on this
|
|
30
|
+
machine` and nothing is written to disk.
|
|
31
|
+
|
|
32
|
+
Chrome 112 is the floor: the mirror needs `--headless=new` and a screencast that survives an
|
|
33
|
+
idle page.
|
|
34
|
+
|
|
35
|
+
## macOS
|
|
36
|
+
|
|
37
|
+
The browser is launched with `--use-mock-keychain` and `--password-store=basic`, so no keychain
|
|
38
|
+
dialog appears on the person's screen and no password is read from the OS keyring. Chrome's own
|
|
39
|
+
password store inside the profile still works.
|
|
40
|
+
|
|
41
|
+
The profile directory is created under the home directory of whoever runs DSH, which is the
|
|
42
|
+
same user the browser runs as. Running that process as a different user leaves the profile
|
|
43
|
+
unwritable, and running it as root leaves Chrome unable to start its sandbox: run DSH as the
|
|
44
|
+
person using it.
|
|
45
|
+
|
|
46
|
+
## Linux
|
|
47
|
+
|
|
48
|
+
The two Linux entries above are the same lookup list; put Chrome or Chromium there. The browser
|
|
49
|
+
runs without a display, so no X or Wayland session is needed for the mirror itself.
|
|
50
|
+
|
|
51
|
+
Linux has not been part of the release test pass; macOS is where this plugin is developed and
|
|
52
|
+
checked.
|
|
53
|
+
|
|
54
|
+
## Windows
|
|
55
|
+
|
|
56
|
+
Not supported. There is no Windows path in the lookup list, and no installation of this plugin
|
|
57
|
+
on Windows has been checked.
|
|
58
|
+
|
|
59
|
+
## When an assumption fails
|
|
60
|
+
|
|
61
|
+
| Assumption | What happens |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| No Chrome at a listed path | First tool call returns the error above; no profile, no process. |
|
|
64
|
+
| Harness older than 0.2.0 | The plugin does not load. |
|
|
65
|
+
| A Harness build with no right column | The tools still drive the browser; there is nowhere to show the mirror. |
|
|
66
|
+
| A site that refuses headless | The user agent is replaced with an ordinary desktop Chrome string; a site that insists on a window can be opened with `headless: false`. |
|
|
67
|
+
| A page wider than 1440×900 | The viewport is fixed, so the page lays out for 1440 wide; nothing is scaled. |
|
|
68
|
+
| Two plugin instances, one profile | The second adopts the Chrome already running on that profile rather than starting another. |
|
|
69
|
+
| The debugging file left by a crashed Chrome | It is removed and the browser is started again. |
|
|
70
|
+
|
|
71
|
+
## Limits by design
|
|
72
|
+
|
|
73
|
+
One page per browser, fixed 1440×900 viewport, no mobile emulation, no downloads, no uploads, no
|
|
74
|
+
script evaluation. The eight tools in the README are the whole surface.
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<!-- ui-copy-ignore -->
|
|
2
|
+
# Roadmap: from a working mirror to the best computer-use plugin
|
|
3
|
+
|
|
4
|
+
The bar is not "another browser driver". The bar is: an agent can finish a real
|
|
5
|
+
task in this browser with fewer turns, fewer tokens, fewer surprises than with
|
|
6
|
+
Codex, Claude Code, browser-use, Stagehand, Playwright MCP or any screenshot-only
|
|
7
|
+
computer-use model — and the person can watch it happen in the sidebar of the
|
|
8
|
+
chat that asked, while every other chat stays untouched.
|
|
9
|
+
|
|
10
|
+
Written as phases with a number attached, because a phase without a number is a
|
|
11
|
+
wish. Baselines come from `node scripts/bench.mjs`; every phase must move one.
|
|
12
|
+
|
|
13
|
+
## Where the competition actually wins
|
|
14
|
+
|
|
15
|
+
| Project | What it does well | What it costs |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Codex / Claude Code (computer + bash) | free-form shell, one action per turn, human can take over mid-run | no page structure, no refs, screenshots dominate context |
|
|
18
|
+
| browser-use | indexed DOM tree with stable element ids, action model, per-step memory | tree is big; a full read is thousands of tokens |
|
|
19
|
+
| Stagehand | `act` / `observe` / `extract` split, self-healing selectors, natural-language actions | server round trips; two model calls per step |
|
|
20
|
+
| Playwright MCP | accessibility tree with `ref` handles, deterministic waits | text-only; a page read is large; no live picture |
|
|
21
|
+
| Skyvern / CUA screenshot models | works on canvas apps and hostile DOM | ~1 image per step, slow and token-expensive |
|
|
22
|
+
|
|
23
|
+
Our position: **a real Chrome with a live, interactive mirror in the owning
|
|
24
|
+
chat, and a read model that is small by construction** — accessibility refs that
|
|
25
|
+
survive a read, viewport-only text, diffs instead of full re-reads, and one
|
|
26
|
+
batched tool for multi-step work. The mirror is not decoration: it is the human
|
|
27
|
+
takeover surface, and no other plugin can show it inside the asking chat without
|
|
28
|
+
stealing the column from other chats.
|
|
29
|
+
|
|
30
|
+
## Phase 0 — measurement (done when numbers exist)
|
|
31
|
+
|
|
32
|
+
- `scripts/bench.mjs`: launch→first frame, steady fps, frame KB, read ms /
|
|
33
|
+
chars / approx tokens for a dense page, a text page and a light page, click,
|
|
34
|
+
key, screenshot KB.
|
|
35
|
+
- Record every run in `docs/BENCH.md` with the commit; no phase is "done"
|
|
36
|
+
without a before/after row.
|
|
37
|
+
|
|
38
|
+
## Phase 1 — read model that costs less (target: dense page read ≤ 1.2k tokens)
|
|
39
|
+
|
|
40
|
+
- Accessibility-tree walk with `ref` handles that stay valid across reads of the
|
|
41
|
+
same page; only elements that exist in the tree get a number.
|
|
42
|
+
- Viewport-limited by default (`inView`), with `mode`: `text` (default),
|
|
43
|
+
`links`, `forms`, `full`; `max_chars` budget; text deduplicated and
|
|
44
|
+
truncation marked, never silent.
|
|
45
|
+
- `changed_only`: after an action, return only added / removed / changed refs —
|
|
46
|
+
the browser-use and Playwright MCP gap.
|
|
47
|
+
- Stable refs: a read re-numbers nothing that did not change, so the model can
|
|
48
|
+
act on numbers from an earlier read and gets a clear "ref 3 is gone, re-read"
|
|
49
|
+
instead of clicking the wrong thing.
|
|
50
|
+
|
|
51
|
+
## Phase 2 — one call, many steps (target: a login flow in 1 tool call)
|
|
52
|
+
|
|
53
|
+
- `sidecar_do`: ordered steps with per-step waits (`wait_text`, `wait_gone`,
|
|
54
|
+
`wait_idle`, `wait_url`), one result: what changed, the final URL, tokens spent
|
|
55
|
+
on one read instead of five.
|
|
56
|
+
- Auto-wait after every action: navigation start/finish, DOM quiet, and
|
|
57
|
+
`document.readyState`, so the next step never races the page.
|
|
58
|
+
- Ref errors are actionable: which ref, why it is gone, and one call that
|
|
59
|
+
re-reads and retries once.
|
|
60
|
+
|
|
61
|
+
## Phase 3 — any operation (target: no "can't do that" left)
|
|
62
|
+
|
|
63
|
+
- Hover, drag, double-click, right-click, wheel with modifiers.
|
|
64
|
+
- `select` option by label or value; checkboxes and radios by label.
|
|
65
|
+
- File upload (`DOM.setFileInputFiles`), download capture with a returned path,
|
|
66
|
+
print-to-PDF, full-page and element screenshots.
|
|
67
|
+
- Tabs: new / switch / close / list; iframes addressed by frame id; dialog
|
|
68
|
+
accept/dismiss; `sidecar_eval` for the last 1% of cases; cookies read/write for
|
|
69
|
+
debugging; console + failed requests surfaced in the state read.
|
|
70
|
+
- Keyboard: modifiers, shortcuts, clipboard paste, IME composition unchanged.
|
|
71
|
+
|
|
72
|
+
## Phase 4 — mirror latency and clarity (target: ≤120 ms input→painted, 60 fps idle)
|
|
73
|
+
|
|
74
|
+
- Adaptive JPEG quality and frame size: drop quality before dropping fps; send a
|
|
75
|
+
small first frame, refine when idle.
|
|
76
|
+
- `Page.startScreencast` tuning (maxWidth/quality/everyNthFrame), Chrome flags
|
|
77
|
+
that stop background throttling, `--disable-frame-rate-limit`.
|
|
78
|
+
- Client: decode off the main thread (`createImageBitmap`), draw only the newest
|
|
79
|
+
frame, no layout thrash, ResizeObserver-driven backing-store sizing for a crisp
|
|
80
|
+
picture at any pane width, DPR-aware.
|
|
81
|
+
- Pointer: keep the 1 ms path; add remote cursor shape to the pane cursor with a
|
|
82
|
+
proper fallback, and a visible "the agent is acting" pulse that never moves the
|
|
83
|
+
system cursor.
|
|
84
|
+
|
|
85
|
+
## Phase 5 — reliability and safety
|
|
86
|
+
|
|
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.
|
|
90
|
+
- Guarded actions (upload, download, close tab, buy-ish submits) can require a
|
|
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.
|
|
97
|
+
- Crash recovery: attach to a still-running Chrome, rebuild the page, keep the
|
|
98
|
+
profile; a killed host never leaves a zombie Chrome.
|
|
99
|
+
- Every failure names what happened, what state the page is in, and the next
|
|
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.
|
|
106
|
+
|
|
107
|
+
## Phase 6 — proof and packaging
|
|
108
|
+
|
|
109
|
+
- `docs/BENCH.md` with before/after per phase; a competitor comparison table with
|
|
110
|
+
measured numbers, not adjectives.
|
|
111
|
+
- `docs/VERIFY.md` walk-through that a stranger can run; `scripts/doctor.mjs`
|
|
112
|
+
green; 3 linters zero errors; tests covering every new tool.
|
|
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).
|
|
133
|
+
|
|
134
|
+
## Order of work
|
|
135
|
+
|
|
136
|
+
1. Phase 0 baseline. 2. Phase 1 + 2 together (they share the read model).
|
|
137
|
+
3. Phase 3 breadth. 4. Phase 4 numbers. 5. Phase 5 hardening. 6. Phase 6 proof,
|
|
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.
|