# dsh-site-bridge

[English](README.md) · **Русский** · [中文](README.zh-CN.md)

**Глаза и руки [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) в вашем браузере.**
Снимает разметку страницы и её BFF-трафик в файлы, понятные агенту, управляет страницей доверенным
вводом и проверяет расширение, которое вы только что написали, — сразу в той же вкладке.

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Две половины, один мост:

| Половина | Что делает |
|---|---|
| **Плагин DSH** (`lib/`) | Держит loopback-мост и даёт модели восемь инструментов. Пишет пакет снимка в рабочее пространство сессии. |
| **Расширение MV3** (`extension/`) | Живёт в вашем профиле Chrome/Edge, работает с вашими куками, снимает разметку и сеть через CDP, управляет страницей настоящими событиями ввода. Локализовано: English, Русский, 中文. |

По отдельности ни одна половина не решает задачу: плагин держит модель и файлы, расширение — сессию
браузера и точность.

---

## Зачем

Писать расширение, которое встраивается в чужой сайт, — это в основном археология: из чего на самом
деле сделана разметка, какой селектор переживёт следующий деплой и какой BFF-эндпоинт вызывает эта
кнопка и с каким телом. Вручную это DevTools, копипаст и догадки.

Проект превращает археологию в снимок:

```
site_capture → captures/<host>/<timestamp>/
  dom.html          сериализованная разметка снятой области
  dom.json          дерево элементов: боксы, атрибуты, кандидаты селекторов
  styles.json       computed-стили ключевых узлов + CSS-переменные темы
  selectors.md      шорт-лист устойчивых селекторов с объяснением, почему они устойчивы
  network.jsonl     каждый снятый xhr/fetch, один JSON на строку
  api.md            эндпоинты, сгруппированные по method+path, с контрактами запроса/ответа
  screenshot.png    опциональный рендер всей страницы
```

Агент читает эти файлы как обычный исходник и пишет расширение. Затем `site_inject` выполняет этот
код в живой странице, и «должно работать» превращается в «работает здесь».

## Возможности

- **Разметка** — дерево элементов с боксами и видимостью, computed-стили интерактивных узлов,
  CSS-переменные темы и кандидаты селекторов по устойчивости (`data-testid` > `id` > `name` >
  `aria-label` > путь по классам > позиционный путь), каждый проверен на уникальность по живому DOM.
- **BFF-трафик** — `xhr`/`fetch` с методом, статусом, заголовками запроса, телом запроса,
  заголовками ответа и телом ответа, только для проверяемого сайта.
- **Доверенный ввод** — клики, ввод, select, клавиши, прокрутка и загрузка файлов через CDP
  `Input.*` / `DOM.setFileInputFiles`: события, которые страница не отличает от действий человека.
- **Виртуальный курсор** — DOM-оверлей, который едет к цели, плюс настоящее событие мыши в этих
  координатах — для контролов, игнорирующих программный `click()`.
- **Глубокие селекторы** — `host >>> inner` проходит сквозь открытые shadow-корни и same-origin
  iframe; координаты клика пересчитываются с учётом смещения фрейма.
- **Работает в фоновых вкладках** — чтение, клики, ввод и скриншоты работают, когда вкладка не в
  фокусе (Chrome тротлит там только таймеры и анимации).
