Голосовой канал для команды и агента: что добавилось к обычному чату
Форк Buzz получил постоянные голосовые комнаты, текстовый чат внутри комнаты и список ников. Разбираю, зачем это понадобилось поверх обычного чата и на чём сломался первый тест вдвоём.
Обычный текстовый чат у нас работал. Задачи агенту ставились, ссылки летали, всё логировалось. Но один сценарий он не закрывал: «сидеть фоном вместе» — зайти утром, работать молча, иногда перекинуться словом. Для этого нужен голос, который просто есть всегда. Созвон, который собирают под задачу, тут не помогает.
Ниже — что я доделывал в форке Buzz, в какой последовательности и что вылезло на первой же живой проверке.
Почему huddle не подошёл
В Buzz уже был huddle. Технически это голос. Практически — совсем другая история.
Huddle живёт в эфемерном канале и умирает, как только перестают продлевать аренду (за это в реле отвечает huddle_renewer). Отсюда главное ограничение: комната не может существовать пустой. А вся ценность сценария «подсесть» держится именно на пустой комнате, которая ждёт. Ты открываешь список каналов и видишь: вот голосовая, в ней уже кто-то есть. Заходишь без звонка и без приглашения.
Если комнаты нет, пока в ней никого — приходится звонить. Звонок уже событие, к нему готовятся. Фоновое присутствие так не работает.
Шаг первый: тип канала voice
Первая правка — свой тип канала voice в форке. Постоянная голосовая комната, как в Discord: висит в списке, живёт без участников, подключает к голосу по клику. Ветка feat/voice-channels в отдельной рабочей копии, upstream не трогаем.
Вот и весь ответ на вопрос «что добавилось к обычному чату» на первом шаге. Канал, главное содержимое которого — присутствие. Сообщения в нём вторичны.
Шаг второй: чат внутри комнаты и ники в списке
Комната заработала, и сразу обнаружились две дырки.
В голосе постоянно возникает то, что голосом не передашь. Ссылка, файл, кусок лога, задача агенту. За всем этим приходилось уходить в другой канал — то есть разрывать то самое совместное присутствие, ради которого комнату и заводили. Отсюда текстовый чат внутри голосовой комнаты. Голос он не заменяет, он просто снимает необходимость выходить.
Число в боковом списке не отвечает на вопрос, из-за которого туда смотрят. Показывалось «3». Но смотрят туда ради «кто» — чтобы решить, подсаживаться ли вообще. Три незнакомых человека и три коллеги по текущей задаче — это разные решения. Поэтому в списке появились ники участников.
Оба пункта про одно и то же: комната должна отвечать на вопросы, с которыми в неё заходят.
Что показала проверка вдвоём
19 августа мы проверили это живьём, двумя людьми. Четыре симптома:
| Симптом | Причина |
|---|---|
| Не слышал собеседника | Комната раскололась: два созвона в одной голосовой |
| «Меня выбило», когда второй зашёл | То же самое: интерфейс перескочил на чужой созвон |
| Выйти и зайти помогло | При повторном входе история уже была получена |
| Слышно плохо | Не объяснено. Отдельная история |
Логи реле свели три симптома из четырёх к одной причине. Признаком «реле дошлёт историю» служил таймаут в 250 мс. Не пришло за 250 мс — значит, истории нет, создаём новый созвон. В итоге второй участник заходил и оказывался в отдельном созвоне внутри той же голосовой комнаты. Отсюда и «не слышу», и «меня выбило»: интерфейс перескакивал на чужой созвон. А «выйти и зайти помогло» — просто потому что на второй попытке история успевала дойти.
Это починено в отдельном issue. Но интересно другое.
Таймаут как признак — самая живучая ошибка
Три разных жалобы от двух человек, один корень: время ответа использовалось как признак состояния. Классика. Локально всё работает, потому что 250 мс хватает с запасом. У двух живых людей с живой сетью — уже не хватает.
Вывод, который я забрал себе: если условие в коде выглядит как «подождём N миллисекунд и решим», это не проверка состояния. Это ставка на сеть. У ставки должен быть явный признак — флаг, счётчик, признак конца потока, что угодно, кроме секундомера.
Чего тесты достанут, а чего нет
Отдельный issue я завёл про стенд: чтобы такие вещи вылавливала машина, до того как в них упрутся живые люди посреди созвона.
Что стенд достанет:
- расщепление комнаты при одновременном входе двух клиентов;
- гонку с историей при медленном реле — искусственная задержка выше таймаута воспроизводит баг детерминированно;
- состояние бокового списка: кто показан, совпадает ли с фактическими участниками.
Чего не достанет — «слышно плохо». Четвёртый симптом так и остался необъяснённым. Качество звука зависит от кодека, сети, микрофона, эха в комнате. Автотест на такое либо врёт, либо превращается в отдельный проект. Пока держу как отдельную историю и проверяю руками.
Полезное разделение: всё, что про состояние и синхронизацию, надо загонять в стенд. Физический мир — звук, микрофон, сеть — остаётся на живую проверку, и это нормально. Не каждая проблема обязана стать автотестом.
Где это всё сейчас
Постоянные голосовые комнаты работают. Текстовый чат внутри и ники в списке — следующий шаг по тому же сценарию. Расщепление комнаты починено. Стенд в работе, качество звука — открытый вопрос.
К обычному чату добавилось, если коротко: место, которое существует, когда в нём никого нет, и показывает, кто в нём есть.