dsh-inner-browser-use 0.11.44 → 0.13.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 +108 -75
- package/README.zh.md +88 -45
- package/client.js +473 -3276
- package/docs/ARCHITECTURE.md +183 -151
- package/docs/BENCH.md +124 -0
- package/docs/COMPATIBILITY.md +72 -102
- package/docs/ROADMAP.md +108 -0
- package/docs/VERIFY.md +80 -417
- package/index.js +227 -1117
- package/locale/en.json +11 -22
- package/locale/zh.json +11 -22
- package/package.json +9 -5
- package/scripts/doctor.mjs +89 -0
- package/sidecar.js +1323 -0
- package/docs/upstream/cookie-persistence.md +0 -52
package/docs/VERIFY.md
CHANGED
|
@@ -1,435 +1,98 @@
|
|
|
1
1
|
# Verify it works
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Five minutes, one page, no debugger. Each step names what a failure looks like.
|
|
4
4
|
|
|
5
5
|
## Before you start
|
|
6
6
|
|
|
7
|
-
- The plugin is installed and enabled
|
|
8
|
-
- The
|
|
9
|
-
|
|
10
|
-
-
|
|
7
|
+
- The plugin is installed and enabled, `0.12.1` or later (`package.json`).
|
|
8
|
+
- The GUI page was reloaded after the install: the page half loads on a page load.
|
|
9
|
+
- Chrome sits at one of the paths in
|
|
10
|
+
[COMPATIBILITY.md](COMPATIBILITY.md#where-chrome-is-looked-for).
|
|
11
|
+
- The app answers at `http://127.0.0.1:60930`.
|
|
11
12
|
|
|
12
|
-
## 1. The
|
|
13
|
+
## 1. The plugin answers
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
|
-
|
|
16
|
+
node scripts/doctor.mjs
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- `"tabType":false`: the page loaded but the tab type did not register; the log
|
|
22
|
-
line `phase:"idle"` in the proof log is the same fact.
|
|
23
|
-
- `client.fresh: false`: a page reported in and then stopped, or the app has been
|
|
24
|
-
idle past a minute. The page half beats every 20 s.
|
|
25
|
-
- `client` present, `connected:[]`: correct before any tab is open. Seats are
|
|
26
|
-
open tabs, not the page.
|
|
27
|
-
|
|
28
|
-
## 2. A tool opens its own page
|
|
29
|
-
|
|
30
|
-
Ask the model: `用 browser_view 看看当前页面`. Nothing was clicked.
|
|
31
|
-
|
|
32
|
-
Expected: the call returns the page text plus numbered controls, and the
|
|
33
|
-
**内置浏览器** tab shows the same page.
|
|
34
|
-
|
|
35
|
-
- `No 内置浏览器 tab answered.`: no page answered within `openWaitMs`. Check
|
|
36
|
-
the proof log for `phase:"resident-open"` and then `phase:"native-address"`; a
|
|
37
|
-
`phase:"native-missing"` instead means the tab was placed but took no address,
|
|
38
|
-
and the next call places it again.
|
|
39
|
-
|
|
40
|
-
## 3. Drive a page
|
|
41
|
-
|
|
42
|
-
```text
|
|
43
|
-
browser_navigate { "url": "https://example.com" }
|
|
44
|
-
browser_view {}
|
|
45
|
-
browser_click { "ref": 1 }
|
|
46
|
-
browser_type { "ref": 2, "text": "hello", "submit": true }
|
|
47
|
-
browser_script { "code": "return document.title" }
|
|
48
|
-
browser_screenshot{}
|
|
49
|
-
browser_download { "url": "https://example.com/report.pdf" }
|
|
50
|
-
browser_upload { "ref": 3, "file": "/absolute/path/note.txt" }
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Expected: the pointer moves and ripples in the page; `click`/`type`/`upload`
|
|
54
|
-
each return a fresh `view`; the screenshot returns a path under
|
|
55
|
-
`.dsh-inner-browser-use/` in the chat's working directory.
|
|
56
|
-
|
|
57
|
-
Measured in the app on 2026-10-25, nothing clicked, one chat on screen:
|
|
58
|
-
|
|
59
|
-
| call | came back |
|
|
60
|
-
| --- | --- |
|
|
61
|
-
| `browser_navigate` | `Example Domain`, one control |
|
|
62
|
-
| `browser_view` | the page text and `#1 [link] Learn more` |
|
|
63
|
-
| `browser_script` | the selection it set |
|
|
64
|
-
| `browser_selection` | that selection, 917 characters |
|
|
65
|
-
| `browser_copy` | `Copied 917 characters` |
|
|
66
|
-
| `browser_click` | text changed to `clicked` |
|
|
67
|
-
| `browser_type` | `#1 [input] n = hello harness` |
|
|
68
|
-
| `browser_download` | `.dsh-inner-browser-use/downloads/report.pdf` and its size |
|
|
69
|
-
| `browser_upload` | `#2 [input] f = C:\fakepath\dshib-upload.txt` |
|
|
70
|
-
|
|
71
|
-
## 4. The tab does not disturb other panes
|
|
72
|
-
|
|
73
|
-
Open the guide pane (`开始`) and the files pane: both are unaffected by this
|
|
74
|
-
plugin. The plugin registers one tab type, one pane key and one title key, and no
|
|
75
|
-
guide card. `test/client.test.mjs` guards the last part.
|
|
76
|
-
|
|
77
|
-
## 5. Read the proof log
|
|
78
|
-
|
|
79
|
-
`DSH_INNER_BROWSER_PROOF` (default `/tmp/dsh-inner-browser-use.log`) records one
|
|
80
|
-
JSON line per lifecycle event: `downlink-open`, `uplink`, `watch-open`.
|
|
81
|
-
|
|
82
|
-
- `watch-open` with no `wanted` lines and a failing tool: the page never received
|
|
83
|
-
the ask; check step 1.
|
|
84
|
-
- `uplink` frames with `"ok":false`: the page answered and refused; the error
|
|
85
|
-
string is the answer.
|
|
86
|
-
|
|
87
|
-
## Exit codes
|
|
88
|
-
|
|
89
|
-
`npm test` runs both halves: 19 host tests over a real socket, 6 client tests
|
|
90
|
-
that load the page module with a fake module loader and a fake `EventSource`.
|
|
91
|
-
`node ~/.dsh/skills/ui-restraint/scripts/ui-copy-lint.mjs .` must print
|
|
92
|
-
`ui-copy-lint: clean.`
|
|
93
|
-
|
|
94
|
-
## A capture needs that chat's page to be drawn
|
|
95
|
-
|
|
96
|
-
Reading, clicking and typing drive the page itself, so they work while you are
|
|
97
|
-
looking at another chat. Pixels are different: they come from a surface the app
|
|
98
|
-
is drawing. A view sitting behind another conversation's view is not drawn — the
|
|
99
|
-
guest keeps a real size (`575x1015`) and `capturePage` never answers at all, so
|
|
100
|
-
the tool tries twice for a short time and then reports what it got, rather than
|
|
101
|
-
waiting fifteen seconds. `browser_script` still reads that page.
|
|
102
|
-
|
|
103
|
-
Two things were measured here, and both say the same: the app draws only the pane
|
|
104
|
-
it is showing. `capturePage` on the covered guest fails with
|
|
105
|
-
`UnknownVizError` — a surface with no frame — and a guest the plugin appends to
|
|
106
|
-
the page itself is never attached at all: every accessor throws
|
|
107
|
-
`The WebView must be attached to the DOM and the dom-ready event emitted`.
|
|
108
|
-
Rendering the page out of sight is therefore not something the page half can do,
|
|
109
|
-
so the tool reports the chat to open and stops. The frame
|
|
110
|
-
`why:"capture-covered"` records the page it was asked to draw.
|
|
111
|
-
|
|
112
|
-
The proof log carries the reason in the frame next to it:
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
tail -5 /tmp/dsh-inner-browser-use.log # look for "why":"shot-geometry"
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
- `columnBefore` / `columnAfter` — the right column's size. `0x…` means the
|
|
119
|
-
column is closed; the tool tried to open it and this is what it got.
|
|
120
|
-
- `body` — the page body's own size. Two zeros with a wide column mean the tab
|
|
121
|
-
exists but is not the one the pane is showing.
|
|
122
|
-
- `active` — which view the pane had in front.
|
|
123
|
-
- `rooms` — the visible session containers and their sizes.
|
|
124
|
-
|
|
125
|
-
## A chat that is not on screen
|
|
126
|
-
|
|
127
|
-
Pixels come from a painted surface. A page behind another chat's tab has a size
|
|
128
|
-
but no surface, so the capture comes back empty; the tool then says which tab to
|
|
129
|
-
bring forward instead of guessing. A second guest would be no help here: it would
|
|
130
|
-
load the address again and photograph the top of a page whose scroll position and
|
|
131
|
-
form state are not the ones the chat is reading, so the image would not be of the
|
|
132
|
-
page the answer describes. Read the content instead — `browser_script` returns the
|
|
133
|
-
same text, and `browser_view` returns the same controls.
|
|
134
|
-
|
|
135
|
-
`browser_screenshot` with `offscreen: true` never opens the right column: it takes
|
|
136
|
-
the image only if a surface already exists and otherwise says so at once.
|
|
137
|
-
|
|
138
|
-
The proof log records what the capture found:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
tail -3 /tmp/dsh-inner-browser-use.log # "why":"capture-covered" carries href and error
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
A call for a chat the person is not reading never takes the column. If that chat
|
|
145
|
-
has no page yet, one is mounted out of sight and answers from there; only if that
|
|
146
|
-
is impossible does the call say which chat to open. The proof log shows the two
|
|
147
|
-
halves:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
grep -m1 '"phase":"wanted"' /tmp/dsh-inner-browser-use.log # panes, screen
|
|
151
|
-
grep -m1 'resident-open' /tmp/dsh-inner-browser-use.log # the page made out of sight
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
At most three pages are kept out of sight, and the oldest goes first, so a long
|
|
155
|
-
session cannot pile them up.
|
|
156
|
-
|
|
157
|
-
## What has been run in the app
|
|
158
|
-
|
|
159
|
-
Each line was run against the live pane on 2026-10-25 and is what the tool
|
|
160
|
-
returned, not what the code was meant to do.
|
|
161
|
-
|
|
162
|
-
| Tool | The page | Result |
|
|
163
|
-
| --- | --- | --- |
|
|
164
|
-
| `browser_navigate` | example.com | title `Example Domain` |
|
|
165
|
-
| `browser_view` | the same page | one control, the link's text |
|
|
166
|
-
| `browser_script` | the same page | added a button, a form, and an output to the page |
|
|
167
|
-
| `browser_click` | the added button at 60,140 (120x40) | its label changed to `clicked`, its title to `probe clicked`, and the reply named the point: `at 120,160` |
|
|
168
|
-
| the pointer | read from the page after a click | `translate3d(116px, 158px, 0px)`, opacity `1`, `z-index: 2147483647` — the click point less the pointer's hotspot |
|
|
169
|
-
| `browser_type` | the added text field | the field held the text that was typed |
|
|
170
|
-
| `browser_download` | a page with a "Download" link | the file is in `<chat folder>/.dsh-inner-browser-use/downloads/` and the tool returns that path |
|
|
171
|
-
| `browser_upload` | the added file field | the field held the chosen file's name |
|
|
172
|
-
| a link the page saves itself | a page whose own script saves a file | the reply names the file under the chat's folder; `POST /land` without the page's header answers 403 |
|
|
173
|
-
| `browser_selection` | not yet run in the app | the host half that carries this tool loads on the next start; the bridge is covered by `test/host.test.mjs` |
|
|
174
|
-
| `browser_copy` | not yet run in the app | same |
|
|
175
|
-
| the page is remounted | the page went blank | the next call reopened it; frame `why:"restore"` |
|
|
176
|
-
| `browser_screenshot` | a page the app is not drawing | said which tab to open; frame `why:"capture-covered"` |
|
|
177
|
-
|
|
178
|
-
A capture of a page the app *is* drawing returned a 74 KB PNG of that page; the
|
|
179
|
-
same call on a covered page has no surface to read, which is the boundary
|
|
180
|
-
described above.
|
|
181
|
-
|
|
182
|
-
## Both halves reload without restarting the app
|
|
183
|
-
|
|
184
|
-
A change is verifiable without asking anyone to restart anything. The two halves
|
|
185
|
-
load at different moments, and both get there on their own:
|
|
186
|
-
|
|
187
|
-
- `index.js` (the host half) is re-imported as soon as the file changes. A route
|
|
188
|
-
added or removed answers at once: `curl -s -o /dev/null -w '%{http_code}'
|
|
189
|
-
http://127.0.0.1:49631/dsh-inner-browser-use/<route>` flips from 404 to 200 on
|
|
190
|
-
the next request, with no app restart.
|
|
191
|
-
- `client.js` (the page half) replaces itself on its own: a loaded copy asks
|
|
192
|
-
`/state` every four seconds and reloads when the served build differs from its
|
|
193
|
-
own. A copy loaded before that self-check existed cannot replace itself, so the
|
|
194
|
-
window's refresh control is the one step that is not automatic; after it, the
|
|
195
|
-
page half keeps itself current. To prove which copy is loaded, read the hash
|
|
196
|
-
the page reports instead of guessing: `curl -s
|
|
197
|
-
http://127.0.0.1:49631/dsh-inner-browser-use/state | python3 -c "import json,sys;
|
|
198
|
-
print(json.load(sys.stdin)['client'])"` — `native: true` means the page runs a
|
|
199
|
-
copy that drives the product's own browser.
|
|
200
|
-
|
|
201
|
-
So: no restart to check a behaviour, and no restart to see the result of one.
|
|
202
|
-
|
|
203
|
-
To skip the wait, a reloader plugin mounted in the same app can be asked directly.
|
|
204
|
-
`dsh-unlag` answers on `POST /dsh-unlag/reload` with the header `x-dsh-unlag: 1`;
|
|
205
|
-
`GET /dsh-unlag/state` reports its two switches. It only acts while no turn is
|
|
206
|
-
running, so a request made from inside a turn answers `{"status":"busy"}` — leave
|
|
207
|
-
a waiter behind that asks again once the turn ends, and the next check runs on
|
|
208
|
-
the new code.
|
|
209
|
-
|
|
210
|
-
## The panel is taken only for the chat the person is looking at
|
|
211
|
-
|
|
212
|
-
A tool call can arrive for a chat whose page is not open. Taking the right panel
|
|
213
|
-
then would put an empty column in front of the chat the person is reading, so the
|
|
214
|
-
page half first asks which chat is on screen and answers only that one.
|
|
215
|
-
|
|
216
|
-
The answer comes from an occupant this plugin puts in the conversation header:
|
|
217
|
-
it renders nothing, measures the nearest header container that has a size, and
|
|
218
|
-
watches whether that container is on screen. Its beats name the chat:
|
|
219
|
-
|
|
220
|
-
```
|
|
221
|
-
grep -o '"phase":"seen","session":"[^"]*"' /tmp/dsh-inner-browser-use.log | tail -1
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Switch chats in the app and the named chat follows, one line per switch. When the
|
|
225
|
-
call is for another chat, its tab is placed in that chat's strip and the call
|
|
226
|
-
answers that the tab is there — the panel is left alone:
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
A frame naming no chat at all, when no header reports, keeps the older behaviour:
|
|
233
|
-
the tab is placed and the panel may open, then closes itself after three seconds
|
|
234
|
-
unless a page of ours mounted in it (`"step":"selfheal"`).
|
|
235
|
-
|
|
236
|
-
## A chat that is not on screen
|
|
237
|
-
|
|
238
|
-
Pixels come from a painted surface. A page behind another chat's tab has a size
|
|
239
|
-
but no surface, so the capture comes back empty; the tool then says which tab to
|
|
240
|
-
bring forward instead of guessing. A second guest would be no help here: it would
|
|
241
|
-
load the address again and photograph the top of a page whose scroll position and
|
|
242
|
-
form state are not the ones the chat is reading, so the image would not be of the
|
|
243
|
-
page the answer describes. Read the content instead — `browser_script` returns the
|
|
244
|
-
same text, and `browser_view` returns the same controls.
|
|
245
|
-
|
|
246
|
-
`browser_screenshot` with `offscreen: true` never opens the right column: it takes
|
|
247
|
-
the image only if a surface already exists and otherwise says so at once.
|
|
248
|
-
|
|
249
|
-
The proof log records what the capture found:
|
|
19
|
+
One line per fact: the address, the browser (`not running` before the first
|
|
20
|
+
call), the profile name and directory, whether it exists, the page, viewers,
|
|
21
|
+
frames, pointer and the control endpoint. It only reads.
|
|
250
22
|
|
|
251
23
|
```bash
|
|
252
|
-
|
|
24
|
+
curl -s http://127.0.0.1:60930/dsh-inner-browser-use/sidecar/state
|
|
253
25
|
```
|
|
254
26
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
is impossible does the call say which chat to open. The proof log shows the two
|
|
258
|
-
halves:
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
grep -m1 '"phase":"wanted"' /tmp/dsh-inner-browser-use.log # panes, screen
|
|
262
|
-
grep -m1 'resident-open' /tmp/dsh-inner-browser-use.log # the page made out of sight
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
At most three pages are kept out of sight, and the oldest goes first, so a long
|
|
266
|
-
session cannot pile them up.
|
|
267
|
-
|
|
268
|
-
## What has been run in the app
|
|
269
|
-
|
|
270
|
-
Each line was run against the live pane on 2026-10-25 and is what the tool
|
|
271
|
-
returned, not what the code was meant to do.
|
|
27
|
+
Expected before any tool call: `"alive":false`, `"profile":"default"`,
|
|
28
|
+
`"viewers":0`, `"error":""`.
|
|
272
29
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
| `browser_view` | the same page | one control, the link's text |
|
|
277
|
-
| `browser_script` | the same page | added a button, a form, and an output to the page |
|
|
278
|
-
| `browser_click` | the added button at 60,140 (120x40) | its label changed to `clicked`, its title to `probe clicked`, and the reply named the point: `at 120,160` |
|
|
279
|
-
| the pointer | read from the page after a click | `translate3d(116px, 158px, 0px)`, opacity `1`, `z-index: 2147483647` — the click point less the pointer's hotspot |
|
|
280
|
-
| `browser_type` | the added text field | the field held the text that was typed |
|
|
281
|
-
| `browser_download` | a page with a "Download" link | the file is in `<chat folder>/.dsh-inner-browser-use/downloads/` and the tool returns that path |
|
|
282
|
-
| `browser_upload` | the added file field | the field held the chosen file's name |
|
|
283
|
-
| a link the page saves itself | a page whose own script saves a file | the reply names the file under the chat's folder; `POST /land` without the page's header answers 403 |
|
|
284
|
-
| `browser_selection` | not yet run in the app | the host half that carries this tool loads on the next start; the bridge is covered by `test/host.test.mjs` |
|
|
285
|
-
| `browser_copy` | not yet run in the app | same |
|
|
286
|
-
| the page is remounted | the page went blank | the next call reopened it; frame `why:"restore"` |
|
|
287
|
-
| `browser_screenshot` | a page the app is not drawing | said which tab to open; frame `why:"capture-covered"` |
|
|
30
|
+
- `not answering under /dsh-inner-browser-use`: the plugin is not loaded, or the
|
|
31
|
+
app moved: set `DSH_BRIDGE` to its address.
|
|
32
|
+
- A 404 there: the plugin did not load, or an older build is on disk.
|
|
288
33
|
|
|
289
|
-
|
|
290
|
-
same call on a covered page has no surface to read, which is the boundary
|
|
291
|
-
described above.
|
|
292
|
-
|
|
293
|
-
## Both halves reload without restarting the app
|
|
294
|
-
|
|
295
|
-
A change is verifiable without asking anyone to restart anything. The two halves
|
|
296
|
-
load at different moments, and both get there on their own:
|
|
297
|
-
|
|
298
|
-
- `index.js` (the host half) is re-imported as soon as the file changes. A route
|
|
299
|
-
added or removed answers at once: `curl -s -o /dev/null -w '%{http_code}'
|
|
300
|
-
http://127.0.0.1:49631/dsh-inner-browser-use/<route>` flips from 404 to 200 on
|
|
301
|
-
the next request, with no app restart.
|
|
302
|
-
- `client.js` (the page half) replaces itself on its own: a loaded copy asks
|
|
303
|
-
`/state` every four seconds and reloads when the served build differs from its
|
|
304
|
-
own. A copy loaded before that self-check existed cannot replace itself, so the
|
|
305
|
-
window's refresh control is the one step that is not automatic; after it, the
|
|
306
|
-
page half keeps itself current. To prove which copy is loaded, read the hash
|
|
307
|
-
the page reports instead of guessing: `curl -s
|
|
308
|
-
http://127.0.0.1:49631/dsh-inner-browser-use/state | python3 -c "import json,sys;
|
|
309
|
-
print(json.load(sys.stdin)['client'])"` — `native: true` means the page runs a
|
|
310
|
-
copy that drives the product's own browser.
|
|
311
|
-
|
|
312
|
-
So: no restart to check a behaviour, and no restart to see the result of one.
|
|
313
|
-
|
|
314
|
-
To skip the wait, a reloader plugin mounted in the same app can be asked directly.
|
|
315
|
-
`dsh-unlag` answers on `POST /dsh-unlag/reload` with the header `x-dsh-unlag: 1`;
|
|
316
|
-
`GET /dsh-unlag/state` reports its two switches. It only acts while no turn is
|
|
317
|
-
running, so a request made from inside a turn answers `{"status":"busy"}` — leave
|
|
318
|
-
a waiter behind that asks again once the turn ends, and the next check runs on
|
|
319
|
-
the new code.
|
|
320
|
-
|
|
321
|
-
## The panel is taken only for the chat the person is looking at
|
|
322
|
-
|
|
323
|
-
A tool call can arrive for a chat whose page is not open. Taking the right panel
|
|
324
|
-
then would put an empty column in front of the chat the person is reading, so the
|
|
325
|
-
page half first asks which chat is on screen and answers only that one.
|
|
326
|
-
|
|
327
|
-
The answer comes from an occupant this plugin puts in the conversation header:
|
|
328
|
-
it renders nothing, measures the nearest header container that has a size, and
|
|
329
|
-
watches whether that container is on screen. Its beats name the chat:
|
|
330
|
-
|
|
331
|
-
```
|
|
332
|
-
grep -o '"phase":"seen","session":"[^"]*"' /tmp/dsh-inner-browser-use.log | tail -1
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Switch chats in the app and the named chat follows, one line per switch. When the
|
|
336
|
-
call is for another chat, its tab is placed in that chat's strip and the call
|
|
337
|
-
answers that the tab is there — the panel is left alone:
|
|
338
|
-
|
|
339
|
-
```
|
|
340
|
-
grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
A frame naming no chat at all, when no header reports, keeps the older behaviour:
|
|
344
|
-
the tab is placed and the panel may open, then closes itself after three seconds
|
|
345
|
-
unless a page of ours mounted in it (`"step":"selfheal"`).
|
|
346
|
-
|
|
347
|
-
## Where a login lives
|
|
348
|
-
|
|
349
|
-
A pane leases the app's own sidebar guest, and the shell decides which storage
|
|
350
|
-
partition that guest gets. The shipped code reserves it like this:
|
|
351
|
-
|
|
352
|
-
```
|
|
353
|
-
partition = `dsh-sidebar-browser-${randomUUID()}`
|
|
354
|
-
// "Reserve one guest in a workspace's process-lifetime partition."
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
So a login is shared by every chat of the same project and survives closing the
|
|
358
|
-
tab or the chat. The partition itself is named fresh each run, so the guest keeps
|
|
359
|
-
nothing on disk; the page half therefore files what the *page* can read under
|
|
360
|
-
`~/.dsh/inner-browser-use/cookies/<hash>.json`, mode 0600, keyed by the workspace
|
|
361
|
-
key, and writes it back when that origin is opened again:
|
|
362
|
-
|
|
363
|
-
```console
|
|
364
|
-
$ ls -l ~/.dsh/inner-browser-use/cookies/
|
|
365
|
-
-rw-------@ 1 apple staff 811 Oct 25 21:26 7b5c760580d2febdb4d5f50a.json
|
|
366
|
-
$ python3 -c "import json,glob;print([(c['name'],c['url']) for c in json.load(open(glob.glob('/Users/apple/.dsh/inner-browser-use/cookies/*.json')[0]))])"
|
|
367
|
-
[('i18next', 'http://127.0.0.1:8765'), ('sid', 'http://127.0.0.1:8765'), ('__ac_nonce', 'https://www.douyin.com'), ...]
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
That file was produced by the app: one guest had been to `http://127.0.0.1:8765`,
|
|
371
|
-
another to `https://www.douyin.com`, and each save replaced only its own origin, so
|
|
372
|
-
both sites sit in the same workspace file. A save runs on `did-stop-loading` and
|
|
373
|
-
every 30 s while the guest is alive; a restore runs once the page it belongs to has
|
|
374
|
-
loaded, and the page is loaded once more only when a cookie was actually written,
|
|
375
|
-
because a cookie set from the page cannot affect a document already open. The log
|
|
376
|
-
shows `{"why":"cookies-restored"}` or `{"why":"signed-in-again"}` when that happens.
|
|
377
|
-
|
|
378
|
-
Two limits stay. Cookies marked `HttpOnly` are invisible to the page, so a login
|
|
379
|
-
that lives only in those needs the password again after the app quits. And the
|
|
380
|
-
store holds only what the page could read when it was saved, so a workspace that
|
|
381
|
-
has not opened a site yet has no file; `node scripts/doctor.mjs` prints
|
|
382
|
-
`sign-in kept per workspace` either way.
|
|
383
|
-
|
|
384
|
-
## What a running app showed
|
|
385
|
-
|
|
386
|
-
Three flows were driven against the app itself, each one visible in its own output:
|
|
387
|
-
|
|
388
|
-
- **The arrow is in the image.** A click answered `at 101,167`, and the saved
|
|
389
|
-
image showed the pointer over that control — the capture puts the arrow back
|
|
390
|
-
even when the action is older than the arrow's own timeout.
|
|
391
|
-
- **A download lands in the chat's folder and arrives in the chat.** Following a
|
|
392
|
-
file link answered `Saved <chat folder>/.dsh-inner-browser-use/downloads/report.txt
|
|
393
|
-
(21 bytes)` and printed the file's contents in the same answer.
|
|
394
|
-
- **The page and its cookies stay.** A guest reopened with `"why":"reopen"` on the
|
|
395
|
-
page it had been on, and a cookie set before a full navigation was still there
|
|
396
|
-
after it.
|
|
397
|
-
- **A file the chat made goes back to the page.** A file input took the caller's
|
|
398
|
-
own saved image: `Attached <image>.png to #1`, and the page reported
|
|
399
|
-
`<image>.png 17033 bytes`. So a page reaches the chat, the chat reaches the
|
|
400
|
-
folder, and the folder reaches the page again without anyone moving it by hand.
|
|
401
|
-
|
|
402
|
-
## A chat that is not on screen
|
|
403
|
-
|
|
404
|
-
The column shows one chat, so the page for any other chat does not exist while it
|
|
405
|
-
is off screen. A call made from such a chat answers at once:
|
|
406
|
-
|
|
407
|
-
```
|
|
408
|
-
This chat is not the one on screen. Open it, then call again.
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
That answer is the guarantee, not a failure: bringing the column to the caller's
|
|
412
|
-
chat would take the screen from whoever is reading. Switch to the caller's chat
|
|
413
|
-
and the same call opens its page in the column it already belongs to.
|
|
414
|
-
|
|
415
|
-
That answer comes from the page's own next heartbeat, not from a timeout:
|
|
416
|
-
|
|
417
|
-
```
|
|
418
|
-
curl -s http://127.0.0.1:<port>/dsh-inner-browser-use/state | grep -o '"pending":[0-9]*'
|
|
419
|
-
grep -o '"step":"other-chat"' /tmp/dsh-inner-browser-use.log | tail -1
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
## Catching up on a published build
|
|
34
|
+
## 2. A tool opens its own page
|
|
423
35
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
36
|
+
Ask the model `用 sidecar_open 打开 https://www.bing.com`, or call it yourself.
|
|
37
|
+
Expected: the result names the address and title, and this chat's right column
|
|
38
|
+
shows the picture in a tab called **后台浏览器** or **Background browser**.
|
|
39
|
+
`doctor.mjs` now reads `attached, headless`, `viewers 1`, frames climbing.
|
|
40
|
+
|
|
41
|
+
- `"alive":true` with `"viewers":0`: the page half has not mounted the mirror.
|
|
42
|
+
Reload the GUI page; a reload of the plugin alone does not rebuild the tab.
|
|
43
|
+
- `"alive":false` with a non-empty `"error"`: Chrome did not start. Check the
|
|
44
|
+
binary at a listed path, and on macOS that the launch carried
|
|
45
|
+
`--use-mock-keychain`.
|
|
46
|
+
- Frames flat while viewers is 1: the screencast stalled. `sidecar_stop`, then
|
|
47
|
+
`sidecar_open` again.
|
|
48
|
+
- `"pointer":null`: no tool input yet; it fills on the first click or move.
|
|
49
|
+
|
|
50
|
+
## 3. Only the chat that asked
|
|
51
|
+
|
|
52
|
+
Switch to another chat: no mirror there, and the first chat keeps its own. A
|
|
53
|
+
call from the other chat drives the same browser; the tab stays put.
|
|
54
|
+
|
|
55
|
+
A mirror that opens in every chat means the page half is not comparing
|
|
56
|
+
`sidebarRight.mounted` and is running an older bundle: reload the GUI page.
|
|
57
|
+
|
|
58
|
+
## 4. Drive it by hand
|
|
59
|
+
|
|
60
|
+
- **Hover** the search field: your cursor turns into an I-beam, the black arrow
|
|
61
|
+
does not move. An arrow that follows the mouse means the pointer paths are
|
|
62
|
+
crossed.
|
|
63
|
+
- **Type** Chinese with `sidecar_type { "text": "…", "submit": true }`: the
|
|
64
|
+
results page appears and the field holds characters, not boxes.
|
|
65
|
+
- **Backspace** with `sidecar_key { "key": "Backspace" }` in a filled field:
|
|
66
|
+
one character goes. Nothing happening means the key reached the document.
|
|
67
|
+
- **Screenshot** with `sidecar_shot {}`: an absolute path under
|
|
68
|
+
`~/.dsh-inner-browser-use/screenshots`. A black picture means the screencast
|
|
69
|
+
was not running when the frame was taken.
|
|
70
|
+
|
|
71
|
+
## 5. Stop, then open again
|
|
72
|
+
|
|
73
|
+
`sidecar_stop` reads `"alive":false` and leaves the profile alone.
|
|
74
|
+
`sidecar_open https://www.bing.com` brings the same profile back, still signed
|
|
75
|
+
in from the first pass.
|
|
76
|
+
|
|
77
|
+
Signed out after the restart: Chrome came up on another profile, or was killed
|
|
78
|
+
instead of closed. The directory printed by `doctor.mjs` is the one that must
|
|
79
|
+
still hold the cookies.
|
|
80
|
+
|
|
81
|
+
## 6. Upgrading from 0.11.x
|
|
82
|
+
|
|
83
|
+
- The `browser_*` tools are gone; asking for one is an unknown-tool error.
|
|
84
|
+
- `GET /dsh-inner-browser-use/state` answers 404; only the routes in
|
|
85
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#routes) exist.
|
|
86
|
+
- The tab is called **后台浏览器** / **Background browser**; a tab still named
|
|
87
|
+
内置浏览器 means the page half is stale: reload the GUI page.
|
|
88
|
+
- Guest-era logins are not migrated; sign in once in the new profile.
|
|
89
|
+
|
|
90
|
+
## Breadth
|
|
91
|
+
|
|
92
|
+
`node scripts/breadth.mjs` drives a shadow-DOM component, a form (type, select,
|
|
93
|
+
checkbox, submit), a native dialog, an iframe (read, type, click), a click that
|
|
94
|
+
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
|
|
96
|
+
broke. Two of them cover a cross-site frame, which Chrome runs in its own
|
|
97
|
+
process.
|
|
428
98
|
|
|
429
|
-
- `client.v` below `version`: the page is older than the files. One reload
|
|
430
|
-
fixes it; a window that already used up its reloads is not retried, so close
|
|
431
|
-
and reopen the window once.
|
|
432
|
-
- `phase:"app-older"` in the proof log: the page is current but the app process
|
|
433
|
-
started before the update. The app keeps serving the files it loaded, so the
|
|
434
|
-
new host half arrives on the next app start; nothing on screen changes until
|
|
435
|
-
then.
|