dsh-inner-browser-use 0.34.95 → 0.34.96

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/client.js CHANGED
@@ -68,6 +68,10 @@ window.__ModuleLoader__.load({
68
68
  '.dshib-side-key:hover{background:var(--dsw-alias-bg-subtle);color:var(--dsw-alias-label-primary)}',
69
69
  '.dshib-side-url{flex:1 1 auto;min-width:0;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-bg-subtle);padding:4px 8px;font:inherit;font-size:12px;color:var(--dsw-alias-label-secondary);outline:none}',
70
70
  '.dshib-side-url:focus{border-color:var(--dsw-alias-border-strong);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base)}',
71
+ '.dshib-side-wins{flex:none;display:flex;gap:4px;padding:4px 6px;overflow-x:auto;border-bottom:1px solid var(--dsw-alias-separator)}',
72
+ '.dshib-side-win{flex:0 1 auto;min-width:0;max-width:180px;display:inline-flex;align-items:center;gap:4px;border:0;border-radius:6px;padding:2px 8px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;cursor:pointer}',
73
+ '.dshib-side-win[data-on="1"]{background:var(--dsw-alias-bg-subtle);color:var(--dsw-alias-label-primary)}',
74
+ '.dshib-side-win > span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
71
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}',
72
76
  '.dshib-side-frame{display:block;user-select:none;-webkit-user-drag:none;cursor:default}',
73
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}',
@@ -115,7 +119,7 @@ window.__ModuleLoader__.load({
115
119
  return ''
116
120
  }
117
121
  }
