dsh-inner-browser-use 0.34.98 → 0.34.99

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 (4) hide show
  1. package/client.js +33 -11
  2. package/index.js +35 -4
  3. package/package.json +1 -1
  4. package/sidecar.js +19 -2
package/client.js CHANGED
@@ -73,7 +73,7 @@ window.__ModuleLoader__.load({
73
73
  '.dshib-side-win[data-on="1"]{background:var(--dsw-alias-bg-subtle);color:var(--dsw-alias-label-primary)}',
74
74
  '.dshib-side-win > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
75
75
  '.dshib-side-stage{flex:1 1 auto;position:relative;min-width:0;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--dsw-alias-bg-base);outline:none;cursor:default}',
76
- '.dshib-side-frame{display:block;user-select:none;-webkit-user-drag:none;cursor:default}',
76
+ '.dshib-side-frame{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;user-select:none;-webkit-user-drag:none;cursor:default}',
77
77
  '.dshib-side-ime{position:absolute;width:1px;height:1px;padding:0;margin:0;border:0;resize:none;overflow:hidden;opacity:0;pointer-events:none}',
78
78
  /* The black arrow is the agent's pointer, not the person's cursor: both are
79
79
  on screen at once, and moving the mouse never takes the arrow over. */
@@ -645,9 +645,27 @@ window.__ModuleLoader__.load({
645
645
  .catch(() => {})
646
646
  }
647
647
 
648
- const scale = size.w > 0 && size.h > 0 ? Math.min(size.w / view.w, size.h / view.h) : 0
649
- const padX = (size.w - view.w * scale) / 2
650
- const padY = (size.h - view.h * scale) / 2
648
+ /* Where the page actually got drawn inside the column: the frame fills
649
+ the stage and the picture sits inside it, so the answer comes from the
650
+ element that is on screen rather than from the last size remembered.
651
+ A stale measure can then never shrink the picture to a thumbnail. */
652
+ const frameEl = React.useRef(null)
653
+ const picture = () => {
654
+ const at = frameEl.current
655
+ const areaEl = stage.current
656
+ if (at === null || at === undefined || areaEl === null || areaEl === undefined) return null
657
+ if (!(view.w > 0) || !(view.h > 0)) return null
658
+ const a = at.getBoundingClientRect()
659
+ const b = areaEl.getBoundingClientRect()
660
+ if (!(a.width > 1) || !(a.height > 1)) return null
661
+ const scale = Math.min(a.width / view.w, a.height / view.h)
662
+ if (!(scale > 0)) return null
663
+ return {
664
+ scale,
665
+ x: a.left - b.left + (a.width - view.w * scale) / 2,
666
+ y: a.top - b.top + (a.height - view.h * scale) / 2,
667
+ }
668
+ }
651
669
 
652
670
  /* The column tells the page how big it is, a beat after the resize stops:
653
671
  a page that relayouts on every pointer step would never settle. */
@@ -729,10 +747,12 @@ window.__ModuleLoader__.load({
729
747
  }
730
748
  const toView = (event) => {
731
749
  const el = stage.current
732
- if (el === null || scale <= 0) return null
750
+ if (el === null) return null
751
+ const spot = picture()
752
+ if (spot === null) return null
733
753
  const box = el.getBoundingClientRect()
734
- const x = (event.clientX - box.left - padX) / scale
735
- const y = (event.clientY - box.top - padY) / scale
754
+ const x = (event.clientX - box.left - spot.x) / spot.scale
755
+ const y = (event.clientY - box.top - spot.y) / spot.scale
736
756
  if (x < 0 || y < 0 || x > view.w || y > view.h) return null
737
757
  return { x: Math.round(x), y: Math.round(y) }
738
758
  }
@@ -828,6 +848,7 @@ window.__ModuleLoader__.load({
828
848
  h('div', { className: 'dshib-side-note' }, L('side.foreign')))
829
849
  }
830
850
  const pointer = person === true || hand === true ? null : ptr
851
+ const spot = pointer === null ? null : (picture() ?? { scale: 1, x: 0, y: 0 })
831
852
  const noteText = phase === 'failed' ? note || L('side.failed') : phase === 'live' ? L('side.empty') : L('side.connecting')
832
853
  return h('div', { className: 'dshib-side' },
833
854
  h('div', { className: 'dshib-side-bar' },
@@ -894,7 +915,7 @@ window.__ModuleLoader__.load({
894
915
  tabIndex: 0,
895
916
  spellCheck: false,
896
917
  'aria-label': L('side.type'),
897
- style: pointer === null ? undefined : { left: `${padX + pointer.x * scale}px`, top: `${padY + pointer.y * scale}px` },
918
+ style: pointer === null || spot === null ? undefined : { left: `${spot.x + pointer.x * spot.scale}px`, top: `${spot.y + pointer.y * spot.scale}px` },
898
919
  onCompositionStart: () => {
899
920
  composing.current = true
900
921
  },
@@ -913,9 +934,10 @@ window.__ModuleLoader__.load({
913
934
  }),
914
935
  frame === null ? null : h('img', {
915
936
  className: 'dshib-side-frame',
937
+ ref: frameEl,
916
938
  src: `data:image/jpeg;base64,${frame.jpeg}`,
917
- width: Math.max(1, Math.round(view.w * scale)),
918
- height: Math.max(1, Math.round(view.h * scale)),
939
+ width: Math.max(1, Math.round(view.w)),
940
+ height: Math.max(1, Math.round(view.h)),
919
941
  alt: '', // ui-copy-ignore
920
942
  draggable: false,
921
943
  }),
@@ -923,7 +945,7 @@ window.__ModuleLoader__.load({
923
945
  className: 'dshib-side-ptr',
924
946
  src: SIDE_POINTER, // ui-copy-ignore
925
947
  alt: '', // ui-copy-ignore
926
- style: { left: `${padX + pointer.x * scale}px`, top: `${padY + pointer.y * scale}px` },
948
+ style: spot === null ? undefined : { left: `${spot.x + pointer.x * spot.scale}px`, top: `${spot.y + pointer.y * spot.scale}px` },
927
949
  })))
928
950
  }
929
951
  },
package/index.js CHANGED
@@ -20,7 +20,7 @@
20
20
  * learns no port and no secret. // ui-copy-ignore
21
21
  */
22
22
 
23
- import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
23
+ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
24
24
  import { homedir } from 'node:os'
25
25
  import { join } from 'node:path'
26
26
  import { Sidecar } from './sidecar.js?v=1'
@@ -50,6 +50,27 @@ function writeMirror(value) {
50
50
  }
51
51
  }
52
52
 
53
+ /** The note stops growing: past this size the oldest half goes, so a long-lived run never fills the disk. */
54
+ const NOTES_MAX = 256 * 1024
55
+ const NOTES_KEEP = 128 * 1024
56
+
57
+ /**
58
+ * Add one line to the mirror's own note, keeping the file bounded.
59
+ * @param {string} line - what happened, one line.
60
+ */
61
+ export function note(line) {
62
+ const file = join(STATE_DIR, 'asks.log')
63
+ try {
64
+ mkdirSync(STATE_DIR, { recursive: true })
65
+ appendFileSync(file, `${new Date().toISOString()} ${line}\n`)
66
+ if (statSync(file).size <= NOTES_MAX) return
67
+ const whole = readFileSync(file, 'utf8')
68
+ writeFileSync(file, whole.slice(whole.length - NOTES_KEEP).replace(/^[^\n]*\n/, ''))
69
+ } catch {
70
+ /* a note that cannot be written must not take the mirror down */
71
+ }
72
+ }
73
+
53
74
  /** Route prefix owned by this plugin. Never under `/api`, which is the Host's own channel. */ // ui-copy-ignore
54
75
  export const ROUTE = '/dsh-inner-browser-use'
55
76
 
@@ -336,7 +357,7 @@ export function apply(ctx) {
336
357
  if (due !== true) return
337
358
  last = Date.now()
338
359
  bridge.want(owner, Date.now(), 'keepalive')
339
- appendFileSync(join(STATE_DIR, 'asks.log'), `${new Date().toISOString()} asked ${owner.slice(-8)} to draw the mirror again\n`)
360
+ note(`asked ${owner.slice(-8)} to draw the mirror again`)
340
361
  } catch {}
341
362
  }, 4000)
342
363
  timer.unref?.()
@@ -511,7 +532,7 @@ export function apply(ctx) {
511
532
  if (Date.now() - before > 2000) {
512
533
  lastAsk.set(chat, Date.now())
513
534
  try {
514
- appendFileSync(join(STATE_DIR, 'asks.log'), `${new Date().toISOString()} ${chat.slice(-8)} alive=${String(said?.alive === true)} viewers=${String(said?.viewers ?? 0)}\n`)
535
+ note(`${chat.slice(-8)} alive=${String(said?.alive === true)} viewers=${String(said?.viewers ?? 0)}`)
515
536
  } catch {}
516
537
  }
517
538
  }
@@ -715,7 +736,17 @@ export function apply(ctx) {
715
736
  const body = await readBody(req).then((raw) => JSON.parse(raw || '{}')).catch(() => ({}))
716
737
  const who = acting(body)
717
738
  if (who === '') return
718
- json(200, { ok: true, forgot: bridge.forget(who) })
739
+ const forgot = bridge.forget(who)
740
+ /* Nobody is watching a picture the person just closed: the browser itself
741
+ is put away a minute later rather than a quarter of an hour, unless an
742
+ agent asks for it again in the meantime. */
743
+ try {
744
+ if (sidecar.watched() === 0) {
745
+ sidecar.touch(REST_MS)
746
+ note('said ' + who.slice(-8) + ' closed the mirror - browser rests in a minute')
747
+ }
748
+ } catch { /* a browser that never started needs no rest timer */ }
749
+ json(200, { ok: true, forgot })
719
750
  return
720
751
  }
721
752
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-inner-browser-use",
3
- "version": "0.34.98",
3
+ "version": "0.34.99",
4
4
  "description": "An agent-owned Chromium sidecar mirrored into the DeepSeek Harness right column: the agent drives it, the person watches and can take the mouse, and logins survive restarts.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/sidecar.js CHANGED
@@ -95,6 +95,12 @@ export function nextQuality(current, base64) {
95
95
 
96
96
  /** Idle lifetime, so a forgotten sidecar does not sit on a login forever. */
97
97
  const IDLE_MS = 15 * 60_000
98
+ /* A pane the person closed is a stronger signal than a pane nobody touched: the
99
+ browser is put away a minute later unless someone asks for it again. A whole
100
+ Chrome with its renderers is a few hundred megabytes, and leaving it up for a
101
+ quarter of an hour after a deliberate close is exactly the cost they did not
102
+ want. */
103
+ const REST_MS = 60_000
98
104
  /* One browser serves every chat, but a chat that is not on screen need not keep
99
105
  a full renderer alive for ever: past this many chats the oldest unwatched tab
100
106
  is closed, and its address brings it back on the next visit. */
@@ -2836,7 +2842,18 @@ export class Sidecar {
2836
2842
  }
2837
2843
  }
2838
2844
 
2839
- touch() {
2845
+ /** @returns {number} how many panes are watching a picture right now. */
2846
+ watched() {
2847
+ let n = this.page?.frames?.size ?? 0
2848
+ for (const page of this.pages?.values() ?? []) n += page.frames?.size ?? 0
2849
+ return n
2850
+ }
2851
+
2852
+ /**
2853
+ * Starts the idle clock again.
2854
+ * @param {number} [ms] - how long a browser nobody watches may stay up.
2855
+ */
2856
+ touch(ms = IDLE_MS) {
2840
2857
  if (this.idle !== null) clearTimeout(this.idle)
2841
2858
  this.idle = setTimeout(() => {
2842
2859
  this.idle = null
@@ -2849,7 +2866,7 @@ export class Sidecar {
2849
2866
  return
2850
2867
  }
2851
2868
  void this.shutdown()
2852
- }, IDLE_MS)
2869
+ }, ms)
2853
2870
  this.idle.unref?.()
2854
2871
  }
2855
2872