Логика простая: телефон держит модели, веб-инструменты и доступ в интернет, а сервер — вычислитель и файлы. Связь двусторонняя: телефон поднимает туннель к серверу наружу, а сервер держит обратный туннель к телефону и ходит через него за моделями и инструментами.
Возможности
- Список серверов разделён на две секции: живые сверху, остальные снизу, внутри секции по имени.
- Кнопка Refresh перечитывает список и статусы, иконка на 700 мс подсвечивается бирюзовым.
- Кнопка Add открывает форму добавления, а меню карточки — форму правки.
- Пустой список объясняет, зачем нужен сервер, и предлагает добавить первый.
- Карточка сервера: цветная точка статуса, имя, подпись состояния и строка метрик: кто подключается, адрес, отпечаток ключа сервера, метод входа, состояние установки.
- Отпечаток в карточке обрезан до 22 символов с многоточием.
- Время последней проверки видно только у неживых серверов — у живых оно не нужно.
- Метки в карточке:
setup ready,setup incomplete,setup in progress. - Кнопки под карточкой: connect или disconnect, test и, пока окружение не поставлено, setup. Ряд кнопок прокручивается по горизонтали, если не влезает.
- Меню «⋮» в карточке: править сервер, поставить окружение, перезапустить агента, показать логи агента, перевыпустить токены.
- Свайп по карточке вправо-налево просит удалить сервер и спрашивает подтверждение.
- Результат сервисных операций открывается шторкой с моноширинным выводом, который можно выделить, скопировать и закрыть.
- Проверка связи показывает плашку с результатом: connected с версией агента, auth failed, unreachable или host key changed.
- После проверки в карточке обновляются статус, время последней связи и отпечаток ключа сервера.
- Диалог установки окружения показывает результат по четырём пунктам: Node.js, CLI агента, systemd и сам демон.
- При смене отпечатка ключа сервера показывается диалог: закреплённый отпечаток и представленный, решение остаётся за пользователем.
- Все уведомления типизированы: успех, предупреждение и ошибка выглядят по-разному.
Технические данные
| Параметр | Значение |
|---|---|
| Где список серверов | локальная база приложения, таблица удалённых серверов |
| Порт по умолчанию | 22 |
| Состояния сервера | unknown, reachable, unreachable, auth_failed |
| Состояния установки | none, in_progress, ready, failed |
| Живой туннель | ненулевой локальный порт и состояние не disconnected и не failed |
| Подпись состояния | connected, online, auth failed, offline |
| Цветов точки статуса | 4 |
| Время проверки на сервере | до 25 секунд |
| Проверка из карточки | подключение 15 секунд, чтение 40 секунд |
| Проверка в форме добавления | подключение 15 секунд, чтение 20 секунд |
| Установка окружения | подключение 15 секунд, чтение 120 секунд |
| Пауза перед обновлением статуса после connect | 1,5 секунды |
| Подсветка Refresh | 700 мс |
| Длина отпечатка в карточке | 22 символа |
| Формат времени | только часы, минуты, секунды |
| Запросов серверу с этого экрана | 9 |
Девять запросов: проверка связи, чтение отпечатка ключа, доверие новому отпечатку, открытие туннеля, закрытие туннеля, статус туннеля, установка окружения, сервисная операция и перевыпуск токенов.
Как читается статус
Состояние считается по первому совпавшему правилу:
| Что видно на экране | Когда | Цвет точки |
|---|---|---|
| connected | туннель живой | зелёный |
| online | последняя проверка прошла | бирюзовый |
| auth failed | сервер не пускает по паролю или ключу | красный |
| offline | всё остальное, включая «ещё ни разу не проверяли» | серый |
Живой туннель важнее результата последней проверки: если туннель поднят, сервер показывается как connected, даже если проверка SSH была давно. Поэтому надпись connected — это не «всё работает», а «туннель сейчас жив».
Отпечаток ключа сервера
Известные отпечатки хранятся на сервере и привязаны к паре адрес-порт. Когда сервер показывает ключ, которого в списке нет, соединение обрывается и приложение показывает диалог: закреплённый отпечаток и представленный. Доверие новому даётся кнопкой, автоматического доверия нет. После доверия новый отпечаток просто добавляется в список, а проверка повторяется сама.
В базе приложения при этом лежит одно поле — для показа в карточке. Оно обновляется при открытии экрана на последний известный серверу отпечаток, без вопроса. Реального доверия это не даёт: сервер по-прежнему отвергнет ключ, пока вы не нажмёте кнопку в диалоге.
Так сделано потому, что смена ключа — это нормальное дело после переустановки сервера, но в то же время это признак подмены соединения. Решать за вас нельзя.
Защита от автоматического переподключения
После нажатия disconnect экран сразу запоминает, что сервер отключён руками, и только потом просит сервер закрыть туннель. Порядок важен: если бы запрос уходил первым, сторож за это короткое время успел бы поднять туннель заново, и кнопка «отключить» ничего бы не делала. Нажатие connect снимает эту пометку.
Что показывает установка окружения
Диалог запускает установку Node.js, CLI агента и демона на сервере. Ответ разбирается по четырём пунктам:
| Пункт | Откуда берётся |
|---|---|
| node.js | флаг установки из ответа |
| codex cli | флаг установки из ответа |
| systemd | флаг готовности из ответа |
| daemon | поиск слов «started» или «running» в выводе |
Состояние «готово» ставится только когда первые три пункта успешны; иначе «не настроено». Состояние установки живёт в базе приложения и обновляется только отсюда: сервер сам туда ничего не пишет.
Форма добавления и правки
Поля: имя, адрес, порт, имя пользователя, пароль. Пароль скрыт, есть глазок для показа. Кнопка «Test connection» проверяет сервер прямо из формы и показывает результат: connected с версией агента, auth failed или unreachable.
Сохранить нельзя, пока не заполнены адрес, пользователь и пароль. Имя можно не заполнять — тогда подставится адрес:порт. На уникальность имя не проверяется: два сервера вполне могут называться одинаково, различать их придётся по адресу.
При успешной проверке серверу доставляется отдельный SSH-ключ для этого сервера, и в карточке появляется метка ssh key. Пароль при этом никуда не девается — он остаётся в базе приложения запасным путём (например, чтобы переустановить ключ), но лежит там шифротекстом: AES-GCM на ключе AndroidKeystore, который не покидает устройство. Открытый текст существует только в памяти приложения.
Что агент умеет на удалённом сервере
Агент, который работает на самом сервере, получает отдельный профиль инструментов через телефон. Там доступны:
- веб-ИИ: спросить модель, посмотреть её состояние, распознать картинку;
- веб-поиск: поиск, чтение страницы, исследование с источниками и проверка утверждений;
- браузер телефона: снимок страницы, описание страницы, проверка в нескольких размерах окна;
- генерация картинок: создать картинку, поправить её маской/прямоугольниками или референсом стиля и посмотреть статусы задач;
- общий статус системы.
Чего там нет намеренно: управления телефоном через мост, инструментов разработки, доступа к библиотеке заготовок и выгрузки токенов. Генерация картинок отдаёт ссылку: файлы телефона удалённому агенту недоступны, поэтому картинки и файлы для распознавания читаются и сохраняются на самом сервере.
Чего агент на сервере не умеет: управлять этим списком серверов. Все подключения, перезапуски и установку окружения делает телефон.
Ограничения
- Пароль сервера хранится в базе приложения шифротекстом (AES-GCM на ключе AndroidKeystore этой установки). После первичной настройки основные операции идут по выделенному ключу
ssh key, а пароль остаётся запасным. Ключ шифрования не переносится вместе с копией базы: на новом устройстве пароль придётся ввести заново. - Установка окружения занимает до двух минут, и всё это время диалог висит.
- Пункт про демон определяется поиском слов в выводе, а не отдельным флагом. При нестандартном выводе будет написано «проверь вручную».
- Экран не опрашивает сервер по расписанию: статусы обновляются при открытии, по Refresh и после connect. Туннель, впрочем, поднимает сторож, пока открыт тред на удалённом агенте и вы не отключали сервер вручную.
- Живой туннель перекрывает результат проверки: карточка долго показывает connected при сломанном SSH-доступе, пока туннель не рвался.
- Кнопка setup исчезает, когда окружение поставлено, но пункт «Run setup» в меню карточки остаётся всегда — поставить заново можно из него.
- Удаление сервера убирает его только из приложения, на самом сервере ничего не происходит: туннель, ключ и демон остаются.
- Если туннель не поднялся, экран не сообщает об этом: переход к тредам просто не происходит, причина остаётся в журнале приложения. Проверить связь после этого нужно кнопкой test.
- Все надписи на экране английские и не переводятся.