Интерфейс приложения¶
Что стоит реализовать в мобильном клиенте, в порядке приоритета, и что произойдёт, если пункт пропустить. Механика кадров — в Протоколе v1.
Обязательный минимум¶
Живое соединение по WebSocket, лента сообщений и ввод текста.
- Делать: подключение с
hello, лента ответов (message), поле ввода →textс постояннымid, индикатор связи, переподключение с паузой. - Если не делать: приложение не работает как чат. Без переподключения любой сбой сети (мобильный интернет, сон устройства) навсегда «зависает» — лента перестаёт отвечать, а пользователь думает, что агент сломался.
Кнопки карточек¶
Кадр prompt несёт text и options[{id,label,style}]. Для exec_approval сервер дополнительно
шлёт поля command (точная команда) и description — покажите команду рядом с кнопками,
чтобы человек видел, что одобряет. Нажатие → один кадр prompt с prompt_id и option_id; сервер
подтверждает prompt_done (или prompt_done{expired:true}). Карточку может закрыть только то
устройство, которому она показана: ответ с другого соединения получает forbidden.
Типы карточек:
exec_approval— одобрение опасной команды.options—once/session/always/deny. Рядом с кнопками показывайтеcommandиdescription.slash_confirm— подтверждение слэш-команды:once/always/cancel.clarify— уточняющий вопрос:c0,c1, … плюсother(«свой ответ», вместе сtext).
Оформление: style — primary (основная кнопка), danger (опасная), пусто — обычная.
- Если не делать: агент вынужден спрашивать текстом, а пользователю — печатать
/approveи подобное вручную. Опасные действия либо не проходят, либо проходят менее осознанно; уточнения приходится вводить вручную. Ответ на карточку не дублируйте обычнымtext— иначе он уйдёт агенту как лишнее сообщение.
Валидация на сервере
Не подставляйте option_id наугад: сервер сам заменит недопустимое значение на безопасное
(deny / cancel) и вернёт фактический выбор в prompt_done.option_id.
Медиа в обе стороны¶
Отправка (вверх). sendImage / sendDocument / sendVoice / sendMedia. Кадры media,
kind ∈ image/video/document/audio. Файл ≤ 8 МиБ — одним кадром; крупнее — кусками по
~3 МиБ с постоянным id (SDK делает это сам).
Приём (вниз). Кадры media от агента (image/voice/video/document/animation):
собирайте data по id, показывайте/сохраняйте при final: true; если есть url — качайте по
ссылке.
- Если не делать: пользователь не может прислать фото/файл/PDF агенту, а сгенерированные
агентом картинки, отчёты и голосовые просто исчезают. Не собирая куски по
id, вы получите битые файлы для всего крупнее 3 МиБ.
Потоковая выдача (правка по id)¶
Если на стороне агента включён стриминг, ответ приходит целиком, а затем правится кадрами
message с тем же id и полем edit: true. Приложение должно хранить сообщения по id и при
edit: true перерисовывать существующее, а не добавлять новое.
{"t":"message","id":"r1","text":"Считаю","edit":true,"chunk":{"index":0,"total":1},"final":false}
{"t":"message","id":"r1","text":"Считаю ответ…","edit":true,"chunk":{"index":0,"total":1},"final":true}
- Если не делать: каждый кадр правки покажется как новое сообщение — в ленте появятся дубли
(«Считаю», «Считаю ответ…» отдельными пузырями). Текст при этом не теряется: если
edit_messageне удался, шлюз дошлёт хвост обычнымmessage.
Аватар¶
Аватар — HTML-страница в WebView, реализующая контракт window.hermesAvatar:
window.hermesAvatar = {
setState: (state) => {}, // 'idle' | 'thinking' | 'speaking'
speak: (text) => {}, // просьба озвучить/показать реплику агента
setVolume: (level) => {}, // уровень микрофона при записи, 0..1
};
URL страницы приходит в кадре ready (avatar_url; задаётся MOBILE_AVATAR_URL). Если URL пуст —
используйте свою страницу (в examples/react-native/avatarHtml.ts есть минимальная с тем же
контрактом). Состояния берите из кадров avatar (idle/thinking/speaking).
- Если не делать: «живости» нет — пользователь не видит, что агент думает/говорит. Это
необязательный пункт: без аватара протокол работает полностью, но пропадает обратная связь во
время долгого ответа (частично её закрывает индикатор
typing).
Голос¶
Запись WAV (16 кГц, моно, PCM 16 бит) → кадр audio с format: "wav" и data в base64. Если в
приложении есть своё распознавание — заполните text, тогда агент получит и текст, и аудио; иначе
Hermes распознает речь сам (при настроенном STT на машине агента). Голосовое от агента приходит
кадром media с kind: "voice".
- Если не делать: нет голосового ввода и озвучки ответов. Не используйте неупакованные форматы и частоту выше 16 кГц: распознавание ожидает WAV 16 кГц — иначе качество распознавания падает, а запись «съедает» лимит 8 МиБ быстрее.
Локальная история¶
Кэшируйте ленту на устройстве, чтобы при запуске и при обрыве сети пользователь видел последние
сообщения, а id уже отправленных сообщений сохранялись.
- Если не делать: пустой экран при каждом запуске, а после повторной отправки после обрыва —
дубли. Источник истины по истории всё равно Hermes (диалог не теряется на сервере), но локальный
кэш нужен для приятного UX и для идемпотентности (повтор с тем же
idсервер игнорирует).
Сводка: что будет, если пропустить¶
| Пункт | Без него |
|---|---|
| Переподключение | связь не восстанавливается после сбоя сети |
| Кнопки карточек | одобрение/clarify/подтверждение приходится печатать текстом |
| Медиа в обе стороны | файлы и картинки «теряются»; крупные — битые |
Правка по id |
дубли в ленте при стриминге |
| Аватар | нет обратной связи во время долгого ответа (некритично) |
| Голос | нет голосового ввода и озвучки |
| Локальная история | пустой экран при запуске, дубли после обрыва |