y83 code / инструмент

Удалённые агенты

Экран «Удалённые агенты» — список SSH-серверов, на которых работает codex app-server, и управление SSH-туннелями к ним. Подпись хаба: «SSH-серверы и удалённые агенты Codex», тайл на главной — «Серверы».

раздел реестра
AI и агенты
код
1 783 строк в 5 файлах
документ
15-remote.md
Раздел: AI & Agents
Идентификатор: remote · Экран: ui/codex/RemoteAgentsScreen.kt (683) + RemoteAgentsFormat.kt
Снимок: 2026-10-11

1. Зачем и что это

Смысл схемы: телефон владельца — «мозг», он держит модели и веб-инструменты; сервер — вычислитель и файлы. Связь двусторонняя:

  • forward-туннель — телефон поднимает SSH-туннель до сервера и проксирует локальный 127.0.0.1:8789 (codex app-server) наружу;
  • reverse-туннель — сервер держит 127.0.0.1:19000, через который ходит на телефон за моделями и MCP.

Экран управляет SSH-частью: добавить сервер, проверить, подключить, перезапустить демон, посмотреть логи, перевыпустить токены, поставить окружение.

2. Как попасть

Хаб инструментов → секция AI & Agents → карточка «Удалённые агенты» (hub_remote_title).

Откуда ещёПуть
Экран терминала«Управление SSH-серверами» → Screen.RemoteAgents (MainRouter.kt:620)
Мостapp_navigate с screen: "remote_agents"

Маршрут: Screen.kt:135 ("remote" -> Screen.RemoteAgents) → MainRouter.kt:380.

3. Экран: элементы и действия

ЭлементЧто делаетГде в коде
Шапка y83@ssh :~$Терминальная шапка; цвет каретки зависит от состояния; meta: «N servers · M connected · updated HH:MM:SS»RemoteAgentsScreen:321, SSH_PROMPT
Кнопка RefreshПерезагрузка списка и статусов (700 мс анимации)refreshNow()
Кнопка AddДиалог добавления сервераonAddServer
Секция // connectedЖивые серверы, по имениsplitSshSections
Секция // offlineОстальныеТо же
Карточка сервераТочка статуса, user@host:port, host key, меткиSshServerCard
Метрики карточкиuser@host:port, host key <fp>, ssh key, setup ready/incomplete/in progress, seen HH:MM:SSsshMetaItems
Кнопки карточкиConnect / Disconnect, Test, Setup, Restart, Logs, Rotate tokenonConnect, runTest, onSetup, runService, rotateTokens
Свайп по карточкеЗапрос удаления (с подтверждением)SwipeableSshRow → ConfirmDeleteDialog
Шторка выводаРезультат сервисной операции (restart / logs / rotate)ServiceDialogState
Диалог SetupПрогресс установки node.js / codex / systemdSshSetupDialog
Диалог host keyПредупреждение о смене host key с отпечаткамиHostKeyTrustRequest
SnackbarТипизированные уведомления (успех / предупреждение / ошибка)TypedNoticeBar

4. Возможности подробно

Статус сервера

sshStatusKind(server, tunnelConnected) — приоритет состояний:

СостояниеУсловиеМетка
CONNECTEDтуннель живойconnected
ONLINEstatus == reachableonline
FAILEDstatus == auth_failedauth failed
OFFLINEиначеoffline

Живой туннель важнее результата последней проверки: если tunnelIsLive(status, localPort) вернул true, сервер показывается как connected, даже если проверка SSH проходила давно. Секции // connected и // offline строятся по этой же функции, поэтому всегда согласованы с точками в карточках.

Проверка (Test)

runTest(server) вызывает remoteTest(host, port, username, authRef):

УсловиеПлашкаСтатус в БД
okconnected (+ версия codex, если есть)reachable
errorKind == host_key_changedhost key changed + диалог доверия—
status == auth_failedauth failedauth_failed
иначеunreachableunreachable

Плюс обновляются lastSeenAt, hostKeyFingerprint (TOFU — «trust on first use») и authType переключается на key, если result.keyInstalled.

Host key (TOFU)

Отпечаток host key запоминается при первом подключении и сверяется при каждом. applyHostKey(server) сама подтягивает текущий отпечаток (remoteHostKey) и записывает его. Если отпечаток поменялся после того, как был зафиксирован, экран показывает предупреждение с двумя значениями (known / got) и ждёт явного решения — автоматического доверия новому ключу нет.

Connect / Disconnect

Подключение вызывает onConnect(server.id) и через 1.5 секунды обновляет статус туннеля и host key. При разрыве метка ставится до запроса на закрытие туннеля, чтобы watchdog не успел пересоздать туннель за пользователя.

Сервисные операции

runService(server, action) — действия над codex app-server на сервере: restart и logs. Результат показывается в шторке вывода: сначала output, при пустом — error, иначе ok / no output.

Перевыпуск токенов

rotateTokens(server) вызывает remoteRotateTokens(serverId, host, port, username, password) и получает пару (ok, message). На сервере выпускаются новые per-server bearer-токены для роутера моделей и MCP. Шторка показывает «token rotated» или «token rotation failed».

Установка окружения (Setup)

Диалог SshSetupDialog шлёт POST /api/v1/remote/setup с таймаутом чтения 120 секунд (установка долгая). Ответ разбирается пофлажно:

