сайт в бете
нашли баг? напишите
левин. записаться
← все статьи
Автоматизация 2026-09-28

Голосовой канал для команды и агента: что добавилось к обычному чату

Форк Buzz получил постоянные голосовые комнаты, текстовый чат внутри комнаты и список ников. Разбираю, зачем это понадобилось поверх обычного чата и на чём сломался первый тест вдвоём.

Л
Проект Левин
автор

Обычный текстовый чат у нас работал. Задачи агенту ставились, ссылки летали, всё логировалось. Но один сценарий он не закрывал: «сидеть фоном вместе» — зайти утром, работать молча, иногда перекинуться словом. Для этого нужен голос, который просто есть всегда. Созвон, который собирают под задачу, тут не помогает.

Ниже — что я доделывал в форке Buzz, в какой последовательности и что вылезло на первой же живой проверке.

Почему huddle не подошёл

В Buzz уже был huddle. Технически это голос. Практически — совсем другая история.

Huddle живёт в эфемерном канале и умирает, как только перестают продлевать аренду (за это в реле отвечает huddle_renewer). Отсюда главное ограничение: комната не может существовать пустой. А вся ценность сценария «подсесть» держится именно на пустой комнате, которая ждёт. Ты открываешь список каналов и видишь: вот голосовая, в ней уже кто-то есть. Заходишь без звонка и без приглашения.

Если комнаты нет, пока в ней никого — приходится звонить. Звонок уже событие, к нему готовятся. Фоновое присутствие так не работает.

Шаг первый: тип канала voice

Первая правка — свой тип канала voice в форке. Постоянная голосовая комната, как в Discord: висит в списке, живёт без участников, подключает к голосу по клику. Ветка feat/voice-channels в отдельной рабочей копии, upstream не трогаем.

Вот и весь ответ на вопрос «что добавилось к обычному чату» на первом шаге. Канал, главное содержимое которого — присутствие. Сообщения в нём вторичны.

Шаг второй: чат внутри комнаты и ники в списке

Комната заработала, и сразу обнаружились две дырки.

В голосе постоянно возникает то, что голосом не передашь. Ссылка, файл, кусок лога, задача агенту. За всем этим приходилось уходить в другой канал — то есть разрывать то самое совместное присутствие, ради которого комнату и заводили. Отсюда текстовый чат внутри голосовой комнаты. Голос он не заменяет, он просто снимает необходимость выходить.

Число в боковом списке не отвечает на вопрос, из-за которого туда смотрят. Показывалось «3». Но смотрят туда ради «кто» — чтобы решить, подсаживаться ли вообще. Три незнакомых человека и три коллеги по текущей задаче — это разные решения. Поэтому в списке появились ники участников.

Оба пункта про одно и то же: комната должна отвечать на вопросы, с которыми в неё заходят.

Что показала проверка вдвоём

19 августа мы проверили это живьём, двумя людьми. Четыре симптома:

Симптом Причина
Не слышал собеседника Комната раскололась: два созвона в одной голосовой
«Меня выбило», когда второй зашёл То же самое: интерфейс перескочил на чужой созвон
Выйти и зайти помогло При повторном входе история уже была получена
Слышно плохо Не объяснено. Отдельная история

Логи реле свели три симптома из четырёх к одной причине. Признаком «реле дошлёт историю» служил таймаут в 250 мс. Не пришло за 250 мс — значит, истории нет, создаём новый созвон. В итоге второй участник заходил и оказывался в отдельном созвоне внутри той же голосовой комнаты. Отсюда и «не слышу», и «меня выбило»: интерфейс перескакивал на чужой созвон. А «выйти и зайти помогло» — просто потому что на второй попытке история успевала дойти.

Это починено в отдельном issue. Но интересно другое.

Таймаут как признак — самая живучая ошибка

Три разных жалобы от двух человек, один корень: время ответа использовалось как признак состояния. Классика. Локально всё работает, потому что 250 мс хватает с запасом. У двух живых людей с живой сетью — уже не хватает.

Вывод, который я забрал себе: если условие в коде выглядит как «подождём N миллисекунд и решим», это не проверка состояния. Это ставка на сеть. У ставки должен быть явный признак — флаг, счётчик, признак конца потока, что угодно, кроме секундомера.

Чего тесты достанут, а чего нет

Отдельный issue я завёл про стенд: чтобы такие вещи вылавливала машина, до того как в них упрутся живые люди посреди созвона.

Что стенд достанет:

  • расщепление комнаты при одновременном входе двух клиентов;
  • гонку с историей при медленном реле — искусственная задержка выше таймаута воспроизводит баг детерминированно;
  • состояние бокового списка: кто показан, совпадает ли с фактическими участниками.

Чего не достанет — «слышно плохо». Четвёртый симптом так и остался необъяснённым. Качество звука зависит от кодека, сети, микрофона, эха в комнате. Автотест на такое либо врёт, либо превращается в отдельный проект. Пока держу как отдельную историю и проверяю руками.

Полезное разделение: всё, что про состояние и синхронизацию, надо загонять в стенд. Физический мир — звук, микрофон, сеть — остаётся на живую проверку, и это нормально. Не каждая проблема обязана стать автотестом.

Где это всё сейчас

Постоянные голосовые комнаты работают. Текстовый чат внутри и ники в списке — следующий шаг по тому же сценарию. Расщепление комнаты починено. Стенд в работе, качество звука — открытый вопрос.

К обычному чату добавилось, если коротко: место, которое существует, когда в нём никого нет, и показывает, кто в нём есть.

теги #голосовой канал#buzz#форк#webrtc#командная работа#автоматизация

один разговор — и поймём, чем я могу помочь.

В эпоху ИИ человеку нужен человек. Сяду рядом и доведу до результата — встреча длится столько, сколько нужно. Без скрипта продаж и пакетов «за 999 000 ₽». Если пойму, что помочь не смогу, — скажу сразу.