Плагин канала, который подключает ассистента OpenClaw к мессенджеру MAX через MAX Bot API (platform-api2.max.ru). Люди пишут вашему боту в MAX — в личном диалоге или в группе, — и агент OpenClaw отвечает там же: с медиа, кнопками и голосом.
Плагин нужен тем, кто пользуется OpenClaw и хочет, чтобы ассистент был доступен в MAX. Понадобятся токен бота MAX (ботов создают организации на business.max.ru) и собственный gateway OpenClaw.
Версия 0.8.0. Изменения: CHANGELOG.md.
- Поддерживаемые типы сообщений
- Требования и совместимость
- Установка
- Быстрый старт
- Транспорты: webhook и long polling
- Справочник настроек
- Доступ и политики
- Действия инструмента
messageи где они разрешены - Медиа и локальные файлы
- Голосовые сообщения
- Команды и меню
- Статус хода
- Несколько аккаунтов и наследование
- Прокси, адрес API и сертификаты Russian Trusted CA
- Секреты и SecretRef
- Надёжность
- Лимиты MAX API
- Безопасность
- Конфиденциальность
- Устранение неполадок
- Обновление с 0.7
- Разработка
- Благодарности, авторы и лицензия
| Тип | Входящие (MAX → агент) | Исходящие (агент → MAX) |
|---|---|---|
| Текст | Да; упоминания распознаются по разметке MAX, @username и шаблонам упоминаний ядра |
Да; markdown переводится в диалект MAX (++underline++) и делится на части по 4000 символов; если MAX отклоняет разметку, текст один раз отправляется заново без неё |
| Изображение | Скачивается и передаётся агенту как медиа | Да; несколько изображений и видео уходят альбомами до 12 штук; публичная https-ссылка на изображение передаётся в MAX ссылкой |
| Видео | Скачивается (лимит размера — mediaMaxMb) |
Да |
| Аудио и голосовые | Скачиваются; если MAX прислал расшифровку, используется она, иначе запись распознаёт ядро OpenClaw | Да; asVoice / audioAsVoice отправляет вложение MAX audio |
| Файл | Скачивается | Да: из разрешённого локального пути, по URL или содержимым в buffer |
| Стикер | Код стикера и изображение | Действие sticker по коду |
| Контакт | Имя и телефоны из VCard, профиль MAX | sendAttachment с type="contact" |
| Геолокация | Координаты и ссылка на карту | sendAttachment с type="location" |
| Карточка ссылки (share) | Заголовок, описание и ссылка | — |
| Пересланное сообщение | Текст и вложения с исходным автором; собственный комментарий отправителя идёт первым | — |
| Ответ на сообщение | Цитата исходного сообщения (до 1000 символов) и его автор | Да, ответом MAX (replyTo) |
| Inline-клавиатура | Нажатия кнопок: callback, подтверждения, меню команд | 7 типов кнопок (callback, link, message, clipboard, open_app, request_contact, request_geo_location); блоки presentation OpenClaw отрисовываются текстом и клавиатурами |
| Изменённое сообщение | Обрабатывается заново как новое | Действие edit; при стриминге черновика правится одно сообщение |
| Удалённое сообщение | Игнорируется (запись в лог на уровне debug) | Действие delete |
| Закрепление | — | pin / unpin в группах и каналах; delivery.pin |
| Запуск бота (deep link) | Доходит до агента как /start или /start <payload>; как и сообщения, пропускается, если старше maxEventAgeMinutes |
— |
| Индикатор набора, отметка о прочтении | — | typing_on на весь ход; mark_seen для входящих сообщений (markSeen) |
Реакции и опросы не поддерживаются.
- OpenClaw ≥ 2026.9.6 (
peerDependencies.openclaw,openclaw.compat.minGatewayVersion). Более ранние версии не работают — это проверено на 2026.9.3–2026.9.5 проверкой типов и набором тестов. В 2026.9.5 нетcreateLivePreviewLifecycleвopenclaw/plugin-sdk/channel-outbound, поэтому модуль плагина не загружается (на этой функции построен статус хода); в 2026.9.3 и 2026.9.4 вдобавок нет проверки отправителя при ответе на вопросы агента кнопками (authorizeвresolveOption), защищённого хука индикатора набора для heartbeat и формата пояснения к плану. Чтобы пользоваться плагином, обновите OpenClaw. - Node.js ≥ 22.
- MAX Bot API в том виде, в каком его описывают схема 0.0.33 и dev.max.ru/docs-api.
- Для режима webhook: публичный HTTPS-адрес на порту 443 с доверенным сертификатом, который ведёт на HTTP-сервер gateway.
Что проверено. Версия 0.8.0 работает в эксплуатации с 27 сентября 2026 года с OpenClaw 2026.9.6 в режиме webhook. Вживую проверены: старт и подписка webhook, статус канала (mode, tokenStatus), текст в личном чате, длинные ответы по частям, команды ядра, отправка инструментом message, включая файл из рабочего каталога агента и отказ на файл неразрешённого типа, и восстановление долговечной очереди после настоящего рестарта gateway — сообщение, которому ядро отказало во время завершения, дождалось следующего старта и получило ответ. Версии 0.7.x до этого работали в эксплуатации с текстом, медиа, кнопками, обоими транспортами и распознаванием входящих голосовых через webhook. Остальное новое в 0.8.0 покрыто автоматическими тестами и прошло независимое ревью кода, но вживую ещё не проверялось — в том числе HTTP-прокси и apiBaseUrl, разрешение SecretRef при старте gateway, статус хода, меню команд, голосовые ответы, пересланные сообщения, группы и long polling.
Из ClawHub:
openclaw plugins install clawhub:@aspalagin/openclaw-maxИз npm:
openclaw plugins install npm:@aspalagin/openclaw-maxУказывайте имя со scope —
@aspalagin/openclaw-max. Пакет npm без scopeopenclaw-max— другой проект (см. Благодарности).
После установки перезапустите gateway, чтобы он загрузил плагин. Id плагина в OpenClaw — openclaw-max, id канала — max.
Обновление: openclaw plugins update openclaw-max для установки из npm или openclaw plugins install clawhub:@aspalagin/openclaw-max --force для установки из ClawHub; затем перезапустите gateway. Перед обновлением прочитайте раздел Обновление с 0.7.
-
Создайте бота. На business.max.ru (нужна зарегистрированная организация или ИП) откройте Чат-боты → Создать, дождитесь модерации и скопируйте токен в разделе Чат-боты → Интеграция.
-
Сохраните токен в файл, который может читать пользователь gateway, например
/home/you/.openclaw/secrets/max-bot-token(права0600). Путь используется как есть: указывайте абсолютный путь;~не раскрывается. Нужен обычный файл: символическая ссылка не читается — как если бы файла не было. -
Настройте
~/.openclaw/openclaw.json: -
Перезапустите gateway:
openclaw gateway restart. -
Проверьте аккаунт:
openclaw channels statusпоказывает аккаунт,tokenSource,tokenStatusиmode;openclaw channels status --probeдополнительно обращается к MAX API. -
Напишите боту в MAX. При
dmPolicy: "pairing"вы получите код сопряжения (pairing); подтвердите его командойopenclaw pairing approve max <code>(openclaw pairing list maxпоказывает ожидающие запросы с id пользователя-отправителя). Чтобы обойтись без сопряжения, добавьте свой id пользователя вallowFrom.
Токен можно задать и через botToken (строкой или SecretRef) или переменной окружения MAX_BOT_TOKEN (только для аккаунта верхнего уровня). Порядок: botToken, затем tokenFile, затем MAX_BOT_TOKEN.
Для рабочей эксплуатации используйте webhook. MAX описывает long polling как режим для разработки и тестирования, ограниченный по скорости и сроку хранения событий, и просит в рабочей эксплуатации использовать webhook (GET /updates). Авторы других плагинов также сообщают, что через long polling голосовые сообщения могут приходить пустыми или не приходить вовсе (в частности, с Android); MAX этого не документирует, и мы пока этого не подтвердили — через webhook голосовые приходят и распознаются.
transport выбирает режим: webhook, если задан webhookUrl, иначе polling. transport: "webhook" без webhookUrl — ошибка конфигурации.
- Требования MAX: HTTPS на порту 443 с сертификатом доверенного удостоверяющего центра; ответ
200в течение 30 секунд; неудавшиеся доставки MAX повторяет, а после 8 часов сбоев снимает подписку. Каждый запрос несёт заголовокX-Max-Bot-Api-Secret. Пока подписка webhook активна, long polling не работает. - Маршрут: плагин принимает webhook на собственном HTTP-сервере gateway (отдельный порт не нужен). Путь:
webhookPath, иначе путь изwebhookUrl, иначе/max/webhook. Поставьте перед gateway обратный прокси или туннель с действительным сертификатом и пробрасывайте только этот путь. - Секрет:
webhookSecret, иначеwebhookSecretFile, иначе секрет генерируется один раз и хранится в файле состояния аккаунта. Формат, который требует MAX: 5–256 символовA–Z a–z 0–9 _ -. Секрет сравнивается за постоянное время до чтения тела запроса; при несовпадении — ответ401. - Подписка: при старте плагин удаляет подписки этого бота на другие URL и подписывает
webhookUrl. Каждые 12 минут он проверяет, что подписка на месте, и создаёт её заново, если MAX её снял. При остановке подписка сохраняется, поэтому события, пришедшие во время рестарта, MAX доставит повторно. - Обработка: HTTP-обработчик проверяет секрет, тело и дубликаты, записывает событие (см. Надёжность) и отвечает; агент работает в задаче аккаунта — по порядку внутри чата, до 4 чатов параллельно.
- Включение: сначала разверните плагин и перезапустите gateway, затем добавьте
webhookUrlи перезапустите ещё раз; убедитесь, что в логе естьMAX webhook subscribedи что сообщение доходит до агента. - Откат: задайте
transport: "polling"(или уберитеwebhookUrl) и перезапустите gateway; при старте polling подписка удаляется. Если плагин не запускается, удалите подписку вручную:curl -X DELETE "<api>/subscriptions?url=<webhookUrl>" -H "Authorization: <token>", где<api>— вашapiBaseUrl, а если он не задан —https://platform-api2.max.ru.
Плагин опрашивает GET /updates и после каждой пачки сохраняет маркер в файле состояния аккаунта, поэтому после рестарта продолжает с того же места. После ошибки он ждёт 2 с, удваивая паузу до 60 с со случайным разбросом (jitter), соблюдает Retry-After, а после 401 повторяет попытку раз в 5 минут. При старте активная подписка webhook этого бота удаляется с предупреждением (пока она есть, MAX не обслуживает GET /updates).
Все опции находятся в channels.max; те же ключи (кроме accounts и commands) принимаются в channels.max.accounts.<id>. «Собственные» опции относятся к одному боту и не наследуются именованными аккаунтами; всё остальное наследуется с уровня канала (см. Несколько аккаунтов).
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
enabled |
логический | true |
false на уровне канала выключает все аккаунты |
botToken |
строка или SecretRef | — | Токен бота. Собственная |
tokenFile |
строка | — | Абсолютный путь к обычному файлу с токеном. Собственная |
name |
строка | — | Отображаемое имя аккаунта. Собственная |
transport |
polling | webhook |
webhook, если задан webhookUrl, иначе polling |
Собственная |
webhookUrl |
строка | — | Публичный HTTPS-адрес, на который MAX отправляет события. Собственная |
webhookSecret |
строка или SecretRef | генерируется | Секрет webhook. Собственная |
webhookSecretFile |
строка | — | Файл с секретом webhook (обычный файл, не символическая ссылка). Собственная |
webhookPath |
строка | путь из webhookUrl, иначе /max/webhook |
Путь маршрута в gateway. Собственная |
webhookQueue.mode |
durable | memory |
durable |
Записывать принятые события webhook в журнал на диске или держать только в памяти |
webhookQueue.maxPending |
целое | 5000 |
Сколько принятых, но ещё не обработанных событий может ждать |
webhookQueue.overflow |
reject | drop |
reject |
При переполнении: ответить 503, чтобы MAX повторил доставку, или подтвердить событие и отбросить |
maxEventAgeMinutes |
целое ≥ 0 | 60 |
Пропускать сообщения, правки, нажатия кнопок и запуски бота старше этого срока (по времени события в MAX); 0 — без ограничения |
dmPolicy |
pairing | allowlist | open | disabled |
pairing |
Кто может писать в личных диалогах |
allowFrom |
массив id пользователей | [] |
Разрешённые отправители личных сообщений; "*" — кто угодно (обязательно при open) |
groupPolicy |
allowlist | open | disabled |
allowlist (или channels.defaults.groupPolicy ядра) |
В каких группах бот отвечает |
groups |
объект с ключами — id чатов или "*" |
{} |
Разрешённые группы и их настройки, см. ниже |
groupAllowFrom |
массив id пользователей | — | Отправители, допущенные в разрешённых группах (если у группы нет своего allowFrom) |
mentionPatterns |
{ mode: "allow" | "deny", allowIn, denyIn } |
allow | Где в группах MAX действуют шаблоны упоминаний ядра |
actionScope |
admitted | current | off |
admitted |
В каких чатах может действовать инструмент message |
notify |
логический | как в MAX (с уведомлением) | Push-уведомления при отправке; false — тихая отправка (в каналах невозможна) |
disableLinkPreview |
логический | как в MAX (превью включены), у сводок инструментов — выключены | Превью ссылок при отправке; заданное значение действует и на сводки инструментов |
markSeen |
логический | true |
Отмечать входящие сообщения прочитанными |
mediaMaxMb |
число | 20 |
Лимит размера скачиваемых и загружаемых медиа, МБ |
mediaMaxCount |
целое | 12 |
Сколько медиавложений скачивается из одного входящего сообщения, включая пересланные |
streamMode |
off | partial | block |
off |
partial — одно сообщение-черновик правится, пока ответ стримится; block — блочные ответы ядра |
streaming |
настройка стриминга ядра | выключено | streaming.mode (off, partial, block, progress) сильнее streamMode; streaming.progress.* настраивает статус хода |
textChunkLimit |
целое | 4000 |
Символов в одном исходящем сообщении; значение аккаунта в приоритете; всё, что больше лимита MAX в 4000, урезается до него |
responsePrefix |
строка | — | Префикс ответов агента, его применяет ядро OpenClaw ("auto" — имя агента) |
historyLimit |
целое ≥ 0 | умолчание ядра | Читает ядро OpenClaw: сколько последних ходов групповой сессии встроенный рантайм агента держит в промпте (нативные CLI-рантаймы ведут историю сами) |
dmHistoryLimit, dms.<userId>.historyLimit |
целое ≥ 0 | без ограничения | Читает ядро OpenClaw: то же для личных диалогов с сессией на канал (session.dmScope); значение для пользователя в приоритете |
actions |
объект { <action>: boolean } |
всё включено | false выключает действие инструмента message (send, edit, delete, sticker, sendAttachment, pin, unpin): оно скрыто из инструмента и отклоняется |
apiBaseUrl |
строка | https://platform-api2.max.ru |
Базовый адрес Bot API; только https, http — лишь для loopback |
httpProxy |
строка или SecretRef | — | HTTP(S)-прокси для всего трафика MAX; "" в аккаунте отключает унаследованный прокси |
logMessagePreview |
логический | false |
Добавлять в debug-логи превью текста длиной 50 символов |
commands |
массив { name, description } |
— | Только на уровне канала: команды бота, которые регистрируются в MAX (до 32; имя ≤ 64 символов без /, описание ≤ 128) |
accounts |
объект | — | Только на уровне канала: именованные аккаунты |
Настройки группы (groups.<chatId> или groups["*"]):
| Ключ | По умолчанию | Описание |
|---|---|---|
requireMention |
true |
Отвечать, только если бота упомянули, ответили на его сообщение или нажали его кнопку |
allowFrom |
— | Отправители, допущенные в этой группе (вместо groupAllowFrom) |
tools |
— | Политика инструментов ядра для этой группы |
disableAudioPreflight |
false |
Не распознавать голосовые без подписи ради поиска упоминания |
enabled |
true |
false — игнорировать группу при любой groupPolicy |
systemPrompt |
— | Дополнительный системный промпт для ходов в этой группе |
skills |
все | Навыки, которые может загружать ход в этой группе; [] — никакие |
Ключи markdown (отрисовка таблиц), blockStreaming (используйте streamMode или streaming.mode: "block") и blockStreamingCoalesce (ядро читает streaming.block.coalesce) схема принимает для совместимости с общим форматом конфигурации каналов, но в MAX они ни на что не влияют: ни плагин, ни ядро OpenClaw не читают их для этого канала. Они оставлены в схеме, чтобы существующие конфиги по-прежнему проходили проверку.
Личные диалоги (dmPolicy):
pairing(по умолчанию) — незнакомый отправитель получает код сопряжения; владелец подтверждает его командойopenclaw pairing approve max <code>. Отправителям изallowFromсопряжение не нужно.allowlist— только id пользователей изallowFrom.open— кто угодно; требуетallowFrom: ["*"].disabled— личные сообщения не принимаются.
Группы (groupPolicy):
allowlist(по умолчанию) — только чаты, перечисленные вgroups(или любой чат, если вgroupsесть запись"*").open— любая группа, в которой состоит бот.disabled— сообщения из групп не принимаются.
В допущенной группе непустой groups.<id>.allowFrom (а если его нет — groupAllowFrom) ограничивает, кто может обращаться к боту: сообщения, нажатия кнопок, команды и голосовые для проверки упоминания от остальных участников игнорируются, их вложения не скачиваются. "*" разрешает всем; в id принимается префикс max:. Список allowFrom для личных сообщений на группы не распространяется.
Упоминания. При requireMention (по умолчанию true) бот отвечает в группе, когда его упомянули (@username или упоминанием MAX), когда кто-то ответил на его сообщение или нажал его кнопку либо когда текст совпал с шаблонами упоминаний, настроенными в ядре OpenClaw (messages.groupChat.mentionPatterns или groupChat.mentionPatterns агента). Шаблоны, которые ядро выводит из имени агента, в MAX не используются — только явно заданные. channels.max.mentionPatterns ограничивает, где действуют шаблоны: { "mode": "deny" } отключает их для MAX, allowIn / denyIn перечисляют id групп. Голосовое без подписи в такой группе проверяется по шаблонам — по расшифровке MAX или после однократного распознавания ядром (только в допущенных группах и от допущенных отправителей).
Подтверждения и вопросы. Кнопки подтверждения (approval) могут нажимать только отправители, явно перечисленные в allowFrom; для вопросов ask_user достаточно записи "*" — но в группе такой ответ засчитывается, только если группа допущена и нажавший есть в её списке отправителей (groups.<id>.allowFrom / groupAllowFrom), когда список задан.
Как узнать id пользователя. openclaw pairing list max показывает id отправителя, который ждёт сопряжения. Id групповых чатов — отрицательные числа.
Именованный аккаунт, унаследовавший политику open, при старте пишет в лог предупреждение с путём опции, которую стоит задать. Помните об этом наследовании, когда добавляете второго бота.
Действия: send, sendAttachment, sticker, edit, delete, pin, unpin.
Цели: user:<id> для личного диалога, числовой id чата для группы или канала (например, -70000000000001). @username и ссылки max.ru MAX Bot API не поддерживает, и такие цели отклоняются с понятной ошибкой. Положительное число без префикса, на которое MAX ответил dialog.not.found, один раз пробуется заново как id пользователя.
message(action="send", target="user:12345678", message="Hello")
message(action="send", target="CHAT_ID", message="Choose:",
buttons=[[{"text":"Yes","type":"callback","payload":"yes"},
{"text":"More","type":"message"}]])
message(action="sendAttachment", target="CHAT_ID", path="report.pdf", caption="Report")
message(action="sendAttachment", target="CHAT_ID", buffer="data:text/plain;base64,SGVsbG8=", filename="hello.txt")
message(action="send", target="CHAT_ID", path="reply.ogg", asVoice=true)
message(action="send", target="CHAT_ID", message="Quiet", silent=true)
message(action="sendAttachment", target="CHAT_ID", type="location", latitude="55.75", longitude="37.62")
message(action="sendAttachment", target="CHAT_ID", type="contact", contactName="Name", vcfPhone="+70000000000")
message(action="sticker", target="CHAT_ID", stickerId="CODE")
message(action="pin", target="CHAT_ID", messageId="MID")
attachments=[…]отправляет несколько файлов: изображения и видео уходят альбомами до 12 штук, аудио и файлы — по одному. В результате естьmessageIds; элементы, которые не удалось отправить, перечислены вmediaErrors.silent=trueотправляет без push-уведомления; в каналах MAX уведомление приходит всегда.pin=trueилиdelivery.pinзакрепляет отправленное сообщение. В личных диалогах MAX закреплять нельзя: плагин пропускает вызов и возвращаетpinned: falseс причиной.
Где разрешены действия (actionScope). В ходе, который начал не владелец, действия работают только в текущем чате и в чатах, которые допускает политика входящих: в диалогах с отправителями из allowFrom или прошедшими сопряжение и в группах из groups — а если у группы есть список отправителей, то только когда автор запроса в нём есть. Без ограничений действуют только вызовы, которые OpenClaw помечает как вызовы владельца (senderIsOwner: true): ходы самого владельца, openclaw message из CLI, чат администратора в Control UI. Любой другой вызов — в том числе запуск вне разговора, у которого признака владельца нет или он ложный, как бывает у heartbeat, субагентов, запусков по расписанию, плагинами и хуками, — ограничен допущенными чатами, а в группе со списком отправителей получает отказ: автора запроса, которого можно было бы проверить, нет. Доставки ядра (ответы агента, напоминания, объявления автоматизаций) идут не через инструмент message и не затронуты. send с presentation (карточки, кнопки), который ядро доставляет без обработчика действий плагина, проверяется так же, и на него тоже действует actions.send: false. Текущий чат засчитывается только аккаунту, через который пришёл ход: действие в нём через другой аккаунт MAX (accountId) проверяется по политике того аккаунта. Отказ — это ошибка инструмента, которая возникает раньше, чем что-либо изменится в MAX; если плагин не может определить чат сообщения, он тоже отказывает. Вне чата текущего хода edit и delete работают только с сообщениями, которые отправил сам бот (автор берётся из того же запроса, что и чат); в текущем чате и для владельца — как раньше. send, sendAttachment и sticker с replyTo (а также send с presentation) могут отвечать только на сообщение того чата, куда уходят (проверка — один запрос, и только когда replyTo задан); ответ на сообщение другого чата или сообщение, чат которого определить нельзя, получает отказ.
admitted(по умолчанию) — как описано выше.current— ходы не владельца действуют только в своём чате.off— плагин ничего не проверяет (поведение 0.7).
Так инъекция промпта в одном чате не сможет править, удалять или публиковать сообщения в другом.
- Входящие медиа скачиваются после проверок доступа, в пределах
mediaMaxMb(по умолчанию 20 МБ) иmediaMaxCount(по умолчанию 12 на сообщение); о медиа, которые не загрузились, агент получает пометку. Файлы хранит медиахранилище ядра OpenClaw. - Исходящие локальные файлы читаются только загрузчиком OpenClaw SDK и только из каталогов, которые ядро разрешает агенту: его рабочего каталога (workspace), media-каталогов gateway и того, что допускает политика файловой системы ядра. Пути за их пределами — в том числе через
..или символические ссылки — завершаются ошибкойLocal media path is not under an allowed directory: …. Чтобы отправить файл из другого места, сначала скопируйте его в рабочий каталог агента. Если политика ядра даёт агенту чтение с хоста, каталог не ограничивается, зато ядро проверяет тип по содержимому: отправляются изображения, аудио, видео, PDF, документы Office, архивы и текстовые документы (.txt,.md,.csv,.json,.yaml), остальное отклоняется ошибкойHost-local media sends only allow …. Отключить эти проверки нельзя. - Содержимое в вызове:
buffer(base64 или data URL) сfilenameиcontentType; лимит размера проверяется до декодирования. Если заданы и путь, иbuffer, используется путь. - Удалённые URL скачиваются в память через загрузчик ядра с защитой от SSRF. Ссылка на изображение передаётся в MAX как есть, только если её хост — публичный https; иначе изображение скачивается и загружается в MAX.
Входящие голосовые передаются ядру OpenClaw как аудиомедиа. Если MAX прислал расшифровку, агент получает её как [Voice transcript: …], и ядро не распознаёт запись повторно; иначе ядро распознаёт аудио провайдером, настроенным в tools.media.audio. Голосовое, которое не удалось скачать и у которого нет расшифровки, доходит до агента как [Voice message: audio unavailable, no transcript].
Голосовые ответы. Канал сообщает ядру, что принимает для TTS аудиофайлы (mp3, m4a, wav, ogg, opus). Когда ответ помечен как голосовой — TTS ядра (например, /tts), директива [[audio_as_voice]] или asVoice=true у send / sendAttachment, — аудио уходит вложением MAX audio: отдельного типа «голосовое сообщение» в Bot API нет. Текст ответа доставляется один раз, отдельным сообщением. Если MAX отклоняет аудио, те же байты отправляются файлом.
channels.max.commandsпри старте аккаунта регистрирует в MAX список команд бота (PATCH /me/commands): до 32 команд.- Сообщение, которое начинается с
/, обрабатывается как команда OpenClaw. - Команды, для которых ядро описывает варианты, —
/think,/fast,/reasoning,/verbose,/usage,/elevated,/trace,/activation,/send,/tts,/session,/subagents,/acp,/tools— без аргумента отвечают меню из кнопок, текущий вариант отмечен ✓. Нажатие применяет команду так же, как ввод/think high. Меню и нажатия доступны только отправителям, которым разрешено выполнять команды. - Запуск бота по deep link (
max.ru/<bot>?start=<payload>) доходит до агента как/start <payload>.
При стриминге прогресса ядра одно служебное сообщение показывает, чем занят агент: строку статуса, план, запросы подтверждения, а с toolProgress — и строки инструментов. Оно правится не чаще раза в секунду, отправляется без уведомления и удаляется после доставки ответа. По умолчанию статус выключен.
"channels": { "max": { "streaming": { "mode": "progress", "progress": { "toolProgress": true } } } }Остальные ключи streaming.progress (label, labels, maxLines, maxLineChars, commandText) — настройки ядра. Если статус не удалось отправить, останавливается только статус; на ход это не влияет.
Один gateway может обслуживать несколько ботов MAX. channels.max верхнего уровня — аккаунт по умолчанию; именованные аккаунты находятся в accounts:
{
"channels": {
"max": {
"tokenFile": "/home/you/.openclaw/secrets/max-main-token",
"dmPolicy": "allowlist",
"allowFrom": ["12345678"],
"maxEventAgeMinutes": 30,
"accounts": {
"support": {
"tokenFile": "/home/you/.openclaw/secrets/max-support-token",
"dmPolicy": "open",
"allowFrom": ["*"]
// наследует maxEventAgeMinutes, groupPolicy, groups, … из channels.max
}
}
}
}
}Именованный аккаунт берёт с уровня канала каждую опцию, которую не задал сам, включая политики доступа и списки; собственное значение аккаунта сильнее, а списки и groups заменяются целиком. Собственные опции не наследуются: botToken, tokenFile, name, transport, webhookUrl, webhookSecret, webhookSecretFile, webhookPath. channels.max.enabled: false выключает все аккаунты, accounts.<id>.enabled: false — один. MAX_BOT_TOKEN действует только для аккаунта верхнего уровня.
Аккаунт в CLI выбирается ключом --account: openclaw message send --channel max --account support --target user:87654321 --message "Hi".
httpProxy(например,http://proxy.example.com:3128, можно с учётными данными) направляет весь трафик MAX этого аккаунта через HTTP(S)-прокси: вызовы Bot API, загрузку файлов, скачивание входящих вложений и удалённых медиа. Loopback-адреса идут мимо прокси. Учётные данные прокси никогда не попадают в логи и ошибки; в логе старта адрес выглядит какhttp://***@host:port.apiBaseUrlнаправляет плагин на другой адрес Bot API, например на тестовый стенд; префикс пути сохраняется. Принимается толькоhttps(http— лишь для loopback), поэтому токен никогда не передаётся открытым текстом.- Неверное значение любой из этих опций останавливает аккаунт при старте ошибкой с именем опции.
- Russian Trusted CA. С июля 2026 года Bot API на
platform-api2.max.ruиспользует сертификат, выпущенный Russian Trusted Sub CA Минцифры, а этого удостоверяющего центра нет в стандартном хранилище доверенных сертификатов Node.js. Плагин поставляется с сертификатами Russian Trusted Root CA и Sub CA (src/russian-trusted-ca.ts) и добавляет их к системным только для собственных соединений с MAX — в том числе внутри туннеля прокси. Общее хранилище доверенных сертификатов процесса не меняется,NODE_EXTRA_CA_CERTSне нужен.
botToken, webhookSecret и httpProxy принимают обычную строку или SecretRef OpenClaw:
"botToken": { "source": "env", "provider": "default", "id": "MAX_BOT_TOKEN" }Провайдер должен быть описан в secrets.providers ядра. Пути channels.max.botToken, webhookSecret, httpProxy (и их варианты в accounts.<id>) видны командам openclaw secrets configure, apply и audit. Gateway разрешает ссылки до старта аккаунта. Аккаунт, у которого ссылка не разрешилась, не стартует: ошибка называет опцию и ссылку, но не значение, и отката на tokenFile или MAX_BOT_TOKEN не происходит; остальные аккаунты продолжают работать. openclaw secrets audit сообщает о токене, секрете webhook или прокси, записанных в openclaw.json обычной строкой.
tokenFile и webhookSecretFile по-прежнему поддерживаются. openclaw channels status показывает tokenSource (config, file, env, none) и tokenStatus (available, configured_unavailable, missing), не раскрывая токен.
- Долговечная очередь webhook. Каждое принятое событие webhook записывается в
<stateDir>/max/inbox-<account>/до того, как MAX получит200. После рестарта или падения первыми обрабатываются необработанные события, в исходном порядке внутри каждого чата. Событие считается завершённым, как только ядро приняло ход агента (прерванный ход ядро возобновляет само). Если событие не удалось записать или очередь заполнена (webhookQueue.maxPending), MAX получает503и повторяет доставку позже (overflow: "drop"вместо этого подтверждает событие и отбрасывает его). Если каталог состояния недоступен для записи, очередь с предупреждением переходит в память. Собственная очередь входящих событий SDK в OpenClaw 2026.9.x доступна только встроенным и официальным плагинам, поэтому плагин ведёт свой журнал по тому же контракту. - Дедупликация. Ключи обработанных событий (
update_type:timestamp:mid; у событий без сообщения вместоmid— чат и пользователь) хранятся 24 часа, до 5000 штук, и переживают рестарт, поэтому повторные доставки MAX отбрасываются. Long polling использует те же ключи и, как очередь webhook, отмечает событие обработанным, как только ядро приняло ход: после рестарта или падения посреди пачки — даже посреди хода — обработанные события не повторяются, а прерванный ход ядро возобновляет само, заново он не запускается. - Завершение gateway. Пока gateway штатно останавливается или перезапускается, ядро не принимает новые ходы агента, а webhook и long polling продолжают получать события. Такое событие не теряется: плагин держит его в очереди и повторяет через 2, 5, 10 и далее каждые 30 секунд, а следующие события того же чата ждут за ним. Если gateway остановился раньше, событие остаётся в журнале на диске и обрабатывается после старта (long polling к тому же не сохраняет маркер пачки). Отказ ядра после того, как оно приняло ход, и другие ошибки обработки не повторяются. В очереди в памяти (
webhookQueue.mode: "memory"или каталог состояния недоступен для записи) такое событие, как и раньше, пропускается с ошибкой в журнале. - Возраст событий. Сообщения, правки, нажатия кнопок и запуски бота старше
maxEventAgeMinutes(по умолчанию 60) пропускаются с предупреждением — после долгого простоя бот не отвечает на сообщения многочасовой давности. События реестра чатов обрабатываются всегда. - Ошибки доставки передаются ядру: не ушло ничего — ответ считается неотправленным; видна часть — это частичная доставка с id видимых сообщений. Если часть текста не отправилась, следующие части не отправляются.
- Длинные ответы делятся по 4000 символов (или по меньшему
textChunkLimit); приstreamMode: "partial"первая часть заменяет черновик, остальные идут новыми сообщениями, кнопки — на последнем. - Ответ из нескольких частей. Если ядро отдаёт ответ несколькими частями, в черновик стриминга или в ответ на нажатие кнопки (
POST /answers) идёт только первая; остальные части, а также блокиstreamMode: "block", приходят новыми сообщениями, и ни одна часть не затирает другую. - Черновик стриминга (
streamMode: "partial"). Обновления черновика отправляются по одному, второго черновика не бывает; финал ждёт отправки черновика, которая уже идёт. Выводы инструментов (режим verbose) приходят отдельными сообщениями, а черновик продолжает стримиться. Черновик, который не стал ответом (NO_REPLY,/stop, ответ через инструментmessageили ответ только из медиа), в конце хода удаляется; после ошибки обработки остаётся. - Повторы: запросы с ответом
429и сетевыми ошибками повторяются;attachment.not.readyпосле загрузки повторяется; отправка текста ограничена лимитом MAX — 2 сообщения в секунду на чат.
По документации MAX Bot API:
| Лимит | Значение |
|---|---|
| Текст сообщения | 4000 символов (более длинный текст плагин делит) |
| Отправка | 2 сообщения в секунду на диалог, группу или канал (плагин ставит отправки в очередь) |
| Правка | Только собственные сообщения бота. В диалогах: сообщения с inline-клавиатурой — любой давности, остальные — в течение 7 дней. В групповых чатах и каналах — любой давности. Не больше 2 правок в секунду на чат |
| Удаление | Боту нужны права администратора с разрешением на удаление. В диалогах — только собственные сообщения бота, в группах и каналах — любые. Не больше 2 удалений в секунду на чат |
| Альбом | До 12 изображений и видео в одном сообщении; файлы и аудио — отдельно |
| Кнопки | Текст ≤ 128 символов, payload ≤ 1024 байт, ≤ 30 рядов, ≤ 210 кнопок |
| Команды бота | До 32 |
| Webhook | HTTPS на порту 443, доверенный сертификат, 200 в течение 30 с; подписка снимается после 8 часов неудачных доставок |
| Long polling | Не для рабочей эксплуатации; недоступен, пока есть подписка webhook |
- Контроль доступа по умолчанию: личные сообщения требуют сопряжения, группы — списка разрешённых, списки отправителей в группах соблюдаются.
- Где разрешены действия: инструмент
messageне может действовать в чатах, которые не допускает политика входящих (см. выше). - Локальные файлы: только из каталогов, которые OpenClaw разрешает агенту; выйти за их пределы через
..и символические ссылки нельзя. Агенту с чтением с хоста ядро разрешает любые пути, но только проверенные по содержимому типы файлов. - Удалённые медиа: скачиваются через SSRF-защиту ядра; приватные, loopback- и link-local-адреса, а также адреса сервисов метаданных не передаются в MAX ссылками.
- Webhook: секрет проверяется за постоянное время до чтения тела запроса.
- Секреты: поддерживается SecretRef; токен, секрет webhook и учётные данные прокси не пишутся в логи.
- TLS: дополнительным российским удостоверяющим центрам плагин доверяет только в собственных соединениях.
- Логи: по умолчанию без текста сообщений.
Прокси и внутренние адреса. Если задан httpProxy, имена хостов скачиваемых медиа разрешает прокси, а не плагин: литеральные приватные адреса и заблокированные имена по-прежнему отклоняются, но имя, которое DNS прокси разрешает во внутренний адрес, будет скачано из сети прокси. Поэтому прокси, стоящий во внутренней сети, даёт путь к адресам этой сети. Используйте прокси, у которого нет доступа к внутренним ресурсам.
Известные ограничения.
- Если во время работы gateway кончилось место на диске или диск стал доступен только для чтения, webhook отвечает
503на каждое событие и бот не отвечает, пока место не освободится. - В допущенной группе голосовое без подписи, которое MAX не расшифровал, отправляется на распознавание речи, чтобы найти упоминание бота. Распознавание платное и не ограничено по частоте; оно включается только при заданных шаблонах упоминаний (
groups.<id>.disableAudioPreflightотключает его для группы). - Кнопка, нагрузка которой начинается с
/, при нажатии выполняется как команда от имени нажавшего и с его правами — так же, как в каналах ядра. - Событие, обработка которого роняет процесс gateway (а не вызывает ошибку, которую ловит плагин), обрабатывается снова после каждого старта, пока не станет старше
maxEventAgeMinutes; при0— без ограничения. pinиunpinпроверяют чат, но не автора сообщения: в допущенном чате, где бот — администратор, они закрепляют и открепляют любое сообщение (в отличие отeditиdeleteвне текущего чата).
Об уязвимостях сообщайте приватно, как описано в SECURITY.md. Поддерживаемая версия: 0.8.x.
Что плагин хранит на диске (<stateDir> — каталог состояния OpenClaw, по умолчанию ~/.openclaw):
| Путь | Содержимое | Срок хранения |
|---|---|---|
<stateDir>/max/state-<account>.json |
Маркер long polling; реестр чатов (id чата, тип, название, когда бота добавили, удалили или остановили); сгенерированный секрет webhook, если он есть | Пока файл не удалят |
<stateDir>/max/inbox-<account>/ (каталог 0700, файлы 0600) |
События webhook, ожидающие обработки, включая текст сообщений и метаданные вложений | Удаляются после обработки |
<stateDir>/max/inbox-<account>/completed.json |
Ключи обработанных событий (update_type:timestamp:mid), без содержимого |
24 часа, до 5000 ключей |
webhookQueue.mode: "memory" держит ожидающие события webhook только в памяти (файл ключей остаётся). Скачанные входящие медиа и историю разговоров хранит ядро OpenClaw, а не плагин.
Логи: тип сообщения, длина текста, id чата, сообщения и пользователя, ошибки и тайминги. Текст сообщений, подписи и расшифровки в логи не пишутся, если не включён logMessagePreview: true (превью в 50 символов на уровне debug). Токены, секреты и учётные данные прокси не пишутся в логи никогда.
Сетевые обращения плагина: MAX Bot API, его хосты загрузки и CDN (через httpProxy, если он задан) и хосты удалённых медиа, которые отправляет агент. Если у голосового нет расшифровки MAX, ядро OpenClaw отправляет аудио провайдеру распознавания речи, настроенному в tools.media.audio.
| Симптом | Вероятная причина и что делать |
|---|---|
| Бот не отвечает в личных сообщениях | dmPolicy — pairing, а отправитель не подтверждён (openclaw pairing list max), или allowlist, а отправителя нет в allowFrom |
| Бот не отвечает в группе | Чата нет в groups (groupPolicy: allowlist); бота не упомянули (requireMention); отправителя нет в groups.<id>.allowFrom / groupAllowFrom; бот не администратор группы — тогда MAX не доставляет ему сообщения группы (замечено при long polling) |
MAX bot token not configured: … в статусе |
Не задан ни botToken, ни tokenFile, ни MAX_BOT_TOKEN; в сообщении указан путь опции. tokenFile, которого нет, который пуст или является символической ссылкой, не читается (tokenFile is missing, empty or not a regular file); аккаунт по умолчанию тогда берёт MAX_BOT_TOKEN, если он задан |
| Аккаунт не стартует, в ошибке упоминается SecretRef | Ссылка не разрешилась; проверьте secrets.providers и переменную, файл или команду, на которые она указывает |
| Webhook: события не приходят | Проверьте публичный URL, сертификат и то, что обратный прокси пробрасывает путь; после 8 часов сбоев MAX снимает подписку — плагин восстановит её в течение 12 минут; пока подписка есть, long polling не работает |
MAX получает 503 от webhook |
Журнал не удалось записать (диск заполнен, нет прав) или достигнут webhookQueue.maxPending |
| Старые сообщения после простоя остаются без ответа | Они старше maxEventAgeMinutes (по умолчанию 60); задайте 0, чтобы отвечать на сообщения любой давности |
Local media path is not under an allowed directory |
Файл лежит вне каталогов, разрешённых агенту; скопируйте его в рабочий каталог |
Host-local media sends only allow … |
У агента чтение с хоста, а тип файла ядро не распознало или не разрешает; отправьте файл разрешённого типа или передайте содержимое через buffer |
Инструмент message получает отказ в другом чате |
actionScope: политика входящих не допускает этот чат; добавьте чат в политику или измените actionScope |
MAX API does not resolve @username … |
Используйте user:<id> или числовой id чата |
pinned: false в личном диалоге |
В диалогах MAX закреплять нельзя |
В сообщении видны ** или _ |
MAX отклонил разметку, и плагин отправил текст заново без неё |
| Аккаунт останавливается при старте с ошибкой прокси или адреса API | httpProxy или apiBaseUrl заданы неверно, или apiBaseUrl использует http для хоста, который не loopback |
/think отвечает кнопками, а не текстом |
Это меню команд: чтобы задать значение сразу, отправьте /think high |
| Предупреждение о том, что аккаунт наследует открытую политику | Именованный аккаунт без своих dmPolicy / groupPolicy наследует open; задайте политику в accounts.<id> |
Конфиг 0.7 работает без изменений. Что меняется в поведении и как вернуть поведение 0.7:
| Изменение в 0.8 | Поведение 0.7 |
|---|---|
В ходе не владельца инструмент message действует только в текущем чате и в чатах, которые допускает политика входящих |
actionScope: "off" (строже — "current") |
Вызов без senderIsOwner: true (heartbeat, субагент, запуск по расписанию без авторитета сообщений) ограничен допущенными чатами; в группе со списком отправителей — отказ |
actionScope: "off" |
Вне чата текущего хода edit и delete не трогают чужие сообщения — только сообщения бота |
actionScope: "off" |
replyTo отправки должен указывать на сообщение того же чата, куда она уходит; иначе отказ |
actionScope: "off" |
| Локальные файлы читает защищённый загрузчик ядра: из разрешённых агенту каталогов, а при чтении с хоста — только проверенных типов | Переключателя нет: копируйте файлы в рабочий каталог агента |
| Сообщения, правки, нажатия кнопок и запуски бота старше 60 минут, включая повторные доставки MAX после простоя, остаются без ответа | maxEventAgeMinutes: 0 |
Webhook отвечает 200 после записи события на диск; 503 — если запись не удалась или очередь заполнена |
webhookQueue.overflow: "drop", webhookQueue.mode: "memory" |
Ожидающие события webhook вместе с текстом сообщений хранятся в <stateDir>/max/inbox-<account>/ до обработки |
webhookQueue.mode: "memory" (файл ключей остаётся) |
Именованные аккаунты наследуют все опции уровня канала, включая dmPolicy, allowFrom, groupPolicy, groups |
Задайте политики в каждом аккаунте |
channels.max.enabled: false выключает и именованные аккаунты |
Оставьте канал включённым; чтобы работали только именованные аккаунты, не задавайте токен на верхнем уровне (botToken, tokenFile, MAX_BOT_TOKEN) |
Непустой groupAllowFrom или groups.<id>.allowFrom ограничивает, кто может обращаться к боту в допущенной группе |
Удалите списки или добавьте "*" |
| Из одного входящего сообщения скачивается не больше 12 медиа | Увеличьте mediaMaxCount |
| Пересланные сообщения запускают ход агента (раньше отбрасывались) | — |
| Сводки инструментов (режим verbose) приходят без превью ссылок | disableLinkPreview: false |
/think, /fast, /reasoning и другие команды с меню без аргумента отвечают кнопками |
— (с аргументом работают как раньше) |
Заданные в ядре messages.groupChat.mentionPatterns теперь действуют в группах MAX |
mentionPatterns: { "mode": "deny" } |
Флаг silent ядра соблюдается; индикатор набора подчиняется typingMode ядра и держится весь ход |
Настройки ядра |
Если MAX отклонил разметку, ответ приходит обычным текстом (с видимыми **, _) |
— |
| В debug-логах нет текста сообщений | logMessagePreview: true |
openclaw secrets audit отмечает botToken, webhookSecret и httpProxy, записанные строкой |
Перенесите их в SecretRef, tokenFile или webhookSecretFile |
streaming.mode сильнее streamMode |
Оставьте только один из ключей |
Polling после ошибки ждёт 2–60 с (было 3 с), после 401 — 5 минут |
— |
Опубликованный пакет больше не содержит scripts/ |
Скрипты остаются в репозитории |
Действуют groups.<id>.enabled, groups.<id>.systemPrompt, groups.<id>.skills, actions и textChunkLimit (в 0.7 схема их принимала, но они не читались) |
Уберите эти ключи из конфига |
Полный список — в журнале изменений.
git clone https://github.com/aspalagin/openclaw-max.git
cd openclaw-max
npm ci
npm run typecheck # запускать до build: build собирает и при ошибках типов
npm run build # dist/index.js, dist/setup-entry.js, dist/secret-contract-api.js, dist/src/*.jsПроверки, как в CI:
npm run format:check # prettier
npm run lint # eslint
npm run check:cycles # циклы импортов между модулями src/
npm run typecheck
npm test # vitest, включая сверку со снимком схемы MAX
npm audit --omit=dev --omit=peer # зависимости времени выполнения, которые входят в пакетCI также запускает typecheck и тесты на openclaw@latest (с zod из lockfile и с минимальной поддерживаемой версией zod) и на openclaw@beta; сбой на beta попадает в отчёт, но сборку не проваливает.
src/__fixtures__/max-schema-0.0.33.yaml— снимок схемы MAX Bot API;npm run schema:update [ref]его обновляет.npm run test:apiобращается к живому API сMAX_BOT_TOKEN(GET /me,GET /updates); вспомогательные скрипты изscripts/есть только в репозитории, в пакет они не входят.- В тестах — только вымышленные токены и id.
Вопросы и предложения: GitHub Issues. Правила участия: CONTRIBUTING.md.
Благодарности. С 28 мая 2026 года (синхронизация с версией 0.5.0, коммит d8d24bc) плагин содержит код, производный от npm-пакета openclaw-max 0.5.0 Евгения Быстрова, опубликованного под лицензией MIT (Copyright (c) 2026 Evgeniy Bystrov): github.com/evgeniyvbystrov/openclaw-max. Уведомление об авторских правах Евгения Быстрова сохранено в LICENSE. Спасибо.
Авторы.
- Petlevoy — основатель проекта.
- Арсений Палагин — сопровождающий.
Разработка ведётся с помощью ИИ-агентов.
Лицензия: MIT, см. LICENSE.
{ "channels": { "max": { "enabled": true, "tokenFile": "/home/you/.openclaw/secrets/max-bot-token", "dmPolicy": "pairing", // Webhook (MAX рекомендует его для рабочей эксплуатации); для long polling уберите оба ключа "webhookUrl": "https://bot.example.com/max/webhook", "webhookSecretFile": "/home/you/.openclaw/secrets/max-webhook-secret" } } }