dsh-builtin-browser 0.4.6 → 0.4.8

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.en.md +62 -491
  3. package/README.md +44 -470
  4. package/client.js +10 -10
  5. package/cordis.patch.yml +4 -4
  6. package/desktop-bridge/plugin-browser-bridge.js +1 -1
  7. package/docs/README.md +8 -4
  8. package/docs/architecture.md +82 -11
  9. package/docs/development.md +38 -0
  10. package/docs/limitations.md +49 -0
  11. package/docs/tool-reference.md +42 -42
  12. package/docs/user-guide.md +93 -37
  13. package/docs/why-browser.md +3 -3
  14. package/lib/browser/types.d.ts +2 -2
  15. package/lib/browser-electron/bridge-connection.d.ts +7 -1
  16. package/lib/browser-electron/bridge-connection.js +58 -3
  17. package/lib/browser-electron/desktop-bridge-host.d.ts +11 -0
  18. package/lib/browser-electron/desktop-bridge-host.js +21 -1
  19. package/lib/browser-electron/entry.d.ts +2 -2
  20. package/lib/browser-electron/entry.js +186 -103
  21. package/lib/browser-electron/history-store.d.ts +1 -1
  22. package/lib/browser-electron/history-store.js +1 -1
  23. package/lib/browser-electron/host-main.js +3 -3
  24. package/lib/browser-electron/provider.d.ts +32 -8
  25. package/lib/browser-electron/provider.js +97 -16
  26. package/lib/browser-electron/remote-host.js +1 -1
  27. package/lib/browser-electron/settings-store.d.ts +14 -6
  28. package/lib/browser-electron/settings-store.js +29 -9
  29. package/lib/index.d.ts +1 -1
  30. package/lib/index.js +1 -1
  31. package/lib/tool-browser/element-fields.d.ts +4 -4
  32. package/lib/tool-browser/element-fields.js +6 -6
  33. package/lib/tool-browser/index.d.ts +6 -6
  34. package/lib/tool-browser/index.js +196 -139
  35. package/package.json +2 -2
  36. package/scripts/verify-host-compat.mjs +6 -6
  37. package/src/browser/types.ts +2 -2
  38. package/src/browser-electron/bridge-connection.ts +61 -3
  39. package/src/browser-electron/desktop-bridge-host.ts +22 -1
  40. package/src/browser-electron/entry.ts +101 -9
  41. package/src/browser-electron/history-store.ts +1 -1
  42. package/src/browser-electron/host-main.ts +3 -3
  43. package/src/browser-electron/provider.ts +135 -31
  44. package/src/browser-electron/remote-host.ts +1 -1
  45. package/src/browser-electron/settings-store.ts +32 -13
  46. package/src/index.ts +1 -1
  47. package/src/tool-browser/element-fields.ts +6 -6
  48. package/src/tool-browser/index.ts +197 -140
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-builtin-browser",
3
- "version": "0.4.6",
4
- "description": "Shared real browser plugin for DeepSeek Harness: install-and-use — a visible native browser the human can take over, driven by the agent over CDP. DOM-level interaction (React/Vue safe), per-task session isolation, cookie persistence (browser_auth), CAPTCHA detection, batch form fill (browser_fill), authenticated download, history/replay, action restriction",
3
+ "version": "0.4.8",
4
+ "description": "Shared real browser plugin for DeepSeek Harness: install-and-use — a visible native browser the human can take over, driven by the agent over CDP. DOM-level interaction (React/Vue safe), per-task session isolation, cookie persistence (dsh_browser_auth), CAPTCHA detection, batch form fill (dsh_browser_fill), authenticated download, history/replay, action restriction",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -169,20 +169,20 @@ try {
169
169
 
170
170
  const exec = { agent: { id: 'verify-host-compat' } }
171
171
  try {
172
- const value = await registered.get('browser_session').execute({}, exec)
173
- ok('browser_session ran', value !== undefined, JSON.stringify(value).slice(0, 100))
172
+ const value = await registered.get('dsh_browser_session').execute({}, exec)
173
+ ok('dsh_browser_session ran', value !== undefined, JSON.stringify(value).slice(0, 100))
174
174
  } catch (error) {
175
- ok('browser_session ran', false, String(error && error.message))
175
+ ok('dsh_browser_session ran', false, String(error && error.message))
176
176
  }
177
177
 
178
178
  try {
179
- const tool = registered.get('browser_a11y')
179
+ const tool = registered.get('dsh_browser_a11y')
180
180
  const value = await tool.execute({}, exec)
181
181
  const text = JSON.stringify(tool.output.render({}, value))
182
- ok('browser_a11y ran and rendered', value?.nodes?.length === 1, '')
182
+ ok('dsh_browser_a11y ran and rendered', value?.nodes?.length === 1, '')
183
183
  ok('its renderer emits the element selector', text.includes('#probe'), text.slice(0, 120))
184
184
  } catch (error) {
185
- ok('browser_a11y ran', false, String(error && error.message))
185
+ ok('dsh_browser_a11y ran', false, String(error && error.message))
186
186
  }
187
187
 
188
188
  console.log('')
@@ -65,7 +65,7 @@ export interface BrowserScrollRequest {
65
65
 
66
66
  /** Press one named key (Enter, Tab, arrows, …). */
67
67
  export interface BrowserKeyRequest {
68
- /** Key name from the supported set (see the browser_key tool). */
68
+ /** Key name from the supported set (see the dsh_browser_key tool). */
69
69
  readonly key: string
70
70
  }
71
71
 
@@ -361,7 +361,7 @@ export interface BrowserSnapshotElement {
361
361
  /**
362
362
  * True when the element lives inside an (same-origin) iframe: coordinates
363
363
  * are still top-document, but DOM selectors are frame-scoped — reach the
364
- * element via `iframe.contentDocument` in browser_execute.
364
+ * element via `iframe.contentDocument` in dsh_browser_execute.
365
365
  */
366
366
  readonly frame?: boolean
367
367
  }
@@ -51,6 +51,16 @@ const MAX_BUFFER_BYTES = 128 * 1024 * 1024
51
51
  */
52
52
  const REQUEST_ID_FIELD = 'bridgeRequestId'
53
53
 
54
+ /**
55
+ * How long the published endpoint may go un-refreshed before its shell is presumed gone.
56
+ *
57
+ * A live shell rewrites the file every fifteen seconds (its keep-alive), so four missed refreshes
58
+ * is not a slow shell — it is a shell that exited and left its last write behind. The distinction
59
+ * matters because the file is the ONLY thing that says where the bridge is: a tombstone names a
60
+ * port nobody owns, and a client that trusts it dials that port for the rest of its life.
61
+ */
62
+ const ENDPOINT_TTL_MS = 60_000
63
+
54
64
  /**
55
65
  * A reusable connection to the bridge.
56
66
  *
@@ -78,12 +88,19 @@ export class BridgeConnection {
78
88
  private idLessBridge = false
79
89
  /** True once this connection may only have one request in flight at a time. */
80
90
  private serialisable = false
91
+ /** True once the shell behind this connection has been reported gone. */
92
+ private lostNotified = false
81
93
 
82
94
  /**
83
95
  * @param endpoint - the shell's published bridge endpoint.
84
96
  * @param endpointPath - where that endpoint is republished, so a moved port can be followed.
97
+ * @param onLost - called once when this connection concludes the shell behind it is gone.
85
98
  */
86
- constructor(private endpoint: BridgeEndpoint, private readonly endpointPath?: string) {}
99
+ constructor(
100
+ private endpoint: BridgeEndpoint,
101
+ private readonly endpointPath?: string,
102
+ private readonly onLost?: () => void,
103
+ ) {}
87
104
 
88
105
  /**
89
106
  * Re-read the published endpoint, and adopt it if the shell has moved.
@@ -103,6 +120,13 @@ export class BridgeConnection {
103
120
  try {
104
121
  const raw = JSON.parse(readFileSync(this.endpointPath, 'utf8')) as Partial<BridgeEndpoint>
105
122
  if (typeof raw.port !== 'number' || typeof raw.token !== 'string') return false
123
+ // A shell that exited stops refreshing the file, and what it left names a port nobody owns.
124
+ // Trusting it is how a host ends up dialling a dead port for the rest of the process's life
125
+ // — reported as "the browser stopped working and only restarting DSH brought it back".
126
+ if (endpointExpired(raw.updatedAt)) {
127
+ this.notifyLost()
128
+ return false
129
+ }
106
130
  if (raw.port === this.endpoint.port && raw.token === this.endpoint.token) return false
107
131
  this.endpoint = {
108
132
  port: raw.port,
@@ -117,6 +141,17 @@ export class BridgeConnection {
117
141
  }
118
142
  }
119
143
 
144
+ /** Report once that the shell is gone, so the surface can fall back instead of dialling it. */
145
+ private notifyLost(): void {
146
+ if (this.lostNotified) return
147
+ this.lostNotified = true
148
+ try {
149
+ this.onLost?.()
150
+ } catch {
151
+ // A listener must never break the transport: the caller's error is the one that matters.
152
+ }
153
+ }
154
+
120
155
  /**
121
156
  * Send one request, reusing the socket if it is still healthy.
122
157
  *
@@ -138,6 +173,16 @@ export class BridgeConnection {
138
173
  // when the condition could not clear, spun until the process ran out of heap.
139
174
  // Follow the shell if its bridge moved to a new port (see refreshEndpoint).
140
175
  this.refreshEndpoint()
176
+ // The shell behind this connection is gone, and the endpoint file says so. Refusing here is
177
+ // what turns a permanent ECONNREFUSED into one named error the surface can act on — by
178
+ // falling back to the browser it hosts itself, instead of dialling a dead port until DSH is
179
+ // restarted.
180
+ if (this.lostNotified) {
181
+ throw new Error(
182
+ 'dsh-builtin-browser: the desktop shell that backed this browser has exited '
183
+ + '(its published endpoint stopped being refreshed)',
184
+ )
185
+ }
141
186
  let socket = this.ensureSocket()
142
187
  if (this.serialisable && this.queue.length > 0) {
143
188
  this.reset(new Error('dsh-builtin-browser: bridge connection reopened so replies stay attributable'))
@@ -288,8 +333,21 @@ export class BridgeConnection {
288
333
  }
289
334
  }
290
335
 
291
- /** The request id a raw answer line echoes back, when it carries one. */
292
- function echoOf(line: string): number | undefined {
336
+ /**
337
+ * Whether a published endpoint has gone un-refreshed long enough to be a tombstone.
338
+ *
339
+ * A missing or unparseable stamp is NOT expired: an older bridge that never wrote one must keep
340
+ * working, and guessing "stale" from absence would break every shell that predates the field.
341
+ * @param updatedAt - the file's own refresh stamp, when it carries one.
342
+ */
343
+ function endpointExpired(updatedAt: unknown): boolean {
344
+ if (typeof updatedAt !== 'string') return false
345
+ const at = Date.parse(updatedAt)
346
+ if (!Number.isFinite(at)) return false
347
+ return Date.now() - at > ENDPOINT_TTL_MS
348
+ }
349
+
350
+ /** The request id a raw answer line echoes back, when it carries one. */function echoOf(line: string): number | undefined {
293
351
  try {
294
352
  const answer = JSON.parse(line) as Record<string, unknown>
295
353
  const value = answer[REQUEST_ID_FIELD]
@@ -118,13 +118,34 @@ export class DesktopBridgeViewHost implements ElectronBrowserViewHost {
118
118
  * the desktop sidebar is the carrier — by design, not by omission.
119
119
  */
120
120
  private userActionHandler: ((action: BrowserUserAction) => void) | undefined
121
+ /** Told when the shell behind this bridge exits; see `onLost`. */
122
+ private lostListener: (() => void) | undefined
121
123
 
122
124
  /**
123
125
  * @param endpoint - the shell's published bridge endpoint.
124
126
  */
125
127
  private constructor(endpoint: BridgeEndpoint, sessionId?: string) {
126
128
  this.owner = sessionId !== undefined && sessionId !== String.fromCharCode(39,39) ? sessionId : randomUUID()
127
- this.connection = new BridgeConnection(endpoint, bridgeEndpointPath())
129
+ this.connection = new BridgeConnection(endpoint, bridgeEndpointPath(), () => this.reportLost())
130
+ }
131
+
132
+ /**
133
+ * Called by the transport once the shell behind it is gone.
134
+ *
135
+ * Forwarded rather than acted on here: only the entry point owns the swap back to the self-hosted
136
+ * browser, and a host that tried to replace itself would be reaching outside its own contract.
137
+ */
138
+ private reportLost(): void {
139
+ try {
140
+ this.lostListener?.()
141
+ } catch {
142
+ // The listener's failure is not the transport's problem.
143
+ }
144
+ }
145
+
146
+ /** Register the "this carrier is gone" listener (see the interface's `onLost`). */
147
+ onLost(listener: () => void): void {
148
+ this.lostListener = listener
128
149
  }
129
150
 
130
151
  /**
@@ -5,7 +5,7 @@
5
5
  * `ctx.electronViewHost`, that host is used (embedded, human-machine shared
6
6
  * view). Otherwise the plugin self-hosts: it spawns its own Electron child
7
7
  * (`host-main.js`) and drives it over a local TCP JSON-RPC socket, so
8
- * installing the plugin is enough for `browser_*` tools to work on any
8
+ * installing the plugin is enough for `dsh_browser_*` tools to work on any
9
9
  * surface.
10
10
  * @module dsh-browser/browser-electron
11
11
  */
@@ -48,7 +48,7 @@ export interface Config {
48
48
  /** Allow navigation only to HTTP(S) URLs. Default true. */
49
49
  readonly httpOnly?: boolean
50
50
  /**
51
- * Directory `browser_download` save paths must resolve inside (prevents a
51
+ * Directory `dsh_browser_download` save paths must resolve inside (prevents a
52
52
  * prompt-injected agent from writing arbitrary machine paths). Default:
53
53
  * the user's Downloads folder; override to confine downloads elsewhere.
54
54
  */
@@ -112,9 +112,10 @@ export function apply(ctx: Context & { browser: BrowserRuntime }, config: Config
112
112
  const sessionId = readSessionId(ctx)
113
113
 
114
114
  // One settings document per plugin instance: the settings panel writes it, the
115
- // provider reads it live, and both ends agree on the same file.
116
- const settings = new SettingsStore()
117
- installSettingsRoute(ctx, settings)
115
+ // provider reads it live, and both ends agree on the same file. The logger is passed so a
116
+ // discarded value (a mistyped channel, most of all) is reported instead of being silently
117
+ // replaced by the default — "my setting did nothing" and "I misspelled it" must not look alike.
118
+ const settings = new SettingsStore(undefined, message => ctx.logger?.warn?.(message))
118
119
 
119
120
  const providerConfig = {
120
121
  httpOnly: config.httpOnly,
@@ -129,6 +130,17 @@ export function apply(ctx: Context & { browser: BrowserRuntime }, config: Config
129
130
  if (config.viewHost !== undefined) {
130
131
  const external = ctx.browser.registerBrowserProvider(new ElectronBrowserProvider(config.viewHost as ElectronBrowserViewHost, providerConfig))
131
132
  ctx.effect(() => () => { external() })
133
+ // The composition supplied the carrier, so `browser.channel` is not ours to honour on this
134
+ // surface. Saying so is the difference between a setting that cannot apply and a setting that
135
+ // appears not to have worked — the second is what sends people looking for a bug elsewhere.
136
+ const asked = settings.get().browser.channel
137
+ if (asked !== 'sidebar' && asked !== 'bundled') {
138
+ ctx.logger?.warn?.(
139
+ `dsh-builtin-browser: browser.channel is "${asked}", but this surface's carrier was `
140
+ + 'supplied by its composition (the desktop shell), which outranks the setting. Set it to '
141
+ + '"sidebar" to record the intent, or run on a surface that picks its own carrier.',
142
+ )
143
+ }
132
144
  return
133
145
  }
134
146
 
@@ -140,25 +152,69 @@ export function apply(ctx: Context & { browser: BrowserRuntime }, config: Config
140
152
  const selfHosted = new RemoteElectronViewHost(defaultHostMainPath())
141
153
  let unregister = ctx.browser.registerBrowserProvider(new ElectronBrowserProvider(selfHosted, providerConfig))
142
154
  let upgraded = false
155
+ /** The carrier an `adopt` installed, so a later choice can release it. */
156
+ let adoptedCarrier: ElectronBrowserViewHost | undefined
143
157
 
144
158
  ctx.effect(() => () => {
145
159
  unregister()
146
160
  selfHosted.dispose()
147
161
  })
148
162
 
163
+ /**
164
+ * Put the self-hosted browser back after an adopted carrier reports that it is gone.
165
+ *
166
+ * This is the half the startup retries never covered: a bridge that was alive at apply time and
167
+ * exited later left every subsequent call dialling a port nobody owns, because the swap happened
168
+ * once and only in one direction. Reaching here means the shell is gone, so the choice made at
169
+ * startup is no longer a choice — the alternative to falling back is a browser surface that does
170
+ * nothing until DSH is restarted.
171
+ * @param adopted - the carrier that reported the loss, disposed once its replacement is in place.
172
+ */
173
+ const restoreSelfHosted = (adopted: ElectronBrowserViewHost): void => {
174
+ try {
175
+ const restored = new RemoteElectronViewHost(defaultHostMainPath())
176
+ unregister()
177
+ unregister = ctx.browser.registerBrowserProvider(new ElectronBrowserProvider(restored, providerConfig))
178
+ ctx.logger?.warn?.(
179
+ 'dsh-builtin-browser: the desktop shell backed the sidebar browser and has exited, so the '
180
+ + 'browser this plugin hosts itself takes over. Pages from the sidebar are gone with it; '
181
+ + 'open the page again.',
182
+ )
183
+ } catch (error) {
184
+ // Nothing better is available: the adopted carrier is gone and a provider that throws is
185
+ // worse than one that explains.
186
+ ctx.logger?.warn?.(`dsh-builtin-browser: could not fall back to the self-hosted browser: ${String(error)}`)
187
+ } finally {
188
+ adopted.dispose?.()
189
+ }
190
+ }
191
+
149
192
  /**
150
193
  * Swap the provider to another carrier, releasing the previous one.
151
194
  * @param host - the carrier to adopt.
152
195
  * @param note - a one-line description for the log.
153
196
  */
154
197
  const adopt = (host: ElectronBrowserViewHost, note: string): void => {
155
- if (upgraded) { host.dispose?.(); return }
198
+ // This guard used to make the FIRST choice final, disposing anything that arrived later. That
199
+ // is right against two carriers racing in, and exactly wrong for a user changing the setting:
200
+ // `chooseCarrier` runs again on purpose, and a refusal here is indistinguishable from the
201
+ // write having done nothing. The previous carrier is now released by the replacement instead.
156
202
  upgraded = true
157
203
  try {
158
204
  unregister()
159
205
  unregister = ctx.browser.registerBrowserProvider(new ElectronBrowserProvider(host, providerConfig))
160
206
  selfHosted.dispose()
161
207
  ctx.logger?.info?.(`dsh-builtin-browser: ${note}`)
208
+ // A borrowed carrier can lose the process behind it, and this is the only notification that
209
+ // will ever arrive. Registering AFTER the swap succeeded means a listener can never fire
210
+ // against a provider that was never installed.
211
+ host.onLost?.(() => { restoreSelfHosted(host) })
212
+ const previous = adoptedCarrier
213
+ adoptedCarrier = host
214
+ // Released AFTER the replacement is installed, so the surface is never without a provider.
215
+ if (previous !== undefined && previous !== host) {
216
+ try { previous.dispose?.() } catch { /* the replacement is already serving */ }
217
+ }
162
218
  } catch (error) {
163
219
  // Never leave the surface without a provider. The half-built host is disposed FIRST: an
164
220
  // adopt that got as far as constructing it may already hold a socket, a listener or a child
@@ -184,6 +240,16 @@ export function apply(ctx: Context & { browser: BrowserRuntime }, config: Config
184
240
  }
185
241
  }
186
242
 
243
+ /**
244
+ * Choose and install the carrier that `browser.channel` names RIGHT NOW.
245
+ *
246
+ * A function rather than straight-line code because the setting is read live everywhere else:
247
+ * the settings route writes the file and the provider picks the change up on its next call, so a
248
+ * channel that could only be honoured at apply time was the one setting whose write silently did
249
+ * nothing. Re-entrant by design — changing the channel means replacing the carrier, which is
250
+ * exactly what a second call does.
251
+ */
252
+ const chooseCarrier = (): void => {
187
253
  // A browser the user explicitly asked for outranks every automatic choice: the
188
254
  // whole point of the setting is that they know which browser they want. It is
189
255
  // launched with a plugin-owned profile, so their own windows are untouched.
@@ -281,6 +347,14 @@ export function apply(ctx: Context & { browser: BrowserRuntime }, config: Config
281
347
  )
282
348
  }
283
349
  })()
350
+ }
351
+
352
+ chooseCarrier()
353
+
354
+ // Wired AFTER the chooser exists, because the handler re-runs it: a settings write that changes
355
+ // `browser.channel` is a request to switch carriers, and answering `ok` without switching is the
356
+ // one thing a caller cannot tell apart from a write that worked.
357
+ installSettingsRoute(ctx, settings, () => { chooseCarrier() })
284
358
  }
285
359
 
286
360
  /** Route the settings panel reads and writes. */
@@ -353,7 +427,11 @@ interface WebServerHost {
353
427
  * @param ctx - the plugin context.
354
428
  * @param settings - the settings document to expose.
355
429
  */
356
- function installSettingsRoute(ctx: Context, settings: SettingsStore): void {
430
+ function installSettingsRoute(
431
+ ctx: Context,
432
+ settings: SettingsStore,
433
+ onChannelChange?: () => void,
434
+ ): void {
357
435
  const inject = (ctx as unknown as {
358
436
  inject?: (deps: readonly string[], callback: (host: WebServerHost) => void) => void
359
437
  }).inject
@@ -363,7 +441,7 @@ function installSettingsRoute(ctx: Context, settings: SettingsStore): void {
363
441
  host.effect(() => host.webServer.register({
364
442
  kind: 'exact',
365
443
  path: SETTINGS_ROUTE,
366
- handler: (request, response) => handleSettingsRequest(request, response, settings),
444
+ handler: (request, response) => handleSettingsRequest(request, response, settings, onChannelChange),
367
445
  }, 'dsh-builtin-browser: settings'))
368
446
  })
369
447
  } catch {
@@ -377,11 +455,13 @@ function installSettingsRoute(ctx: Context, settings: SettingsStore): void {
377
455
  * @param request - the incoming request.
378
456
  * @param response - the response to write.
379
457
  * @param settings - the settings document.
458
+ * @param onChannelChange - re-runs the carrier choice when this write changed `browser.channel`.
380
459
  */
381
460
  async function handleSettingsRequest(
382
461
  request: IncomingMessage,
383
462
  response: ServerResponse,
384
463
  settings: SettingsStore,
464
+ onChannelChange?: () => void,
385
465
  ): Promise<void> {
386
466
  const json = (status: number, body: unknown): void => {
387
467
  response.writeHead(status, {
@@ -407,7 +487,19 @@ async function handleSettingsRequest(
407
487
  try {
408
488
  const body = await readBody(request)
409
489
  const patch: unknown = body.trim() === '' ? {} : JSON.parse(body)
410
- json(200, { ok: true, settings: settings.update(patch) })
490
+ const before = settings.get().browser.channel
491
+ const updated = settings.update(patch)
492
+ // A changed `browser.channel` is a request to switch carriers, not just a value to store. The
493
+ // choice is re-run here so the write takes effect now — and the response says so, because a
494
+ // caller that cannot tell "applied" from "stored for later" has no way to know what it got,
495
+ // which is exactly how a write that did nothing read as a success.
496
+ const channelChanged = updated.browser.channel !== before
497
+ if (channelChanged) onChannelChange?.()
498
+ json(200, {
499
+ ok: true,
500
+ settings: updated,
501
+ ...channelChanged ? { carrierRechosen: true } : {},
502
+ })
411
503
  } catch (error) {
412
504
  json(400, { ok: false, error: `invalid settings patch: ${String(error)}` })
413
505
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Persistent browsing history: which pages the shared browser actually visited,
3
- * kept independently from any session's operation log (`browser_history`) and
3
+ * kept independently from any session's operation log (`dsh_browser_history`) and
4
4
  * from the browser process's lifetime.
5
5
  *
6
6
  * It lives beside the browser profile (`$DSH_HOME/dsh-builtin-browser-host/
@@ -41,7 +41,7 @@ import { dirname, join } from 'node:path'
41
41
  // several Electron instances sharing Roaming\Electron fight over the GPU
42
42
  // cache/session locks, which can leave the window without a display surface
43
43
  // (capturePage then fails). A dedicated userData also persists cookies across
44
- // host restarts (on top of browser_auth). Must run before app is ready.
44
+ // host restarts (on top of dsh_browser_auth). Must run before app is ready.
45
45
  try {
46
46
  const base = process.env.DSH_HOME ?? app.getPath('appData')
47
47
  app.setPath('userData', join(base, 'dsh-builtin-browser-host'))
@@ -769,7 +769,7 @@ async function handle(op: string, msg: { id: number; viewId?: string; windowId?:
769
769
  const entry = win?.views.get(viewId)
770
770
  if (win === undefined || entry === undefined) throw new Error(`focus: unknown view ${viewId}`)
771
771
  // Keys go to the view that holds web focus. A view that was never
772
- // clicked holds none, so the FIRST browser_key of a session was dropped
772
+ // clicked holds none, so the FIRST dsh_browser_key of a session was dropped
773
773
  // by the renderer while CDP still answered the dispatch with success —
774
774
  // and `document.hasFocus()` inside the page even read true.
775
775
  const contents = entry.webContentsView.webContents
@@ -881,7 +881,7 @@ async function handle(op: string, msg: { id: number; viewId?: string; windowId?:
881
881
  throw new Error('capture produced no image (view not painted)')
882
882
  }
883
883
  // The measured size travels with the image. Without it the native path — which is the
884
- // default self-hosted one — could never answer `browser_screenshot`'s width/height,
884
+ // default self-hosted one — could never answer `dsh_browser_screenshot`'s width/height,
885
885
  // because the seam's only channel was {base64, mime} and the provider had nothing to
886
886
  // forward. The caller's `maxWidth`/`maxHeight` are applied here, so this is the only
887
887
  // place that knows what came back.