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.
@@ -1,104 +1,74 @@
1
1
  # Compatibility
2
2
 
3
- What this plugin assumes, and what it degrades to when an assumption fails.
4
-
5
- ## Requirements
6
-
7
- | Capability | Needed for | Missing means |
8
- | --- | --- | --- |
9
- | Desktop app (`dshDesktop.browser`) | Leasing the app's own browser tab | The call answers that the built-in browser is not available; tools stay registered. |
10
- | `sidebar.right.pane.tab` slot | The tab body | Nothing registers; no tab appears in the **+** menu. |
11
- | `ctx.sidebarRightTabs` | Tab type registration | Registration is skipped silently; the tab never appears. |
12
- | `ctx.sidebarRight.openTabIn` | Placing a tab in the column | Tools need the tab opened by hand; the call says so in one sentence. |
13
- | `ctx.webServer.register` | The whole wire | Plugin fails to load, visibly. |
14
- | `ctx.tools.register` | The tool surface | Bridge still runs; the model sees no `browser_*` tools. |
15
- | `EventSource` in the page | Learning that a tab is wanted | Same as the row above. |
16
-
17
- ## What it never touches
18
-
19
- Installing this plugin changes nothing a user already had:
20
-
21
- - Nothing shipped is wrapped or shadowed: the browser tab, its kind, its tools'
22
- behaviour and its partition are the product's; this plugin only drives them.
23
- - No guide card: the `开始` pane renders exactly the entries it had before. A
24
- client test asserts the registered tab type carries no `guide` array.
25
- - No global CSS: every rule is scoped to the page it drives.
26
- - No slot occupant on any slot it does not own, and no `computerUse`/`browserUse`
27
- provider registration, so the shipped browser's own providers keep their seats.
28
- - Removing the bundle removes every one of the above.
29
-
30
- ## Version notes
31
-
32
- - **Node 20+** on the Host. The plugin uses only `node:http`, `node:fs/promises`,
33
- `URL`, and `AbortSignal`.
34
- - **No dependencies.** No bundler, no build step, no post-install script.
35
- - `engines.dsh` is `>= 0.2.0`. Checked against Desktop builds whose sidebar
36
- browser tab kind is `browser` and whose guest handshake is an `about:blank#`
37
- webview carrying the workspace partition; on any other shape the plugin treats
38
- the Desktop bridge as absent rather than guessing.
39
-
40
- ## Coexistence
41
-
42
- - The tab kind is `browser` — the built-in browser's own. This plugin registers no
43
- tab kind of its own.
44
- - The route prefix is `/dsh-inner-browser-use`. Nothing is mounted under `/api`.
45
- - The guest partition is the one the app leases for the workspace
46
- (`dsh-sidebar-browser-<uuid>`). Two plugins leasing the same workspace key share
47
- storage, exactly as two built-in tabs do.
48
-
49
- ## Known limits
50
-
51
- | Limit | Why | Workaround |
52
- | --- | --- | --- |
53
- | A login is kept only if the page can read its cookie | `HttpOnly` cookies are not visible to the page half | Sites that keep the session in a readable cookie come back signed in |
54
- | One storage partition per workspace | The lease key is the workspace CWD | Use one workspace per account you need isolated |
55
- | One chat is on screen at a time | The column shows one chat, so another chat's page does not exist | Open the chat that asks; the call answers in about a second |
56
- | Text-only observations | Frames are JSON over one SSE socket | Use `browser_script` to pull exact values |
57
- | A capture needs that page drawn | The app has no surface to read for a page another tab covers | Bring the chat's tab forward; the call says so |
58
-
59
- ## Downloads land in the chat folder
60
-
61
- The app cancels its own downloads, so a page cannot save a file by itself. The
62
- page half therefore collects what a page offers — an `<a download>`, a `blob:`
63
- address, a `data:` address, or a link whose path ends in an archive, office
64
- document, PDF, text, image or media extension — and hands the bytes to the Host,
65
- which writes them under `<chat folder>/.dsh-inner-browser-use/downloads/`. The
66
- call returns that path, and `browser_download` with no argument prints the files
67
- already collected.
68
-
69
- Limits that come from the page, not from the plugin:
70
-
71
- - One offered file is capped at 7 MB and six are taken in one go; a file the
72
- page could not hand over is named with its size in the answer, so the page can
73
- be asked again. The Host refuses an upload over 8 MB with the size named.
74
- - A page whose `connect-src` forbids the site's own address cannot fetch it; the
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.
@@ -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.