118
- const closeHere = (sessionId) => {
122
+ const closeHere = (sessionId, byPerson = false) => {
119
123
  /* A tab the person closed is not a tab they want back on return. */
120
124
  try {
121
125
  void fetch(`${ROUTE}/sidecar/forget?session=${encodeURIComponent(sessionId)}`, { method: 'POST' }) // ui-copy-ignore
@@ -132,6 +136,11 @@ window.__ModuleLoader__.load({
132
136
  const kind = String(record?.kind ?? record?.record?.kind ?? '')
133
137
  const id = String(record?.id ?? record?.record?.id ?? '')
134
138
  if (kind !== SIDE_KIND && id !== SIDE_ID) continue
139
+ /* The person's close arrives from the tab strip: the host is
140
+ already taking that tab down, and closing it again here races
141
+ the host and can leave the tab half closed. Forgetting the ask
142
+ is what this path owes. */
143
+ if (byPerson) continue
135
144
  try {
136
145
  if (typeof record?.close === 'function') record.close()
137
146
  else if (typeof surface?.closeIn === 'function') surface.closeIn(sessionId, id)
@@ -172,7 +181,7 @@ window.__ModuleLoader__.load({
172
181
  the keepalive put the mirror back a few seconds later — a tab
173
182
  the person closed that would not stay closed. Every close path
174
183
  ends here. */
175
- onClose: (_tab, where) => closeHere(String(where?.sessionId ?? mountedOf())),
184
+ onClose: (_tab, where) => closeHere(String(where?.sessionId ?? mountedOf()), true),
176
185
  })))
177
186
  disposers.push(scope.slots.inject(SLOT, () => scope.slots.register({
178
187
  name: SLOT, // ui-copy-ignore
@@ -349,6 +358,9 @@ window.__ModuleLoader__.load({
349
358
  const [draft, setDraft] = React.useState('')
350
359
  const [editing, setEditing] = React.useState(false)
351
360
  const [size, setSize] = React.useState({ w: 0, h: 0 })
361
+ /* The browser's own windows, this chat's only: a second window a site
362
+ opened is a row here, not a second tab in the column. */
363
+ const [wins, setWins] = React.useState([])
352
364
  const stage = React.useRef(null)
353
365
  /* Which chat owns the mirror, as the sidecar host records it. // ui-copy-ignore
354
366
  Empty until
@@ -521,6 +533,44 @@ window.__ModuleLoader__.load({
521
533
  return () => observer.disconnect()
522
534
  }, [])
523
535
 
536
+ /* The picture carries the page, not the window list: ask for the list on
537
+ a slow clock, and after a switch. */
538
+ const [winsTick, setWinsTick] = React.useState(0)
539
+ React.useEffect(() => {
540
+ if (mine !== true) return undefined
541
+ let alive = true
542
+ const chat = String(props.sessionId ?? '')
543
+ const load = () => {
544
+ fetch(`${ROUTE}/sidecar/tabs?session=${encodeURIComponent(chat)}`) // ui-copy-ignore
545
+ .then((reply) => (reply.ok === true ? reply.json() : null))
546
+ .then((info) => {
547
+ if (alive === false || info?.ok !== true || Array.isArray(info.tabs) !== true) return
548
+ setWins(info.tabs)
549
+ })
550
+ .catch(() => {})
551
+ }
552
+ load()
553
+ const clock = setInterval(load, 2500)
554
+ return () => {
555
+ alive = false
556
+ clearInterval(clock)
557
+ }
558
+ }, [mine, props.sessionId, winsTick])
559
+
560
+ const switchWin = (at) => {
561
+ fetch(`${ROUTE}/sidecar/tabs`, { // ui-copy-ignore
562
+ method: 'POST',
563
+ headers: { 'content-type': 'application/json' },
564
+ body: JSON.stringify({ session: String(props.sessionId ?? ''), op: 'use', index: at }), // ui-copy-ignore
565
+ })
566
+ .then((reply) => (reply.ok === true ? reply.json() : null))
567
+ .then((info) => {
568
+ if (info?.ok === true && Array.isArray(info.tabs) === true) setWins(info.tabs)
569
+ setWinsTick((n) => n + 1)
570
+ })
571
+ .catch(() => {})
572
+ }
573
+
524
574
  const scale = size.w > 0 && size.h > 0 ? Math.min(size.w / view.w, size.h / view.h) : 0
525
575
  const padX = (size.w - view.w * scale) / 2
526
576
  const padY = (size.h - view.h * scale) / 2
@@ -716,6 +766,17 @@ window.__ModuleLoader__.load({
716
766
  goto()
717
767
  },
718
768
  })),
769
+ ...(wins.length > 1
770
+ ? [h('div', { className: 'dshib-side-wins' },
771
+ ...wins.map((win) => h('button', {
772
+ key: `${win.index}:${win.url}`,
773
+ className: 'dshib-side-win',
774
+ type: 'button', // ui-copy-ignore
775
+ 'data-on': win.current === true ? '1' : '0',
776
+ title: win.url,
777
+ onClick: () => switchWin(win.index),
778
+ }, h('span', null, String(win.title ?? '') || String(win.url ?? '')))))]
779
+ : []),
719
780
  h('div', {
720
781
  className: 'dshib-side-stage',
721
782
  ref: stage,
package/index.js CHANGED
@@ -562,7 +562,10 @@ export function apply(ctx) {
562
562
  return
563
563
  }
564
564
  try {
565
- const page = sidecar.page
565
+ /* The shape belongs to the page this chat is actually looking at: a
566
+ chat driving a tab of its own must not resize the browser's base
567
+ page and keep a letterboxed picture. */
568
+ const page = sidecar.pageFor(actor) ?? sidecar.page
566
569
  if (page === null || page === undefined) {
567
570
  json(409, { ok: false, text: 'the browser is not open' }) // ui-copy-ignore
568
571
  return
@@ -661,6 +664,44 @@ export function apply(ctx) {
661
664
  return
662
665
  }
663
666
 
667
+ if (path === '/sidecar/tabs' && req.method === 'GET') {
668
+ /* This chat's windows only: another chat's pages never enter this list. */
669
+ const who = acting({ session: url.searchParams.get('session') })
670
+ if (who === '') return
671
+ try {
672
+ const tabs = await sidecar.windowsFor(who)
673
+ json(200, { ok: true, tabs, ...(url.searchParams.get('session') === '' ? { session: who } : {}) })
674
+ } catch (error) {
675
+ json(500, { ok: false, text: String(error?.message ?? error) })
676
+ }
677
+ return
678
+ }
679
+
680
+ if (path === '/sidecar/tabs' && req.method === 'POST') {
681
+ let body
682
+ try {
683
+ body = JSON.parse(await readBody(req))
684
+ } catch {
685
+ json(400, { ok: false, text: 'bad frame' })
686
+ return
687
+ }
688
+ const actor = acting(body)
689
+ if (actor === '') return
690
+ try {
691
+ const at = Number(body.index ?? -1)
692
+ if (body.op === 'close') {
693
+ await sidecar.closeTab(at, actor)
694
+ } else {
695
+ await sidecar.useTab(at, actor)
696
+ }
697
+ const tabs = await sidecar.windowsFor(actor)
698
+ json(200, { ok: true, tabs })
699
+ } catch (error) {
700
+ json(500, { ok: false, text: String(error?.message ?? error) })
701
+ }
702
+ return
703
+ }
704
+
664
705
  if (path === '/sidecar/forget' && req.method === 'POST') {
665
706
  /* Closing the tab is an answer: coming back should not reopen it. Only the
666
707
  chat that holds the mirror may withdraw its own ask. */
@@ -789,6 +830,7 @@ export function apply(ctx) {
789
830
  '',
790
831
  '只有人明确说要看网页/用浏览器时才开始:「用网页端 / 在浏览器里 / 打开某个网站 / 上 ChatGPT 生成 / 帮我在这个页面点…」。人只给了链接、没说要用浏览器时,先用 `web_fetch` 或 envoy 读;读不到(登录墙、空壳、要交互)就说明原因再问一句,不要自己开。',
791
832
  '不要用它:读公开文章、单个链接正文、文档、搜索结果摘要、GitHub 仓库或 PR 页面——这些能用 `web_fetch` / envoy 读就不用浏览器;一打开就是一个后台 Chrome 进程加一个页签,会话多了很占内存。',
833
+ '也不要用它做连通性、可达性、服务是否起来的检查:那是一个 HTTP 请求的事,`web_fetch` 或 `curl` 就够,开浏览器只会多一个进程和一栏画面。只有「这一步必须在真实浏览器里点/填/登录」才轮到它。',
792
834
  '人点名之后不问、不另起外部 Chrome;画面留在本会话右栏,做完保留,不主动关。',
793
835
  '先接着用已有的页:`browser_view`(拿编号控件与可见文字)→ `browser_click` / `browser_type` → 再 `browser_view` 直到出现要的结果;页面要等就重复读,不 sleep。`browser_open` 只用于没有页、要换新地址、或要明确刷新;同一个地址不要反复重开。',
794
836
  '页面很长、只想确认某句话/按钮在哪时用 `browser_find`,别整页读。要读长文(文章、文档、issue、论坛帖)用 `browser_read`:它回 Markdown,只花几十分之一的 token,链接可点;要动手点、填表才用 `browser_view` 拿编号。',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-inner-browser-use",
3
- "version": "0.34.95",
3
+ "version": "0.34.96",
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
@@ -2603,6 +2603,28 @@ export class Sidecar {
2603
2603
  return page === null ? [] : page.tabs()
2604
2604
  }
2605
2605
 
2606
+ /**
2607
+ * The windows this chat may see: the browser's own pages minus the ones other
2608
+ * chats are driving. A chat's column shows its own windows, never a
2609
+ * neighbour's.
2610
+ * @param {string} session - chat asking.
2611
+ * @returns {Promise<Array<{title: string, url: string, current: boolean, index: number}>>} its windows.
2612
+ */
2613
+ async windowsFor(session) {
2614
+ const list = await this.tabs(session)
2615
+ const page = this.pageFor(session) ?? this.page
2616
+ const foreign = new Set(typeof page?.siblings === 'function' ? page.siblings().map(String) : [])
2617
+ const shown = []
2618
+ for (let at = 0; at < list.length; at += 1) {
2619
+ const row = list[at]
2620
+ if (foreign.has(String(row.targetId))) continue
2621
+ /* The index is the browser's own row, not this shorter list's: switching
2622
+ goes back through `tabs()` unfiltered. */
2623
+ shown.push({ title: String(row.title ?? ''), url: String(row.url ?? ''), current: row.current === true, index: at })
2624
+ }
2625
+ return shown
2626
+ }
2627
+
2606
2628
  /**
2607
2629
  * Move the mirror to another tab, leaving the old one open where it was.
2608
2630
  * @param {number} index - a row from `tabs()`.
package/tools.js CHANGED
@@ -1776,7 +1776,7 @@ export function sidecarTools(sidecar, refs, host = {}) { // ui-copy-ignore
1776
1776
  return [
1777
1777
  {
1778
1778
  name: 'browser_open', // ui-copy-ignore
1779
- description: 'Start (or reuse) the background browser — a real Chrome with its own persistent profile — and open a URL in it. It is not the host\'s own browser tab: this Chrome runs on its own and only its picture is mirrored into this chat\'s right column, so it keeps working while the person looks elsewhere. Logins survive restarts.',
1779
+ description: 'Start (or reuse) the background browser — a real Chrome with its own persistent profile — and open a URL in it. It is not the host\'s own browser tab: this Chrome runs on its own and only its picture is mirrored into this chat\'s right column, so it keeps working while the person looks elsewhere. Logins survive restarts. Use it only when the person explicitly asked for a browser this turn; reading a URL, a document or checking whether a site answers is `web_fetch`\'s job, not this one.',
1780
1780
  parameters: {
1781
1781
  type: 'object', // ui-copy-ignore
1782
1782
  properties: {