Как DSH Mobile отслеживает ответы ИИ-агента без Google Push

DSH Mobile — неофициальный Android-клиент для DeepSeek Harness, среды запуска ИИ-агентов на компьютере. С телефона можно открыть чат проекта, отправить задачу или фотографию, а также ответить на вопрос агента. При этом у приложения нет облачного сервера, а связь с компьютером строится напрямую или через relay.
В версиях 0.9.x разработчик переработал механизм уведомлений. Приложение больше не держит постоянное соединение в ожидании результата и не использует Google Firebase Cloud Messaging. Вместо этого оно сохраняет сведения об отправленной задаче и самостоятельно проверяет ее состояние. Такой подход позволяет переживать выгрузку приложения Android и перезагрузку телефона.
Квитанция подтверждает прием, но не завершение задачи
После отправки команды компьютерный плагин возвращает квитанцию. Она означает только, что команда принята плагином: агент может еще несколько минут выполнять работу, запросить уточнение или завершиться с ошибкой. Для получения фактического результата в DSH Mobile появился отдельный запрос к плагину, который только читает состояние задачи и ничего не запускает повторно.
Проверка доступна лишь для задач, на которые у конкретного устройства есть квитанция. Если запросить сведения о чужой задаче или о команде, отправленной с компьютера, приложение получит нейтральный статус unknown. Поэтому по ответу нельзя определить даже существование такой задачи.
Состояние определяется по журналу DSH, а не по содержимому открытого окна чата. Плагин различает пять вариантов: queued для задачи в очереди, running для уже начавшегося выполнения, waiting для ожидания ответа или подтверждения, а также completed и failed для завершившихся запусков. Во всех неоднозначных случаях возвращается unknown — например, если выполнение отменили или журнал еще не дочитан.
Позиция чтения журнала хранится в SQLite, поэтому после перезапуска компьютера разбор продолжается с прежнего места. Тексты задач и аргументы инструментов в этой базе не сохраняются. Если агент ждет ответа, телефон показывает «Нужен ответ» и продолжает наблюдение. Ответить на вопрос можно с телефона, но подтверждать действия агента по-прежнему требуется на компьютере: увидеть ожидание и обладать правом подтверждения — разные функции.
Long polling вместо постоянного подключения
Наблюдение за задачами отделено от потока SSE, который используется для открытого чата. Сервер выдает непрозрачный токен текущего состояния, а следующий запрос с этим токеном может оставаться открытым до изменения результата или истечения тайм-аута. Если время вышло, но состояние не изменилось, сервер возвращает обычный ответ 200 — это штатная ситуация, а не ошибка.
За опрос и очередь уведомлений отвечает единый координатор, поэтому одновременно выполняется не более одного запроса. Для одной задачи ожидание длится до 25 секунд. Если активны задачи в нескольких чатах, каждому чату выделяется не более секунды за проход. При 32 группах полный цикл может занимать до 32 секунд без учета сети и работы компьютера. При отсутствии сети цикл приостанавливается, а при недоступности компьютера повтор выполняется с ограниченной задержкой.
Пока есть активные наблюдения, Android foreground-служба показывает карточку «Ждём результат задачи». После завершения последней задачи и показа уведомления служба останавливается. Это наблюдение проверялось на одном телефоне, поэтому разработчик не распространяет результат на все версии Android и прошивки.
Восстановление после выгрузки и перезагрузки
Перед отправкой команды приложение сохраняет идентификатор сессии, запроса и срок действия наблюдения в отдельной зашифрованной записи. Если плагин явно отказал или точно известно, что команда не была доставлена, запись удаляется. При неясном результате она остается, и после сбоя телефон снова проверяет судьбу уже отправленной задачи, не создавая дубль.
При завершении процесса Android служба использует режим START_STICKY, а после перезагрузки и разблокировки телефона или обновления приложения запуск выполняет специальный получатель. Перед восстановлением проверяются наличие живых наблюдений и неотправленных уведомлений, сохраненная привязка и разрешение на уведомления. Если приложение принудительно остановили в настройках Android, автоматического запуска не будет — его придется открыть вручную.
Наблюдение действует 24 часа с момента отправки. Одновременно хранятся до 32 наблюдений и до 32 еще не показанных уведомлений; при переполнении сохраняются новые записи. Уведомление содержит название чата и открывает его по нажатию. Карточка «Нужен ответ» исчезает после открытия чата или отправки ответа с телефона либо компьютера.
Какие ошибки исправили в версиях 0.9.x
Одной из самых заметных проблем была задержка при ответе агенту. В предрелизной сборке полный список сессий читался четыре раза, а на компьютере с более чем 4000 сессиями это занимало около 11 секунд. Вместо полного обхода адаптер стал читать заголовок только нужной сессии, проверять рабочее пространство и повторно убеждаться, что сессия не была перенесена или архивирована. Полный путь от нажатия до реакции агента сократился примерно с 17 до 2–4 секунд в проведенных тестах.
Ранее ответы на вопросы агента хранились только в памяти плагина и исчезали после его перезапуска. Начиная с 0.9.1 они сохраняются в SQLite, включая ответы с компьютера и пропуски вопросов. История ограничена 20 ответами на чат и 128 ответами в общей сложности, срок хранения каждой записи составляет 24 часа и не продлевается после перезапуска. Дополнительно снимок чата ограничили размером 1 МиБ JSON, чтобы длинная история не мешала загрузке.
В версии 0.9.2 исправили зависшее ожидание после удаления задачи из очереди. Теперь такое наблюдение закрывается со статусом rejected, а оставшиеся после версии 0.9.1 записи проверяются при запуске и также завершаются. В обновлении 0.9.3 обработка стала шире: неудачная задача возвращает failed, а остановленная пользователем — rejected с причиной user-stop, без ошибочного уведомления.
Загрузка фотографий и защита состояния
При передаче файлов через relay загрузки размером примерно от 5 МБ могли обрываться. Причиной оказался лимит на короткие сообщения: хвосты TLS-блоков ошибочно считались отдельными мелкими сообщениями, и после превышения порога в 64 сообщения поток закрывался. В версии 0.9.2 каждый крупный блок освобождает слот лимита, сохраняя ограничение для потока из одних коротких сообщений. Для исправления необходимо обновить не только APK, но и relay с плагином.
Для потоковой отправки вложений в приложении установлен отдельный тайм-аут 120 секунд вместо 15. Изображения HEIF и HEIC на Android 9 и новее перед отправкой преобразуются в JPEG, уменьшаются до 4096 пикселей по стороне и ограничиваются 16 МиБ памяти. Файл сохраняется в цветовом пространстве sRGB с качеством 90, а общий лимит вложения остается равным 20 МиБ. Для диагностики проблем добавлены сообщения в logcat с тегом DshMobileUpload.
Наконец, плагин получил защиту от несогласованности после восстановления SQLite из резервной копии. Рядом с базой хранится отдельный журнал с идентификатором эпохи и счетчиками принятых и завершенных команд. Если он указывает на более новые данные, чем база, плагин включает режим recovery_hold и блокирует новые изменяющие запросы до проверки владельцем. Историю при этом можно читать, а после сверки команд блокировка снимается созданием новой эпохи. Это не доказывает, что потерянная команда не выполнялась, но предотвращает ее неосторожный повтор.


