# 📦 @goodandready/dsh-remote-workspace

Корпоративный плагин удалённой разработки для DeepSeek Harness: SSH, SFTP-синхронизация и туннелирование

npm version license DSH Plugin Node version

Все проекты автора

🇬🇧 English • 🇷🇺 Русский • 🇨🇳 中文说明

⭐ Если вам нравится этот плагин, поставьте ему звезду на GitHub — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.

🐛 Если вы нашли баг или хотите предложить новый функционал, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
--- ## ⚡ Обзор и решаемая проблема В современной программной инженерии и агентных сценариях автономные AI-агенты платформы **DeepSeek Harness (DSH)** регулярно сталкиваются с необходимостью вести разработку на удалённых серверах: мощных GPU-нодах, облачных виртуальных машинах, staging-контурах и контейнерных кластерах. Без плагина `@goodandready/dsh-remote-workspace` возникают фундаментальные ограничения: 1. **Замкнутость на локальной машине**: Все стандартные операции DSH и инструменты агента исполняются строго локально, там где физически запущен процесс DSH. 2. **Ненадёжность самодельных скриптов**: Прямой вызов SSH/SCP через системные утилиты не имеет пула соединений, рвёт сессии при задержках сети и создаёт избыточный оверхед на постоянные рукопожатия. 3. **Риск затирания кода и повреждения файлов**: Обычное копирование файлов поверх существующих не отслеживает одновременные изменения и может повредить файл при разрыве соединения в момент записи. 4. **Изолированность портов**: Чтобы получить доступ к удалённому веб-серверу, базе данных или тестовому сервису, разработчику приходится вручную настраивать сторонние SSH-туннели. `@goodandready/dsh-remote-workspace` решает эти задачи на уровне экосистемы Cordis: он добавляет высокопроизводительный пул соединений SSH2, потоковую работу с SFTP с атомарной защитой записи, 3-стороннюю синхронизацию с контролем конфликтов по SHA-256, динамический проброс портов и нативную карточку настроек в Web UI в стиле `dsh-clinebot`. --- ## 🏗️ Архитектура ```mermaid graph LR subgraph DSH["DeepSeek Harness (Контекст Cordis)"] UI["Web UI Карточка настроек
(стиль dsh-clinebot)"] Routes["REST API Маршруты
(/state, /browse, /test, /sync)"] Tools["Инструменты агента
(remote_exec, remote_fs, sync, tunnel)"] Ssh["SshService
(Пул SSH2 и Keepalive)"] SFTP["RemoteFsService
(Атомарный SFTP-поток)"] Sync["MirrorSyncService
(3-сторонний движок SHA-256)"] Tunnel["TunnelService
(Проброс портов)"] end subgraph RemoteNode["Удалённый сервер (Облачный VM / GPU Нода)"] SSHD["SSH Сервер (:22)"] FS["Файловая система хоста"] AppPort["Сервис / Dev-сервер"] end UI -->|REST API| Routes Routes --> Ssh Routes --> SFTP Routes --> Sync Tools --> Ssh Tools --> SFTP Tools --> Sync Tools --> Tunnel Ssh -->|SSH2-канал / Ключ или Пароль| SSHD SFTP -->|SFTP-подсистема| FS Sync -->|Дифференциальный Pull / Push| FS Tunnel -->|Локальный проброс портов| AppPort classDef default fill:#1e1e2e,stroke:#6366f1,stroke-width:1px,color:#cdd6f4; classDef accent fill:#313244,stroke:#a6e3a1,stroke-width:2px,color:#a6e3a1; class DSH,RemoteNode accent; ``` --- ## ✨ Исчерпывающий разбор возможностей ### 1. `SshService` — Пул соединений и универсальная аутентификация - **Пул соединений**: Поддерживает постоянные авторизованные сессии SSH2, индексированные по `host:port:username`. - **Два метода аутентификации**: - **SSH-ключ (Private Key)**: Поддержка путей к локальным файлам (`~/.ssh/id_ed25519`), прямых PEM-строк и парольных фраз (passphrase). - **Пароль (Password)**: Прямая безопасная аутентификация по логину и паролю. - **Диагностический зонд**: Метод `testConnection` измеряет пинг в миллисекундах и определяет платформу удалённого хоста (`uname -srm`). - **Защита от таймаутов**: Пакеты keep-alive предотвращают разрыв соединения корпоративными фаерволами. - **Прокси и прыжки**: `proxyCommand` запускает команду в стиле OpenSSH. Токены: `%h`, `%p`, `%r`, `%n` и `%%`. `jumpHosts` — упорядоченный список идентификаторов профилей. Если массив пуст, используется `jumpHostId` через запятую. Первый бастион берётся из пула, следующие открывают отдельные соединения и закрываются вместе с целевой сессией. - **Агент и одноразовый код**: `agentPath` задаёт сокет агента. Пустое значение использует `SSH_AUTH_SOCK`, а на Windows можно указать `pageant`. Запрос keyboard-interactive появляется на карточке и истекает через 60 секунд. - **Простой пула**: соединение без туннеля и без выполняемой команды закрывается через 30 минут. Следующая команда открывает его снова. - **Повтор до вывода**: если соединение оборвалось до первого вывода, команда повторяется до трёх раз. Таймаут команды, уже начатый вывод и `idempotent: false` не повторяются. - **Отдельный терминал**: терминал не делит соединение пула. Закрытие терминала закрывает только его SSH-сессию. ### 2. `RemoteFsService` — Отказоустойчивый SFTP - **Атомарная запись файлов**: Загрузка производится во временный файл (`.tmp..`) с последующим атомарным переименованием. Разрыв соединения не повреждает целевой файл. - **Потоковое чтение**: Высокоскоростное чтение фрагментами без переполнения оперативной памяти. - **Файловые операции**: Полный набор примитивов: `stat`, `listDir`, рекурсивное создание каталогов `mkdir -p` и рекурсивное удаление `remove`. ### 3. `MirrorSyncService` — 3-сторонняя синхронизация с контролем конфликтов - **Учёт контрольных сумм**: Сохраняет базовые слепки SHA-256 в файле `.dsh-sync-manifest.json`. - **Обнаружение конфликтов**: Если файл изменился одновременно и на удалённом сервере, и в локальном зеркале с момента последней синхронизации, операция останавливается с подробным отчётом о конфликте. - **Направленная синхронизация**: Поддерживает режимы `pull` (сервер → локально) и `push` (локально → сервер) с флагом принудительной перезаписи `force`. - **Предпросмотр (Dry-Run)**: Возможность оценить состав изменяемых, добавляемых и удаляемых файлов до реального применения. - **Умная фильтрация**: Игнорирование служебных каталогов и артефактов сборки (`.git`, `node_modules`, `.dsh`, `.worktrees`, `.DS_Store`). ### 4. `TunnelService` — Встроенные SSH-туннели - **Прямой проброс портов (Local Port Forwarding)**: Открывает локальный порт на машине с DSH и перенаправляет трафик через защищённый SSH-туннель к любому порту на удалённой машине (например, `127.0.0.1:8080` → удалённый `127.0.0.1:8080`). - **Управление жизненным циклом**: Программный запуск, остановка и получение списка активных туннелей. ### 5. `tools.js` — Инструменты для AI-моделей 4 чётких ортогональных инструмента для агента: - `remote_exec`: Выполнение bash-команд на удалённом сервере с фиксацией кода возврата, stdout и stderr. - `remote_fs`: Операции чтения, записи, stat, листинга, создания директорий и удаления. - `remote_sync`: Синхронизация между локальным зеркалом и сервером с отчётом о конфликтах и режимом dry-run. - `remote_tunnel`: Управление SSH-туннелями (запуск, остановка, список). - `remote_hosts`: Краткая markdown-таблица хостов без паролей, ключей и команд прокси. `query` обязателен; пустая строка показывает все хосты. - `remote_cluster`: Одна команда на хостах, которые совпали по окружению, всем указанным меткам и необязательным псевдонимам. `maxWorkers` по умолчанию 8. ### 6. `client.js` — Нативный UI карточки настроек - Полноценная интеграция в слот `settings.plugin.item` с ключом `dsh-remote-workspace`. - **Сегментированный переключатель авторизации**: Удобное переключение между SSH-ключом и паролем. - **Интерактивный браузер удалённых папок**: Модальное окно с навигацией по каталогам и выбором рабочей директории в один клик. - **Индикатор статуса и задержки**: Проверка связи с отображением пинга и ОС удалённого узла. Бейдж шапки читает `/dsh-remote-workspace/state` и показывает проверку, отсутствие активного хоста, готовность или недоступность статуса. - **Кнопки действий**: Быстрый запуск синхронизации и контроль туннелей прямо из настроек. Ошибка сохранения, удаления, выбора активного профиля, обзора каталога или остановки туннеля показывается в предупреждении на карточке. Проверка соединения отправляет поля профиля, включая `host`. - **Подпись в списке плагинов**: английская `Remote Workspace` или китайская `远程开发工作区` из словарей карточки. - **Версия в блоке обновления**: строка показывает установленную версию из ответа статуса. Пока ответ не пришёл, показывается нейтральная подпись неизвестной версии. - **Группы хостов**: у профиля есть `environment`, `tags`, `location` и `description`. Карточка показывает плоский список, группы по окружению или по метке и может проверить группу целиком. - **Файлы**: вкладка Files загружает и скачивает удалённый файл, показывает прогресс по байтам и даёт отменить передачу. Файл больше 512 МБ отклоняется. - **Шрифт терминала**: `terminalFontFamily` — семейство шрифтов CSS. Пустое значение оставляет стандартный моноширинный набор. - **Скрытая вкладка**: опрос состояния останавливается, пока вкладка браузера скрыта, и сразу обновляется, когда вкладка снова видна. - **Боковая панель**: кнопка Remote слева открывает в центральной колонке хосты, терминал, файлы, контейнеры, туннели и кластер. Возврат в чат только скрывает колонку, буфер терминала сохраняется. --- ## 📦 Быстрая установка Установка в профиль `web`: ```bash dsh plugin --profile web add @goodandready/dsh-remote-workspace ``` Либо стандартная команда DSH: ```bash dsh plugin add @goodandready/dsh-remote-workspace ``` --- ## ⚙️ Полная таблица конфигурации Настройки могут задаваться через карточку в Web UI или в файле конфигурации DSH (`settings.yaml`): ```yaml dsh-remote-workspace: activeProfileId: "prod-cloud-gpu" profiles: - id: "prod-cloud-gpu" name: "Cloud GPU VM" host: "remote.example.com" port: 22 username: "deploy" authType: "key" # "key" или "password" privateKeyPath: "/home/user/.ssh/id_ed25519" passphrase: "" password: "" remoteWorkspace: "/var/www/my-project" localMirrorPath: "/home/user/projects/my-project" ``` ### Таблица параметров | Параметр | Тип | По умолчанию | Описание | |---|---|---|---| | `profiles` | `Array` | `[]` | Список настроенных профилей удалённых хостов. | | `activeProfileId` | `string` | `""` | Идентификатор активного профиля. | | `profile.id` | `string` | `""` | Уникальный ID профиля. | | `profile.name` | `string` | `""` | Отображаемое имя в интерфейсе. | | `profile.host` | `string` | `""` | Доменное имя или IP-адрес сервера. | | `profile.port` | `number` | `22` | Порт SSH. | | `profile.username` | `string` | `""` | Имя пользователя для входа. | | `profile.authType` | `string` | `"key"` | Метод аутентификации: `"key"` или `"password"`. | | `profile.privateKeyPath`| `string` | `""` | Путь к файлу приватного SSH-ключа. | | `profile.privateKey` | `string` | `""` | Содержимое приватного ключа в формате PEM. | | `profile.passphrase` | `string` | `""` | Пароль для расшифровки приватного ключа. | | `profile.password` | `string` | `""` | Пароль для входа по логину и паролю. | | `profile.remoteWorkspace` | `string` | `""` | Путь к каталогу проекта на удалённом сервере. | | `profile.localMirrorPath` | `string` | `""` | Путь к локальному зеркалу проекта. | | `profile.agentPath` | `string` | `""` | Сокет SSH-агента. Пустое значение использует `SSH_AUTH_SOCK`. | | `profile.proxyCommand` | `string` | `""` | OpenSSH ProxyCommand. Токены: `%h` `%p` `%r` `%n`. | | `profile.jumpHosts` | `string[]` | `[]` | Идентификаторы бастионов, первый — вход. | | `profile.jumpHostId` | `string` | `""` | Бастионы через запятую, если `jumpHosts` пуст. | | `profile.environment` | `string` | `""` | Окружение для группы и фильтра кластера. | | `profile.tags` | `string[]` | `[]` | Метки. Фильтр кластера требует совпадения каждой. | | `profile.location` | `string` | `""` | Подпись места. | | `profile.description` | `string` | `""` | Заметка. | | `terminalFontFamily` | `string` | `""` | Семейство шрифта терминала. Пусто — стандартный моноширинный набор. | --- ## 🔌 Справочник инструментов агента ### `remote_exec` Выполняет shell-команду на активном удалённом сервере. - **Параметры**: - `command` (`string`, обязательно): Текст команды. - `cwd` (`string`, опционально): Рабочий каталог на сервере (по умолчанию `remoteWorkspace`). - **Возвращает**: `{ exitCode: number, stdout: string, stderr: string }` ### `remote_fs` Файловые операции через SFTP. - **Параметры**: - `action` (`string`, обязательно): `"read"`, `"write"`, `"stat"`, `"list"`, `"mkdir"`, `"remove"`. - `path` (`string`, обязательно): Путь к файлу или каталогу. - `content` (`string`, для записи): Содержимое файла при action `"write"`. - `recursive` (`boolean`, для удаления): Рекурсивное удаление. - **Возвращает**: Объект результата в зависимости от действия (`{ content }`, `{ stat }`, `{ entries }`, `{ ok: true }`). ### `remote_sync` 3-сторонняя синхронизация между локальным зеркалом и сервером. - **Параметры**: - `direction` (`string`, обязательно): `"pull"` (сервер → локально) или `"push"` (локально → сервер). - `force` (`boolean`, опционально): Принудительная перезапись при конфликтах. - `dryRun` (`boolean`, опционально): Симуляция изменений без записи на диск. - **Возвращает**: Сводку синхронизации со списком изменённых файлов и конфликтов. ### `remote_tunnel` Управление SSH-туннелями проброса портов. - **Параметры**: - `action` (`string`, обязательно): `"start"`, `"stop"` или `"list"`. - `localPort` (`number`, для start): Локальный порт прослушивания. - `remotePort` (`number`, для start): Целевой удалённый порт. - `tunnelId` (`string`, для stop): Идентификатор туннеля для закрытия. - **Возвращает**: `{ tunnelId, localPort, remotePort }` или список туннелей `{ tunnels: [...] }`. ### `remote_hosts` Список хостов для модели без секретов. - **Параметры**: - `query` (`string`, обязательно): Поиск по имени, хосту или идентификатору. Пустая строка показывает все. - **Возвращает**: Markdown-таблицу. Пароль, ключ, путь к ключу, passphrase, сокет агента и proxy command не включаются. ### `remote_cluster` Одна команда на отфильтрованном наборе хостов. - **Параметры**: - `command` (`string`, обязательно): Команда оболочки. - `environment` (`string`): Точное имя окружения. - `tags` (`string`): Метки через запятую. Нужны все. - `aliases` (`string`): Идентификаторы или имена через запятую. - `maxWorkers` (`number`): Параллельность, по умолчанию 8. - **Возвращает**: Строку на каждый хост: успех, код выхода, длительность, stdout, stderr и ошибка. - **Ограничение**: Сбой соединения одного хоста не останавливает остальные. --- ## 🌐 Справочник HTTP API Все маршруты зарегистрированы с префиксом `/dsh-remote-workspace`: | Метод | Путь | Описание | Тело запроса | |---|---|---|---| | `GET` | `/dsh-remote-workspace/state` | Возвращает профили, активный профиль и туннели. | — | | `POST` | `/dsh-remote-workspace/profiles/save` | Создание или сохранение профиля. | JSON профиля | | `POST` | `/dsh-remote-workspace/profiles/delete` | Удаление профиля. | `{ id: string }` | | `POST` | `/dsh-remote-workspace/profiles/active` | Установка активного профиля. | `{ id: string }` | | `POST` | `/dsh-remote-workspace/test` | Проверка связи и замер пинга. | JSON профиля | | `POST` | `/dsh-remote-workspace/browse` | Получение списка файлов для браузера папок. | `{ profile: object, path: string }` | | `POST` | `/dsh-remote-workspace/sync` | Запуск синхронизации. | `{ direction: "pull" \| "push", dryRun?: boolean, force?: boolean }` | | `POST` | `/dsh-remote-workspace/profiles/import-ssh-config` | Импорт `~/.ssh/config` или переданного текста. | `{ content?: string }` | | `POST` | `/dsh-remote-workspace/profiles/test-group` | Проверка сохранённых профилей по списку id. | `{ ids: string[] }` | | `POST` | `/dsh-remote-workspace/cluster` | Команда на отфильтрованных сохранённых профилях. | `{ command, environment?, tags?, aliases?, maxWorkers? }` | | `GET` | `/dsh-remote-workspace/file/download` | Поток удалённого файла. Query: `profileId`, `filePath`. | — | | `POST` | `/dsh-remote-workspace/file/upload` | Загрузка тела файла. Query: `profileId`, `filePath`. | байты файла | | `POST` | `/dsh-remote-workspace/terminal/font` | Сохранение шрифта терминала. | `{ fontFamily: string }` | | `POST` | `/dsh-remote-workspace/auth/keyboard` | Ответ keyboard-interactive. | `{ id, answers }` | --- ## 📄 Лицензия MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY) ### 13. Пакетная синхронизация и диагностика (v0.3.1) - **`🚀 Быстрый Tarball поток`**: Высокоскоростная передача структуры каталогов через потоковый `tar -czf` без задержек пофайлового обхода. - **`🩺 Диагностика сервера (remote_diagnose)`**: Мгновенный аудит занятых портов (`ports`), срабатываний OOM Killer (`oom_killer`), дискового пространства (`disk`) и crash-логов служб. - **`📥 Импорт из ~/.ssh/config`**: Автоматическое добавление хостов, ключей и bastion-узлов в зашифрованный `.env` vault. - **`🗃️ Менеджер переменных окружения (remote_env)`**: Атомарное изменение и безопасный просмотр удалённых `.env` файлов с маскированием паролей. - **`📡 Мониторинг аномалий`**: Оповещение о переполнении диска (<10%), дефиците ОЗУ (<5%) и циклических перезапусках контейнеров через шину событий Cordis (`remote-workspace/alert`). ### 14. Совместимость с DSH 0.2.0-rc.1 и комплексное повышение надёжности (v0.3.11) - **`⚙️ Формы настроек DSH 0.2.0-rc.1`**: Volatile-свойства схемы `Config` и динамическая регистрация `whileServed` в клиенте (#76). - **`🛠️ Исправление маршрутов .env и диагностики`**: Восстановлена работоспособность эндпоинтов просмотра/сохранения `.env` и запуска системной диагностики (#77). - **`⏱️ Сборщик простаивающих терминальных сессий`**: Автоматическое завершение брошенных PTY-сессий и освобождение выделенных SSH-каналов (#78). - **`🛡️ Защита жизненного цикла туннелей`**: Взаимное закрытие сокетов и потоков при обрыве связи, точный счётчик активных подключений (#79). - **`📦 Устойчивость tar-синхронизации`**: Перехват системных ошибок spawn утилиты `tar` и уничтожение зомби-процессов (#80). - **`🔒 Экранирование команд shell`**: Строгая валидация числовых параметров и POSIX-экранирование одинарными кавычками в Docker, диагностике и файловых операциях (#81). - **`💾 Синхронизация бинарных файлов`**: Чтение сырых Buffer в `MirrorSyncService.pull` без искажений кодировки UTF-8 (#82). - **`👀 Устойчивость файлового вотчера`**: Обработка ошибок `fs.watch` (`ENOSPC`) и блокировка параллельных наложений синхронизации (#83). - **`⚡ Гейт поллинга по видимости вкладки`**: Отключение фоновых опросов при скрытой вкладке браузера (#84).