dsh-site-bridge 0.6.2 → 0.6.4

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 CHANGED
@@ -124,7 +124,7 @@ The popup UI follows the browser language: English, Russian and Simplified Chine
124
124
 
125
125
  ```text
126
126
  site_status → bridge state, pairing path, tabs
127
- site_tabs {action:"list"} → tabs; claim/use/release/open/focus
127
+ site_tabs {action:"list"} → tabs; claim/use/release/open/close/focus
128
128
  site_network {action:"start"} → begin recording, then act on the page
129
129
  site_capture {screenshot:true} → write the bundle into the workspace
130
130
  site_snapshot {limit:40} → interactive elements with stable selectors
@@ -145,6 +145,13 @@ A typical loop for writing a page-embedded extension:
145
145
  the browser's cookies.
146
146
  5. Iterate on the selectors the capture marked as fragile.
147
147
 
148
+ ### Popup and tools are the same surface
149
+
150
+ The popup is a manual control for the person at the keyboard: attach a tab, detach it, start recording,
151
+ pick an element. The agent can do all of that through the tools, including `site_tabs {action:"claim"}`
152
+ for any tab it sees in `list`. The attach button is a human gesture, not a permission gate — the
153
+ bridge token is the only thing that decides who may drive the browser.
154
+
148
155
  ## Architecture
149
156
 
150
157
  ```
package/README.ru.md CHANGED
@@ -124,7 +124,7 @@ Chrome покажет плашку «началась отладка этого
124
124
 
125
125
  ```text
126
126
  site_status → состояние моста, путь к спариванию, вкладки
127
- site_tabs {action:"list"} → вкладки; claim/use/release/open/focus
127
+ site_tabs {action:"list"} → вкладки; claim/use/release/open/close/focus
128
128
  site_network {action:"start"} → начать запись, затем действовать на странице
129
129
  site_capture {screenshot:true} → записать пакет снимка в рабочее пространство
130
130
  site_snapshot {limit:40} → интерактивные элементы с устойчивыми селекторами
@@ -145,6 +145,13 @@ site_inject {js:"..."} → выполнить ваш скрипт в с
145
145
  браузера.
146
146
  5. Доработать селекторы, которые снимок пометил как хрупкие.
147
147
 
148
+ ### Попап и инструменты — одна и та же поверхность
149
+
150
+ Попап — ручное управление для человека за клавиатурой: подключить вкладку, отключить, включить
151
+ запись, выбрать элемент. Агент умеет то же самое через инструменты, включая
152
+ `site_tabs {action:"claim"}` для любой вкладки из `list`. Кнопка подключения — жест человека, а не
153
+ разрешение: доступ решает только токен моста.
154
+
148
155
  ## Архитектура
149
156
 
150
157
  ```
package/README.zh-CN.md CHANGED
@@ -103,7 +103,7 @@ Chrome 会显示「已开始调试此浏览器」提示条:这正是全部功
103
103
 
104
104
  ```text
105
105
  site_status → 网桥状态、配对路径、标签页
106
- site_tabs {action:"list"} → 标签页;claim/use/release/open/focus
106
+ site_tabs {action:"list"} → 标签页;claim/use/release/open/close/focus
107
107
  site_network {action:"start"} → 开始录制,然后在页面上操作
108
108
  site_capture {screenshot:true} → 把快照包写入工作区
109
109
  site_snapshot {limit:40} → 带稳定选择器的可交互元素
@@ -123,6 +123,12 @@ site_inject {js:"..."} → 在页面里执行你的脚本以验证
123
123
  4. 用 `site_inject` 注入脚本体 → 确认锚点能找到、BFF 调用能带着浏览器 Cookie 通过。
124
124
  5. 针对快照标记为脆弱的选择器继续调整。
125
125
 
126
+ ### 弹出窗口与工具是同一条通道
127
+
128
+ 弹出窗口是给键盘前的人的双手控制:连接标签页、断开、开始录制、选择元素。智能体通过工具可以做同样
129
+ 的事,包括对 `list` 中任意标签页执行 `site_tabs {action:"claim"}`。连接按钮是人的操作,而不是权限
130
+ 闸门——能否驱动浏览器只由网桥令牌决定。
131
+
126
132
  ## 架构
127
133
 
128
134
  ```
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "default_locale": "en",
4
4
  "name": "__MSG_appName__",
5
- "version": "0.6.2",
5
+ "version": "0.6.4",
6
6
  "description": "__MSG_appDesc__",
7
7
  "minimum_chrome_version": "116",
8
8
  "permissions": [
package/extension/sw.js CHANGED
@@ -294,6 +294,25 @@ async function claimTab(tabId) {
294
294
  return attach(id)
295
295
  }
296
296
 
297
+ chrome.tabs.onUpdated.addListener((tabId, info, tab) => {
298
+ const session = state.sessions.get(tabId)
299
+ if (!session) return
300
+ const next = { id: tab.id, url: tab.url, title: tab.title }
301
+ if (next.title === session.target.title && next.url === session.target.url) return
302
+ session.target = next
303
+ void evt('target', { tab: next, tabId })
304
+ })
305
+
306
+ chrome.tabs.onRemoved.addListener((tabId) => {
307
+ if (!state.sessions.has(tabId)) return
308
+ state.sessions.delete(tabId)
309
+ if (state.activeTabId === tabId) {
310
+ state.activeTabId = state.sessions.size > 0 ? [...state.sessions.keys()][0] : null
311
+ }
312
+ void evt('target', { tab: null, tabId })
313
+ void evt('active', { tabId: state.activeTabId })
314
+ })
315
+
297
316
  chrome.debugger.onDetach.addListener((source, reason) => {
298
317
  const session = state.sessions.get(source.tabId)
299
318
  if (!session) return
@@ -1512,7 +1531,21 @@ async function dispatch(op, args) {
1512
1531
  case 'tab.open': {
1513
1532
  const tab = await chrome.tabs.create({ url: String(args.url ?? 'about:blank'), active: args.active !== false })
1514
1533
  await attach(tab.id)
1515
- return { target: state.sessions.get(tab.id)?.target ?? { id: tab.id, url: tab.url, title: tab.title } }
1534
+ // A fresh tab has no title yet: wait for the load so the caller gets a
1535
+ // target it can show, instead of an empty string.
1536
+ const deadline = Date.now() + Math.min(Number(args.waitMs ?? 10000), 30000)
1537
+ let info = tab
1538
+ while (Date.now() < deadline) {
1539
+ info = await chrome.tabs.get(tab.id).catch(() => info)
1540
+ if (info.status === 'complete' && info.title) break
1541
+ await sleep(150)
1542
+ }
1543
+ const session = state.sessions.get(tab.id)
1544
+ if (session) {
1545
+ session.target = { id: info.id, url: info.url, title: info.title }
1546
+ await evt('target', { tab: session.target, tabId: tab.id })
1547
+ }
1548
+ return { target: session?.target ?? { id: tab.id, url: tab.url, title: tab.title } }
1516
1549
  }
1517
1550
  case 'tab.focus': {
1518
1551
  const session = requireSession(args.tabId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-site-bridge",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Chrome/Edge MV3 bridge for DeepSeek Harness: capture page markup (DOM, styles, selectors) and BFF traffic (xhr/fetch with headers and bodies) into a workspace snapshot bundle, drive the page with trusted CDP input, and verify injected scripts. Authorization never leaves the browser and is never written to disk.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",