dsh-site-bridge 0.8.0 → 0.8.1

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.
@@ -1,6 +1,6 @@
1
1
  # Ограничения: что именно ломается и как это снять
2
2
 
3
- Четыре ограничения, которые чаще всего мешают на реальных сайтах. Для каждого: наблюдаемое
3
+ Пять ограничений, которые чаще всего мешают на реальных сайтах. Для каждого: наблюдаемое
4
4
  поведение, причина (со ссылкой на код), что нужно изменить и сколько это стоит.
5
5
 
6
6
  | # | Ограничение | Статус | Что осталось |
@@ -9,6 +9,7 @@
9
9
  | 2 | shadow DOM частично | **частично снято** | открытые корни: селекторы-цепочки и поиск работают; закрытые корни не видны |
10
10
  | 3 | тело ответа ≤ 400 КБ | **снято** | лимит 5 МБ в воркере, тела > 200 КБ выносятся в `bodies/<id>.json` |
11
11
  | 4 | один таб на сессию | **снято** | `Map<tabId, session>`, `tabId` в командах, переключатель в попапе |
12
+ | 5 | Chrome отказывает в `chrome.debugger.attach` отдельной вкладке (`Cannot access a chrome-extension:// URL of different extension`) | открыто | воспроизведено на `mail.google.com`: соседние сайты подключаются, Gmail — нет, при этом `tabs.get` вкладку отдаёт, а в списке целей у неё обычный `page` с https-адресом. Обход не найден; `site_tabs {action:"diagnose"}` показывает, что видит Chrome |
12
13
 
13
14
  Ниже — исходный разбор каждого ограничения; для снятых пунктов оставлено описание того, что
14
15
  именно было изменено и какие границы остались.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "default_locale": "en",
4
4
  "name": "__MSG_appName__",
5
- "version": "0.8.0",
5
+ "version": "0.8.1",
6
6
  "description": "__MSG_appDesc__",
7
7
  "minimum_chrome_version": "116",
8
8
  "permissions": [
package/extension/sw.js CHANGED
@@ -88,18 +88,27 @@ async function evt(kind, payload = {}) {
88
88
 
89
89
  let looping = false
90
90
 
91
+ /** Runs a promise with a deadline, so one stuck Chrome call cannot park the loop. */
92
+ function withDeadline(promise, ms, label) {
93
+ return Promise.race([
94
+ promise,
95
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} не ответил за ${ms} мс`)), ms))
96
+ ])
97
+ }
98
+
91
99
  async function ensureLoop() {
92
100
  if (looping) return
93
101
  looping = true
94
102
  let greeted = false
95
103
  try {
96
104
  for (;;) {
97
- const cfg = await loadCfg()
98
- if (!cfg.token) {
99
- await sleep(1500)
100
- continue
101
- }
105
+ state.lastTick = Date.now()
102
106
  try {
107
+ const cfg = await loadCfg()
108
+ if (!cfg.token) {
109
+ await sleep(1500)
110
+ continue
111
+ }
103
112
  const reply = await api(cfg, '/v1/poll?timeout=20000')
104
113
  if (!greeted) {
105
114
  greeted = true
@@ -115,7 +124,7 @@ async function ensureLoop() {
115
124
  state.attempts = 0
116
125
  for (const command of reply.commands || []) {
117
126
  // Sequential: a command may pin a tab for its whole duration.
118
- await runCommand(cfg, command)
127
+ await withDeadline(runCommand(cfg, command), 120000, `команда ${command?.op ?? '?'}`)
119
128
  }
120
129
  } catch (error) {
121
130
  greeted = false
@@ -129,6 +138,16 @@ async function ensureLoop() {
129
138
  }
130
139
  }
131
140
 
141
+ // If the loop parks on a Chrome call that never settles, restart it.
142
+ setInterval(() => {
143
+ if (!looping) return
144
+ if (Date.now() - (state.lastTick ?? 0) > 90000) {
145
+ state.lastError = 'цикл опроса завис, перезапускаю'
146
+ looping = false
147
+ void ensureLoop()
148
+ }
149
+ }, 30000)
150
+
132
151
  function sleep(ms) {
133
152
  return new Promise((resolve) => setTimeout(resolve, ms))
134
153
  }
@@ -186,6 +205,8 @@ async function handleMessageInner(message) {
186
205
  connected: state.lastPollOk > 0 && Date.now() - state.lastPollOk < 45000,
187
206
  lastError: state.lastError ?? null,
188
207
  attempts: state.attempts || 0,
208
+ loopAlive: looping,
209
+ lastTickAgoMs: state.lastTick ? Date.now() - state.lastTick : null,
189
210
  target: active?.target ?? null,
190
211
  activeTabId: state.activeTabId,
191
212
  targets: [...state.sessions.values()].map((session) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-site-bridge",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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",