- **Ничего не хранится из авторизации** — см. [Безопасность](#безопасность).

### Чем это отличается от плагинов автоматизации браузера

Есть плагины, которые дают агенту инструменты `browser_*`: навигация, клик, ввод, чтение страницы.
Это не один из них, хотя всё перечисленное умеет: ввод здесь — инструмент проверки снимка, а не
продукт.

Продукт — снимок: DOM с боксами и computed-стилями, CSS-переменные темы, кандидаты селекторов с
проверкой уникальности и собственные xhr/fetch сайта с заголовками и телами, разложенные по файлам,
которые агент читает. Автоматизация нужна, чтобы «этот селектор работает» и «этот вызов BFF проходит
с моими куками» проверялись на живой странице, а не предполагались.

Практическое следствие: плагин сделан для написания расширения под конкретный сайт, а инструменты
ввода нужны, чтобы проверять эту работу на живой странице, а не для браузинга.

## Требования

- DeepSeek Harness с CLI `dsh` и профилем (Desktop или Web).
- Chrome или Edge 125+ (расширение использует `chrome.debugger` и MV3 service worker).
- Node.js 22+ для инструментов в `tools/` и для тестов.

## Установка

### 1. Host-половина

```powershell
dsh plugin --profile desktop add dsh-site-bridge
```

Это ставит опубликованный пакет. Из локального клона вместо этого:

```powershell
# из корня репозитория
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install.ps1
```

Скрипт линкует плагин в ваш профиль, добавляет его в `dsh.profile.bundles` и раскладывает расширение
в `%USERPROFILE%\.dsh\site-bridge\extension`. Вручную то же самое:

```powershell
dsh plugin --profile desktop add <путь-к-этому-репозиторию>
```

Если CLI не сделал этого сам, добавьте `"dsh-site-bridge"` в `dsh.profile.bundles` в
`%USERPROFILE%\.dsh\profiles\desktop\package.json`, затем перезапустите DSH — host-половина
загружается при старте профиля.

### 2. Браузерная половина

1. `chrome://extensions` → включите **Режим разработчика** → **Загрузить распакованное** →
   `%USERPROFILE%\.dsh\site-bridge\extension`.
2. Откройте попап. Он покажет порт и путь к `bridge.json` (то же печатает `site_status`).
3. Нажмите **Импорт bridge.json** и выберите этот файл — или вставьте токен: он сохранится сам,
   кнопки нет.
4. Откройте нужную страницу и нажмите **Подключить эту вкладку**.

Chrome покажет плашку «началась отладка этого браузера»: это и есть CDP-сессия, на которой всё
работает. Она исчезнет после **Отключить**.

Интерфейс попапа следует языку браузера: английский, русский и упрощённый китайский лежат в
`extension/_locales`.

## Использование

```text
site_status                     → состояние моста, путь к спариванию, вкладки
site_tabs {action:"list"}       → вкладки; claim/use/release/open/close/focus
site_network {action:"start"}   → начать запись, затем действовать на странице
site_capture {screenshot:true}  → записать пакет снимка в рабочее пространство
site_snapshot {limit:40}        → интерактивные элементы с устойчивыми селекторами
site_text {selector:"#cart"}    → видимый текст с лимитом, для проверки
site_act {steps:"[...]"}        → click/fill/type/press/scroll/upload/wait/back/eval
site_pick {action:"wait"}       → человек кликает элемент и пишет примечание
site_cursor {x,y,click:true}    → виртуальный курсор, настоящее событие мыши
site_shot {fullPage:true}       → скриншот в captures/shots/*.png
site_inject {js:"..."}          → выполнить ваш скрипт в странице для проверки
```

Типичный цикл написания встраиваемого расширения:

1. `site_tabs` → привязать страницу.
2. `site_capture` → прочитать `selectors.md`, `styles.json`, `api.md`.
3. Написать userscript / content script в своём репозитории.
4. `site_inject` с телом скрипта → проверить, что якорь найден и вызов BFF проходит с куками
   браузера.
5. Доработать селекторы, которые снимок пометил как хрупкие.

### Попап и инструменты — одна и та же поверхность

Попап — ручное управление для человека за клавиатурой: подключить вкладку, отключить, включить
запись, выбрать элемент. Агент умеет то же самое через инструменты, включая
`site_tabs {action:"claim"}` для любой вкладки из `list`. Кнопка подключения — жест человека, а не
разрешение: доступ решает только токен моста.

Кнопка паузы в попапе разрывает связь со стороны браузера: расширение перестаёт опрашивать мост и отклоняет любые команды, поэтому ни один инструмент не дотянется до него и не включит обратно. Вернуть связь может только эта кнопка, и состояние переживает перезапуск воркера.

## Архитектура

```
┌──────────────────────────── DSH host ────────────────────────────┐
│  инструменты (site_*) ──► мост: 127.0.0.1, токен, очередь long-poll│
│  редакция на приёме ──► буфер в памяти ──► запись артефактов       │
└───────────────────────────────┬───────────────────────────────────┘
                                │ HTTP long-poll (без websocket)
┌───────────────────────────────┴───────────────────────────────────┐
│  MV3 service worker: chrome.debugger ──► CDP                        │
│    Network domain  → запросы с заголовками и телами                │
│    Input domain    → доверенные мышь/клавиатура, загрузка файлов   │
│    Runtime domain  → снимок DOM, селекторы, внедрённые скрипты     │
│  popup: подключение, вкладка, запись (ru / en / zh)                │
└────────────────────────────────────────────────────────────────────┘
```

Протокол небольшой и описан в [docs/protocol.md](docs/protocol.md).

## Безопасность

- **Авторизация не покидает браузер.** Расширение не читает куки и токены: запросы делает сама
  страница, браузер прикладывает их. Мост видит их в полёте, но значения `cookie`, `authorization`,
  `x-csrf-token`, `x-api-key`, `set-cookie` и подобных заголовков, credential-подобные query-параметры
  и поля JSON/форм заменяются на `«redacted»` **на приёме** — до того, как что-то сохранено.
- **На диск не попадает ничего авторизационного.** Имена заголовков сохраняются, потому что «этому
  эндпоинту нужен `x-csrf-token`» — часть контракта; значения — нет.
- **Захват ограничен сайтом.** Пишутся только свои `xhr`/`fetch` проверяемого сайта (тот же хост, тот
  же домен второго уровня или сторонний JSON). Телеметрия и агенты безопасности, которые отправляют
  дамп HTML страницы на свой сервер, отфильтровываются — иначе исходник с инлайн-токенами попадал бы
  в пакет.
- **Только loopback.** Мост слушает `127.0.0.1` и отклоняет любой запрос без 32-байтного токена
  сопряжения. `site_status` печатает путь к файлу с токеном, а не сам токен.
- **Остаточный риск.** Значения заголовков и тела всё равно проходят через процесс DSH в памяти. Если
  сайт кладёт секрет под нестандартным ключом, редакция его не узнает. Считайте пакет снимка
  чувствительным, как и любой дамп трафика.

### Модель доверия

Мост принимает только запросы с токеном сопряжения. Расширение просит `debugger`, а не
`<all_urls>`, и не внедряет content script в каждую страницу. Цена — шаг сопряжения и плашка отладки
Chrome. Взамен право выдаётся на вкладку и на время сессии, а запросы читаются там, где они сделаны:
учётные данные не покидают браузер.

### Согласие

Подключение вкладки по умолчанию — решение человека. Список вкладок агент получает свободно, но
`site_tabs {action:"claim"}` для вкладки, которую пользователь не подключал, отклоняется, пока в вызове
нет `confirmed: true` — причём в отказе названа сама вкладка, чтобы было понятно, о чём спрашивать.
Закрытие вкладки, подключённой пользователем, требует такого же подтверждения. Это правило внутри
плагина, а не разрешение ОС: оно заставляет агента остановиться и спросить, вместо того чтобы тихо
расширять свою область доступа.

## Ограничения

Кросс-доменные iframe, закрытые shadow-корни и лимиты тела разобраны в
[docs/limitations.md](docs/limitations.md) — с точным местом в коде, планом снятия каждого и оценкой
стоимости.

## Тесты

```powershell
node test\redact.test.mjs     #  8 — правила редакции
node test\bridge.test.mjs     #  8 — протокол, токен, живость
node test\artifacts.test.mjs  #  5 — раскладка пакета и вынос больших тел
node test\plugin.test.mjs     #  2 — регистрация инструментов и сквозной путь
node test\e2e.mjs             # 31 — реальный Chrome через протокол
node test\tools-chrome.mjs    # 19 — реальный Chrome через инструменты
```

`e2e.mjs` и `tools-chrome.mjs` поднимают одноразовый Chrome, загружают расширение через
`Extensions.loadUnpacked` и проверяют захват, доверенный ввод, виртуальный курсор, загрузку файла,
инъекцию и скриншоты на локальной странице с рабочим BFF-эндпоинтом.

## Структура репозитория

```
lib/            плагин DSH: инструменты, мост, редакция, запись артефактов
extension/      MV3: service worker (CDP), popup, иконки, локали
docs/           протокол, ограничения, подробное руководство
scripts/        install.ps1
tools/          растеризация иконок, превью попапа/иконок, чтение asar (только для разработки)
test/           юнит, интеграционные, end-to-end
```



MIT — см. [LICENSE](LICENSE).

## Версионирование

Версия живёт в `package.json` и зеркалится в `extension/manifest.json`; история — в `CHANGELOG.md`.
Каждый коммит, меняющий поставляемый код, поднимает версию; коммиты только с документацией — нет:

```powershell
node tools\version.mjs check                  # упадёт, если три файла расходятся
node tools\version.mjs bump minor             # поднимет package.json + manifest.json + заготовку в CHANGELOG
```

Релизы помечаются тегами `vX.Y.Z`. Версии ниже 0.3.0 проставлены задним числом по истории коммитов,
поэтому старый тег может указывать на содержимое, где ещё стояла 0.1.0.

## Лицензия

MIT — см. [LICENSE](LICENSE).