Поле ответаЧто означает
nodeInstallednode.js установлен
codexInstalledcodex cli установлен
systemdReadysystemd-unit готов
output содержит started / runningдемон запущен

setupState становится ready, только если все три флага ok; иначе failed. Состояние пишется в локальную БД (RemoteServerEntity).

Секции и шапка

sshSummaryItems(total, live) даёт «N servers» и, если есть живые, «M connected». Время снимка — sshClockLabel в формате HH:MM:SS. Разделение на секции — splitSshSections: живые сверху, офлайн снизу, внутри — по имени (стабильный порядок).

5. Данные: БД, файлы, настройки

ЧтоГде лежитКак меняетсяКто читает
Список серверовRoom, RemoteServerEntityДиалог добавления/правки, remoteServerDao().upsertЭкран, терминал, туннели
Host key fingerprintПоле hostKeyFingerprintapplyHostKey / runTestПроверка SSH при подключении
Статус и setupПоля status, setupState, lastSeenAtrunTest, Setup-диалогЭкран
Креды сервераauthRef (пароль SSH; в базе — шифротекст, поле SecretString)Диалог сервераSSH-транспорт, туннель

6. Backend: REST/роуты

Метод и путьНазначение
POST /api/v1/remote/tunnel/openоткрыть SSH-туннель к серверу
POST /api/v1/remote/tunnel/closeзакрыть туннель
GET /api/v1/remote/tunnel/statusстатус туннеля (status, localPort, reverseHealthy, daemonRunning, lastError, reconnectCount)
POST /api/v1/remote/tunnel/reconnectпереподнять туннель
POST /api/v1/remote/tunnel/check«Check now» для одного сервера
POST /api/v1/remote/setupустановка node.js / codex / systemd на сервере
POST /api/v1/remote/serviceдействие над codex app-server (restart / logs)
GET /api/v1/remote/tokensтекущие токены роутера/MCP сервера
POST /api/v1/remote/tokens/rotateперевыпуск per-server токенов
POST /api/v1/remote/tokens/revokeотзыв токенов
POST /api/v1/remote/testпроверка SSH + host key
GET /api/v1/remote/hostkeyотпечаток host key

Роуты: backend/internal/api/v1/routes.go:389–426 (tunHandler.Open/Close/ Status/Reconnect/Check/Setup/Service/Tokens/RotateTokens/RevokeTokens).

7. Мост и MCP

ЧтоКакПримечание
Открыть экранapp_navigate с screen: "remote_agents"BridgeScreenCatalog.kt:164
Диагностика туннеляGET /api/v1/remote/tunnel/status или POST …/checkСкилл y83-remote
e2e-проверкаPROBE=1 PROBE_MCP=1 bash scripts/remote-e2e.sh <host>Скилл y83-remote

MCP-профиль /mcp/remote (на сервере) даёт агенту на Ubuntu инструменты через телефон, но не даёт управления телефоном (bridge-команд) и выгрузки токенов.

8. Настройки, конфиг, квоты и лимиты

ПараметрЗначениеГде задаётся
Порт SSH по умолчанию22RemoteServerEntity.port
Таймаут Setup120 с чтенияДиалог Setup
Connect → refresh1.5 сonConnect
Анимация Refresh700 мсrefreshNow
Статусыunknown / reachable / unreachable / auth_failedRemoteServerEntity
Setup-состоянияnone / in_progress / ready / failedТо же

9. Ловушки и ограничения

  • Здоровье туннеля = ready + reverseHealthy + daemonRunning. Поле lastError при этом может быть непустым — это история сбоев, а не текущая проблема. Ориентироваться нужно на errorActive и reverseHealthy / daemonRunning.
  • Живой туннель перекрывает статус проверки. Сервер может показываться connected, даже если последний Test был давно.
  • Смена host key требует явного решения. Автодоверия нет — это защита от MITM.
  • Setup долгий (до 120 с), а «daemon started» определяется по подстроке в выводе, а не по отдельному флагу — при нестандартном выводе будет «check manually».
  • Все строки захардкожены по-английски («connected», «setup ready» и т. д.) — экран не локализован.
  • setupState обновляется только после Setup — сервер сам туда не пишет.
  • Телефон не читает токены сервера. Для remote-профиля нужно, чтобы туннель был переподнят с per-server токеном; legacy-токен авторизует остальные инструменты, но не читает remote-файлы.
  • bridge_command на remote-профиле недоступен — в отличие от первоклассных browser-тулов.

10. Источники правды

  • app/src/main/java/com/y83/ws/ui/codex/RemoteAgentsScreen.kt — экран.
  • app/src/main/java/com/y83/ws/ui/codex/RemoteAgentsFormat.kt — статусы, секции, форматирование.
  • app/src/main/java/com/y83/ws/data/db/RemoteServerEntity.kt — модель сервера.
  • app/src/main/java/com/y83/ws/ui/agents/AgentsViewModel.kt — tunnelIsLive.
  • app/src/main/java/com/y83/ws/data/api/ — клиент: remoteTest, remoteService, remoteRotateTokens, tunnelStatus, tunnelClose.
  • backend/internal/api/v1/routes.go:389–426 — роуты remote/tunnels.
  • skills/y83-remote/SKILL.md — архитектура remote, диагностика, туннель.
  • scripts/remote-e2e.sh — e2e-проверка связки.

Источник: docs/project/hub/15-remote.md · AI и агенты · 1 783 строк в 5 файлах. Страница собирается из документации проекта при сборке сайта.