# dsh-local-link

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

[![CI](https://github.com/donoteatme/dsh-local-link/actions/workflows/ci.yml/badge.svg)](https://github.com/donoteatme/dsh-local-link/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/dsh-local-link.svg)](https://www.npmjs.com/package/dsh-local-link)
[![npm downloads](https://img.shields.io/npm/dm/dsh-local-link.svg)](https://www.npmjs.com/package/dsh-local-link)
[![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com/p/donoteatme/dsh-local-link/)
[![DSH Market](https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-en.svg)](https://dsh.market/)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Открывайте ту же веб-сессию [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) с телефона, планшета или другого компьютера в доверенной частной сети. `dsh-local-link` добавляет в стандартный клиент DSH одноразовое подключение по QR-коду и адаптивный мобильный интерфейс — без облачного ретранслятора, отдельного аккаунта, нативного приложения, альтернативного чата или второго механизма выбора рабочих пространств.

Откройте `Local access`, отсканируйте QR-код — и подключённый браузер попадёт в текущую сессию Harness, выбранную на компьютере. Диалог, права доступа, рабочие пространства и интерфейсы плагинов останутся теми же.

Ветка `1.2.x` поддерживает DeepSeek Harness `0.1.5-rc.2`, `0.1.7-rc.2`, `0.2.0-rc.2` и `0.2.1-rc.2` / `0.2.1-rc.5` и делает стандартный веб-клиент удобным на узких сенсорных экранах. Существующий интерфейс Harness преобразуется в адаптивные панели, компактные элементы управления сессией и удобные для касаний действия, но сам клиент, сессия, слоты плагинов, разрешения и поток событий агента остаются прежними.

> **Граница безопасности:** шлюз работает по незашифрованному HTTP и предназначен только для доверенной частной сети. Не выставляйте его порт в интернет и не используйте плагин в публичных сетях Wi-Fi.

<p align="center">
  <img src="docs/images/local-access-qr.jpg" width="960" alt="DeepSeek Harness с открытым dsh-local-link: одноразовое подключение по QR-коду к текущей стандартной сессии Harness">
</p>

<p align="center"><sub>Одна сессия Harness, прямое подключение внутри доверенной локальной сети.</sub></p>

## Возможности и назначение

Local Link — намеренно небольшой LAN-компаньон для существующего веб-клиента Harness, а не универсальная платформа удалённого доступа. Его задача — надёжно решить один сценарий: открыть уже запущенный на компьютере Harness с другого устройства в той же частной сети и продолжить работу с теми же сессиями в браузере.

Разработка сосредоточена на следующем:

- надёжный доступ к текущему Harness Host из той же локальной сети;
- короткоживущие приглашения и понятное управление жизненным циклом подключённых устройств;
- адаптивное отображение стандартного клиента без дублирования бизнес-логики Harness;
- совместимость с зарегистрированными представлениями, слотами, разрешениями, темами и состоянием сессии Harness;
- доступность, локализация, ограниченная диагностика и усиление безопасности внутри этой границы.

Плагин намеренно не решает следующие задачи:

- публикация в интернете, облачные ретрансляторы, управляемые туннели или облачные аккаунты;
- отдельное нативное приложение для телефона или компьютера;
- замена чата или форк интерфейса Harness;
- независимая синхронизация рабочих пространств, файлов или сессий вне Harness;
- универсальная среда удалённых расширений, не связанная с открытием текущего веб-интерфейса.

Это не означает, что плагин завершён или заморожен. Будущие изменения должны делать локальный браузерный доступ проще, безопаснее, совместимее и удобнее для диагностики, а не превращать продукт в систему другого класса.

## Установка

### npm — рекомендуемый способ

Остановите запущенный процесс `dsh web`, добавьте плагин в профиль `web`, затем снова запустите Harness:

```shell
dsh plugin --profile web add dsh-local-link
dsh web
```

### Git clone — рабочая копия для разработки

Требования: Git, Node.js 22.19+ или 24+, Corepack и глобально установленный `dsh`, совместимый с диапазоном `engines.dsh`, объявленным пакетом.

```shell
git clone https://github.com/donoteatme/dsh-local-link.git
cd dsh-local-link
corepack pnpm install --frozen-lockfile
npm run verify
dsh plugin --profile web add .
dsh web
```

Профиль ссылается непосредственно на рабочую копию. После изменения клиентского кода пересоберите проект, а после изменения Host-части перезапустите `dsh web`.

## Совместимость

Local Link `1.2.5` собирается и проверяется на DeepSeek Harness `0.2.1-rc.5`. Поддержаны доверенные адреса из `webStartup` с резервным путём через `webRuntime` для старых Host. Выбор чата для QR и защита от мобильного автофокуса используют публичные Session-scoped слоты; кнопка парных устройств открывает штатные настройки через актуальный launcher.

Диапазон устанавливаемых версий DeepSeek Harness объявлен в `package.json` через `engines.dsh`. Совместимость проверяется на конкретных релизах и не выводится только из успешной установки или плавающего dist-tag.

На `0.2.1-rc.5` проверены LAN-сопряжение, переход по QR в выбранный чат, авторизованные HTTP/WebSocket, немедленное закрытие соединения после отзыва устройства, запрет RPC выбора папок через LAN и мобильная вёрстка на ширинах 320–768 CSS-пикселей в EN и ZH. Предыдущие Host оставлены в диапазоне на основании исторических проверок из [changelog](CHANGELOG.md); вся старая матрица и физический телефон заново не проверялись. Запись с микрофона телефона через обычный LAN HTTP по-прежнему не поддерживается: релиз не добавляет HTTPS или голосовой ввод.

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

### Открытие Harness на другом устройстве

1. Откройте на компьютере нужную сессию.
2. Нажмите `Local access` в нижней части боковой панели Harness.
3. Отсканируйте QR-код или скопируйте одноразовую ссылку на другое устройство в той же сети.
4. Первый браузер, использовавший приглашение, откроет стандартный клиент Harness в выбранной сессии. Административные действия уровня Host, зарезервированные Harness для loopback-подключений, удалённо недоступны.

Приглашение одноразовое, по умолчанию действует пять минут и немедленно заменяется при выборе `Generate another code`. После успешного сопряжения QR-панель закрывается автоматически. Чтобы подключить другой браузер или устройство, снова откройте `Local access` и получите новое приглашение.

На телефонах и планшетах Mobile View включается автоматически при ширине viewport не более 834 CSS-пикселей. То же адаптивное поведение можно проверить через эмуляцию устройства в браузере: отдельный URL-параметр, подмена user-agent или второй клиент не нужны.

## Мобильный интерфейс

Стандартным веб-интерфейсом Harness трудно пользоваться на телефоне: постоянная десктопная навигация занимает экран, элементы управления сессией конкурируют с заголовком диалога, некоторые действия зависят от наведения или долгого нажатия, а сведения о субагентах слишком плотные для узкого экрана. Mobile View делает существующий интерфейс удобным, не создавая второй клиент и не дублируя бизнес-логику Harness.

| Стандартное поведение на узком экране | Mobile View в `1.2.x` |
| --- | --- |
| Десктопная навигация конкурирует с диалогом | Навигация по рабочим пространствам и сессиям открывается в закрываемой левой панели |
| Метаданные сессии перегружают заголовок | Контекст, модель, доступ, пресет, активность и экспорт вынесены в компактную правую панель |
| Сведения о субагентах трудно просматривать и открывать | В 0.1.5 чип у поля ввода открывает каталог снизу; в 0.1.7 и 0.2 каталог и навигация принадлежат Harness и находятся в заголовке |
| Действия с сессиями и рабочими пространствами рассчитаны на наведение | Меню действий остаются видимыми и доступными для касания |
| При смене сессии может открыться экранная клавиатура | Автоматический фокус поля ввода подавляется, но намеренное касание по-прежнему устанавливает фокус |
| Десктопные отступы не учитывают безопасные области телефона | Заголовок, поле ввода, вкладки, медиа, оверлеи и прокрутка адаптируются к узкому экрану |

Меняется только представление, но не ответственность и поведение Harness:

- стандартный браузер рабочих пространств и сессий превращается в левую выдвижную панель;
- динамические слоты Chat, Trajectory, поля ввода, оверлеев и сторонних плагинов сохраняются;
- `Current session` открывается из заголовка и показывает структуру контекста, модель, доступ к рабочему пространству, пресет, активность и скачивание журнала сессии;
- на телефоне дублирующий индикатор контекста под чатом и кнопку открытия на компьютере (Проводник/VS Code/Rider) скрыли; на десктопе оба элемента остались;
- в 0.1.5 статус дочерних агентов открывает вложенный каталог в нижней панели; в 0.1.7 и 0.2 для этого используется штатное действие Harness в заголовке;
- сервис тем Harness управляет компактной кнопкой «солнце/луна»;
- десктопные Settings и вложенное действие Local access скрыты в мобильной навигации, но переключение темы остаётся доступным;
- меню сессии и активного рабочего пространства сразу доступны на сенсорных экранах;
- безопасные области, прокручиваемые вкладки, ограниченные по размеру медиа и поведение виртуальной клавиатуры адаптированы к узким экранам;
- модальные панели плагина удерживают клавиатурный фокус и после закрытия возвращают его вызвавшему элементу;
- существующие рабочие пространства остаются доступны, а вводящая в заблуждение удалённая кнопка `Add workspace`, открывающая системный выбор каталога, скрыта.

Скриншоты в тёмной теме сняты из настоящих браузерных сессий Harness `0.2.1-rc.5`: мобильные кадры — `430 × 932`, desktop — `1440 × 1000`. В отдельных демонстрационных профилях используются только вымышленные рабочие пространства, диалоги, модели, устройства и статистика. Русский README использует английские кадры; у китайского README отдельные скриншоты на упрощённом китайском. Субагенты показаны в штатном каталоге Harness в заголовке.

| Навигация | Текущая сессия | Субагенты | Диалог |
| :---: | :---: | :---: | :---: |
| <img src="docs/images/mobile-navigation.png" width="210" alt="Навигационная панель Mobile View в тёмной теме с вымышленными рабочим пространством и сессией"> | <img src="docs/images/mobile-session-info.png" width="210" alt="Панель Current session в тёмной теме с вымышленными моделью, доступом к рабочему пространству, статистикой и журналом сессии"> | <img src="docs/images/mobile-subagents.png" width="210" alt="Штатный каталог субагентов Harness в заголовке с двумя вымышленными дочерними агентами"> | <img src="docs/images/mobile-chat.png" width="210" alt="Диалог Harness в Mobile View с исходными действиями сообщения и элементами поля ввода в одну строку"> |

### Разрешения

Удалённый браузер наследует уровень доступа выбранной сессии Harness: **Read only**, **Workspace write** или **Full access**. Mobile View только показывает это значение и не создаёт новый слой разрешений и не ослабляет существующий. Это относится к обычным операциям сессии; Settings, учётные данные, нативные Host-действия и создание пресетов агентов остаются доступными только через loopback. Скрытие `Add workspace` — ограничение интерфейса, а не граница авторизации.

Адаптивный интерфейс рассчитан на viewport шириной от 360 до 834 CSS-пикселей. Релизная матрица включает проверки `360 × 800`, `390 × 844`, `430 × 932` и `768 × 1024`; для приёмки релиза также нужны проверки на реальном телефоне в портретной и альбомной ориентации. Поведение, сохранение темы, поверхности совместимости и полный чек-лист описаны в документе [Mobile View](docs/MOBILE_VIEW.md).

### Управление доверенными браузерами

На компьютере с Harness откройте `Paired devices` в панели QR-кода либо перейдите в `Settings → Local access`. Каждый новый браузер сначала называется `My device`; подпись определяется автоматически, например `Phone · Chrome`, `Tablet · Safari` или `Computer · Edge`.

- `Rename` меняет только отображаемое имя.
- `Revoke` аннулирует учётные данные браузера и немедленно закрывает все открытые WebSocket-соединения Local Link, авторизованные этим устройством.
- После очистки cookie, открытия приватного окна, использования нового профиля браузера или отзыва устройства потребуется новое приглашение.

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

<p align="center">
  <img src="docs/images/local-access-devices.jpg" width="960" alt="Настройки DeepSeek Harness, открытые на компактном списке подключённых устройств Local access">
</p>

Разверните `Diagnostics` на той же странице Settings, чтобы просмотреть последние события локального шлюза. `Copy report` формирует готовый для issue JSON, а `Clear` удаляет локальную историю. Отчёт содержит только коды событий, время, серьёзность и небольшой разрешённый контекст; в него никогда не попадают токены подключения, cookie, IP-адреса, идентификаторы устройств и сессий, имена устройств, пути запросов, промпты, диалоги или файлы проекта.

<p align="center">
  <img src="docs/images/local-access-diagnostics.jpg" width="960" alt="Настройки Local access в DeepSeek Harness с раскрытой нативной панелью Diagnostics">
</p>

## Почему плагин остаётся небольшим

- **Стандартный интерфейс Harness:** плагин открывает существующий веб-клиент и поток событий агента.
- **Только локальная сеть:** один шлюз частной сети перенаправляет запросы к loopback Harness Host.
- **Две runtime-зависимости:** поддержка схемы конфигурации и генерация QR-кода.
- **Без цифрового отпечатка:** доступ определяется случайными учётными данными отдельного браузера, а не попыткой угадать личность устройства.
- **Нативные точки расширения:** элементы боковой панели и Settings используют слоты и сервисы локализации Harness.
- **Нативная адаптивность:** поставляемый Harness AppFrame остаётся смонтированным, а зарегистрированные представления диалога и оверлеи продолжают работать динамически.
- **JSON-локализация:** весь видимый текст плагина хранится в согласованных словарях локализации.
- **Локальная диагностика:** ограниченная и очищенная от чувствительных данных история помогает разбирать проблемы без аналитики и внешнего сборщика.

## Как это работает

```text
Браузер на компьютере: 127.0.0.1:3080
  └─ Local access → одноразовое приглашение
                         │
Телефон / планшет / компьютер в частной локальной сети
  └─ 192.168.x.x:3088 → проверка сети и Host
                       → подключение или проверка cookie устройства
                       → прокси HTTP / WebSocket
                       → 127.0.0.1:3080 (тот же Harness Host)
```

Шлюз не создаёт вторую сессию Harness. При первом подключении он передаёт новому origin браузера выбранную на компьютере сессию; данные сессии и текущие события диалога по-прежнему поступают из того же Host.

Для авторизации используется случайный 256-битный cookie credential. Хранится только его SHA-256-хэш. Редактируемое имя и определённые браузером сведения об устройстве никогда не предоставляют доступ.

Полномочия локальной сети объявляются через официальный контракт подключения Harness `trustedHosts`. Благодаря этому API сессий и WebSocket-трафик проходят аутентификацию без притворного обозначения телефона как loopback-клиента. Harness `0.1.2-rc.1` и проверенные сборки `0.1.5` и `0.1.7` аутентифицируют транспорт целиком, поэтому Local Link самостоятельно запрещает RPC конфигурации, учётных данных, нативных Host-действий и создания пресетов агентов, которые ранние поддерживаемые версии Harness ограничивали loopback-подключениями.

## Конфигурация

Встроенный патч профиля задаёт консервативные значения по умолчанию:

| Параметр | По умолчанию | Назначение |
| --- | --- | --- |
| `listenHost` | `0.0.0.0` | Слушать локальные интерфейсы; проверка запросов по-прежнему принимает только частные и loopback-источники. |
| `listenPort` | `3088` | Порт LAN-шлюза. |
| `upstreamOrigin` | `http://127.0.0.1:3080` | Существующий loopback веб-сервер Harness. |
| `accessMode` | `pairing` | Требовать одноразовое приглашение и cookie устройства. |
| `pairingTtlSeconds` | `300` | Срок действия приглашения. |
| `deviceTtlDays` | `90` | Срок запоминания браузера. |
| `diagnosticsEnabled` | `true` | Хранить ограниченную локальную историю диагностики. |
| `diagnosticsMaxEntries` | `15` | Максимальное количество сохранённых событий (`5`–`200`). |
| `diagnosticsFile` | рядом со `stateFile` | Локальное JSON-хранилище событий; встроенный профиль использует `~/.dsh/local-link/diagnostics.json`. |

`listenHost` принимает только `0.0.0.0` либо явный частный или loopback IP-адрес. Публичные адреса прослушивания отклоняются при запуске; эта проверка конфигурации дополняет, но не заменяет системный брандмауэр.

Режим `trusted-lan` отключает авторизацию отдельных устройств и предназначен только для изолированных сетей разработки. Поддерживаемым режимом по умолчанию остаётся `pairing`, потому что подключённый браузер Harness может читать файлы, отправлять промпты, подтверждать действия и запускать команды.

Плагин никогда не создаёт правила брандмауэра. Если Windows запросит сетевой доступ, разрешайте его только для профиля **Private** и не перенаправляйте порт `3088` на роутере.

## Устранение неполадок и диагностика

Diagnostics — локальная событийная история ошибок, а не журнал запросов. По умолчанию плагин хранит 15 событий, показывает 12 последних, объединяет одинаковые события в пределах пяти секунд и ничего не записывает для успешных запусков, запросов, подключений, копирования, переименования или отзыва.

При обращении за помощью:

1. Один раз воспроизведите ошибку.
2. Откройте `Settings → Local access → Diagnostics`.
3. Нажмите `Refresh`, если панель уже была открыта.
4. По новейшему стабильному коду события определите границу сбоя.
5. Нажмите `Copy report`, проверьте JSON и при необходимости приложите его к issue.

Отчёт никогда не содержит секреты, адреса, идентификаторы, имена, URL, пути, промпты, диалоги или данные проекта и никогда не отправляется автоматически. Полный список отслеживаемых ошибок приведён в [справочнике событий и первичных проверок](docs/DIAGNOSTICS.md).

## Локализация

Словарь плагина выбирается активным языком Harness; отдельного переключателя языка в плагине нет.

Минимальная автоматическая страница подключения загружается до появления сервисов локализации Harness. Она использует те же словари и выбирает английский или китайский по списку языков подключающегося браузера; английский используется по умолчанию.

| Язык | Словарь | Статус |
| --- | --- | --- |
| Английский | `src/locales/en.json` | Включён |
| Китайский | `src/locales/zh.json` | Включён |

Тесты проверяют совпадение ключей словарей. Чтобы добавить язык, скопируйте `en.json`, переведите значения, не меняя ключи, зарегистрируйте идентификатор локали в `src/client.tsx`, расширьте языковую карту страницы подключения в `src/gateway/pair-page.ts` и выполните `npm test`.

## Разработка

```shell
corepack pnpm install --frozen-lockfile
npm run typecheck
npm test
npm run build
npm run verify
```

Документация проекта:

- [Архитектура и границы совместимости](docs/ARCHITECTURE.md)
- [Поведение Mobile View и релизная матрица](docs/MOBILE_VIEW.md)
- [Локальная диагностика и коды событий](docs/DIAGNOSTICS.md)
- [Модель безопасности](docs/SECURITY.md)
- [Участие в разработке](CONTRIBUTING.md)
- [Сообщение об уязвимостях](SECURITY.md)

## Известные ограничения

- Трафик локальной сети не шифруется: текущий шлюз использует обычный HTTP.
- Отзыв устройства блокирует новые запросы и переподключения и завершает все открытые WebSocket-соединения Local Link, авторизованные этим устройством.
- Выбранная удалённым браузером светлая или тёмная тема в поддерживаемых версиях Harness действует только на текущей странице; после перезагрузки восстанавливается настройка Host, а значение `system` определяется по удалённому устройству.
- Для приглашений используется частный IPv4-интерфейс с наивысшим приоритетом, определённый при запуске; ручной выбор между несколькими LAN-интерфейсами пока отсутствует.
- Быстрый переход к конкретному разделу Settings использует небольшой семантический мост совместимости, поскольку Harness не предоставляет общий сервис навигации по настройкам.
- Контейнер действий в нижней части Harness требует правила совместимости разметки при наличии полноширинного действия Cordis.
- Mobile View рассчитан на viewport от 360 CSS-пикселей, но сторонние представления фиксированной ширины, виртуальные клавиатуры, смена ориентации, режим разделённого экрана и системное масштабирование текста всё ещё требуют проверки на реальных устройствах.
- Кнопки и текстовые поля плагина используют примитивы Harness `Button` и `Input` и их семантические варианты. У поддерживаемых сборок Harness нет полного публичного контракта адаптивной оболочки, отступов и скруглений, поэтому мобильная геометрия и композиции остаются задокументированными рисками совместимости с дизайн-системой; смена имён экспорта значков обработана совместимым адаптером.

## Сведения о разработке

Первоначальная реализация и документация создавались совместно сопровождающим проекта и OpenAI Codex. Все изменения по-прежнему должны проходить проверку сопровождающим, автоматические тесты, аудит безопасности и обычный процесс принятия изменений. Пакет не содержит runtime-зависимостей от AI-сервисов, телеметрии или сгенерированного кода.

## Лицензия

[MIT](LICENSE) © 2026 участники разработки dsh-local-link.
