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 +63 -2
- package/index.js +43 -1
- package/package.json +1 -1
- package/sidecar.js +22 -0
- package/tools.js +1 -1
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
|
-
|
|
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.
|
|
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: {
|