@aspalagin/openclaw-max 0.7.3 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +412 -344
  3. package/README_EN.md +492 -0
  4. package/SKILL.md +45 -37
  5. package/assets/icon.png +0 -0
  6. package/dist/secret-contract-api.js +6 -0
  7. package/dist/src/access-policy.js +123 -0
  8. package/dist/src/accounts.js +170 -23
  9. package/dist/src/action-scope.js +213 -0
  10. package/dist/src/actions.js +159 -31
  11. package/dist/src/api.js +99 -83
  12. package/dist/src/callbacks.js +27 -7
  13. package/dist/src/channel-agent-prompt.js +8 -19
  14. package/dist/src/channel-config.js +25 -3
  15. package/dist/src/channel-lifecycle.js +28 -5
  16. package/dist/src/channel-outbound.js +43 -9
  17. package/dist/src/channel-policy.js +19 -6
  18. package/dist/src/channel.js +16 -0
  19. package/dist/src/command-menu.js +199 -0
  20. package/dist/src/config-schema.js +106 -10
  21. package/dist/src/deliver.js +144 -48
  22. package/dist/src/dispatch.js +12 -11
  23. package/dist/src/gateway-drain.js +40 -0
  24. package/dist/src/group-mention.js +129 -0
  25. package/dist/src/inbound-attachments.js +162 -20
  26. package/dist/src/inbound.js +388 -134
  27. package/dist/src/media-temp.js +31 -20
  28. package/dist/src/network.js +216 -0
  29. package/dist/src/onboarding.js +4 -2
  30. package/dist/src/polling.js +180 -12
  31. package/dist/src/presentation.js +11 -0
  32. package/dist/src/progress-draft.js +168 -0
  33. package/dist/src/runtime.js +15 -0
  34. package/dist/src/secret-contract.js +149 -0
  35. package/dist/src/send.js +250 -46
  36. package/dist/src/stream-draft.js +83 -41
  37. package/dist/src/turn-adoption.js +24 -0
  38. package/dist/src/typing.js +55 -0
  39. package/dist/src/update-journal.js +267 -0
  40. package/dist/src/webhook-queue.js +151 -26
  41. package/dist/src/webhook-runner.js +61 -9
  42. package/dist/src/webhook.js +34 -15
  43. package/openclaw.plugin.json +983 -8
  44. package/package.json +17 -4
  45. package/src/access-policy.ts +154 -0
  46. package/src/accounts.ts +260 -25
  47. package/src/action-scope.ts +274 -0
  48. package/src/actions.ts +188 -35
  49. package/src/api.ts +158 -101
  50. package/src/callbacks.ts +26 -7
  51. package/src/channel-agent-prompt.ts +8 -19
  52. package/src/channel-config.ts +32 -3
  53. package/src/channel-lifecycle.ts +33 -6
  54. package/src/channel-outbound.ts +50 -7
  55. package/src/channel-policy.ts +27 -7
  56. package/src/channel.ts +16 -0
  57. package/src/command-menu.ts +277 -0
  58. package/src/config-schema.ts +111 -10
  59. package/src/deliver.ts +174 -55
  60. package/src/dispatch.ts +12 -11
  61. package/src/gateway-drain.ts +45 -0
  62. package/src/group-mention.ts +191 -0
  63. package/src/inbound-attachments.ts +173 -22
  64. package/src/inbound.ts +443 -130
  65. package/src/media-temp.ts +35 -23
  66. package/src/network.ts +233 -0
  67. package/src/onboarding.ts +4 -2
  68. package/src/polling.ts +221 -15
  69. package/src/presentation.ts +14 -1
  70. package/src/progress-draft.ts +226 -0
  71. package/src/runtime.ts +23 -0
  72. package/src/secret-contract.ts +183 -0
  73. package/src/send.ts +346 -59
  74. package/src/stream-draft.ts +102 -41
  75. package/src/turn-adoption.ts +33 -0
  76. package/src/types.ts +9 -3
  77. package/src/typing.ts +77 -0
  78. package/src/update-journal.ts +302 -0
  79. package/src/webhook-queue.ts +205 -33
  80. package/src/webhook-runner.ts +65 -8
  81. package/src/webhook.ts +39 -18
  82. package/scripts/check-cycles.mjs +0 -38
  83. package/scripts/test-api.mjs +0 -169
  84. package/scripts/test-send.mjs +0 -162
  85. package/scripts/update-schema.mjs +0 -48
package/README.md CHANGED
@@ -1,313 +1,303 @@
1
- # openclaw-max – плагин MAX для OpenClaw
2
-
3
- Канал-плагин для подключения AI-ассистента [OpenClaw](https://openclaw.ai) к мессенджеру [MAX](https://max.ru) (ex-VK Teams / ICQ New).
4
-
5
- Версия 0.7.1, изменения — в [CHANGELOG.md](CHANGELOG.md). Проверено на OpenClaw 2026.9.6 и схеме MAX Bot API 0.0.33.
6
-
7
- ## Что это
8
-
9
- Плагин позволяет общаться с OpenClaw-ботом через мессенджер MAX — так же, как через Telegram. Поддерживает:
10
-
11
- - Приём и отправку текстовых сообщений (MAX-диалект markdown: `++подчёркивание++`, упоминания `max://user/id`)
12
- - Вложения (фото, видео, аудио, файлы, стикеры, контакты, геолокация)
13
- - Inline-кнопки: callback / link / message / clipboard / open_app / request_contact / request_geo_location
14
- - Контракт OpenClaw `presentation`: заголовок, текст, context, divider, таблицы и графики (моноширинно), кнопки и select → inline-клавиатура; нажатия approval/ask_user/command/callback возвращаются в gateway
15
- - Закрепление сообщений (pin/unpin) и `delivery.pin` для отправляемых сообщений
16
- - Long polling (с персистентным marker) и Webhook на HTTP-сервере gateway (секрет, мгновенный ACK, дедупликация повторов) — см. «Webhook»
17
- - Реестр чатов из событий bot_added/bot_started (замена deprecated GET /chats)
18
- - Ретраи на 429/сетевые сбои и `attachment.not.ready`
19
- - Мультиаккаунт
20
- - DM-security и pairing
21
-
22
- ## TLS: сертификат Минцифры (важно!)
23
-
24
- С июля 2026 MAX Bot API живёт на `platform-api2.max.ru` с сертификатом, выпущенным
25
- Russian Trusted Sub CA (Минцифры), которого нет в стандартном доверенном наборе Node.js.
26
- Плагин решает это сам: все запросы к API идут через выделенный undici-dispatcher,
27
- в CA-набор которого добавлены Russian Trusted Root/Sub CA (встроены в пакет,
28
- `src/russian-trusted-ca.ts`). Доверие ограничено только соединениями плагина —
29
- процесс-wide trust store не трогается, `NODE_EXTRA_CA_CERTS` не требуется.
30
-
31
- ## Структура проекта
32
-
33
- ```
34
- openclaw-max/
35
- ├── index.ts # Точка входа (defineChannelPluginEntry) → dist/index.js
36
- ├── setup-entry.ts # Точка входа setup-режима (defineSetupPluginEntry) → dist/setup-entry.js
37
- ├── openclaw.plugin.json # Манифест плагина
38
- ├── package.json
39
- ├── tsconfig.json
40
- ├── README.md
41
- ├── src/
42
- │ ├── channel.ts # maxPlugin: сборка адаптеров канала
43
- │ ├── channel-config.ts # Аккаунты, включение/удаление, CLI setup
44
- │ ├── channel-policy.ts # DM-политика, группы (requireMention, tools), pairing
45
- │ ├── channel-directory.ts # Нормализация целей, directory (self/peers/groups)
46
- │ ├── channel-outbound.ts # sendText/sendPayload/sendMedia, presentation, pin
47
- │ ├── channel-lifecycle.ts # Старт/стоп аккаунта, статус, probe, аудит групп
48
- │ ├── channel-agent-prompt.ts # Подсказки агенту (стикеры, location, кнопки…)
49
- │ ├── monitor.ts # Точка входа приёма: выбор транспорта
50
- │ ├── monitor-types.ts # Опции монитора, статус, подписанные update_type
51
- │ ├── polling.ts # Long polling (marker), снятие чужих подписок
52
- │ ├── webhook-runner.ts # Webhook-режим: секрет, подписка, сверка, запуск очереди
53
- │ ├── webhook-queue.ts # Webhook: очередь апдейтов, обработка в задаче аккаунта
54
- │ ├── webhook.ts # Webhook: роут gateway, secret, быстрый ACK, дедупликация
55
- │ ├── dispatch.ts # Разбор update'ов по update_type
56
- │ ├── inbound.ts # Gate DM/групп, контекст, запуск агента
57
- │ ├── inbound-attachments.ts # Скачивание и описание входящих вложений
58
- │ ├── callbacks.ts # Нажатия кнопок, approval/ask_user
59
- │ ├── deliver.ts # Доставка ответа: чанки, альбомы, pin
60
- │ ├── stream-draft.ts # Edit-стриминг (streamMode: partial)
61
- │ ├── send.ts # Send-хелперы (sendWithBody, кнопки, медиа, pin)
62
- │ ├── actions.ts # message-tool actions (send/edit/delete/pin/…)
63
- │ ├── presentation.ts # Рендер presentation и callback-конверты
64
- │ ├── api.ts # HTTP-клиент MAX API (retry, TLS, upload, лимитер)
65
- │ ├── types.ts # TypeScript-типы MAX Bot API
66
- │ ├── russian-trusted-ca.ts # Встроенные сертификаты Минцифры
67
- │ ├── format.ts # Конвертация markdown в MAX-диалект
68
- │ ├── media-temp.ts # Временные файлы медиа, очистка имён
69
- │ ├── state.ts # Персист: marker, реестр чатов, секрет webhook
70
- │ ├── accounts.ts # Резолвинг аккаунтов из конфига
71
- │ ├── config-schema.ts # Zod-схема конфига
72
- │ ├── model-buttons.ts # Кнопки выбора модели
73
- │ ├── onboarding.ts # Setup wizard
74
- │ ├── sticker-cache.ts # Кэш кодов стикеров
75
- │ └── __fixtures__/ # Снимок схемы MAX (max-schema-<версия>.yaml)
76
- └── scripts/
77
- ├── test-api.mjs # Проверка токена и API
78
- ├── test-send.mjs # Тест send + edit + delete
79
- ├── check-cycles.mjs # Проверка циклических импортов src/
80
- └── update-schema.mjs # Обновление снимка схемы MAX
81
- ```
1
+ # openclaw-max — канал мессенджера MAX для OpenClaw
2
+
3
+ [English version](README_EN.md)
4
+
5
+ Плагин канала, который подключает ассистента [OpenClaw](https://openclaw.ai) к мессенджеру [MAX](https://max.ru) через MAX Bot API (`platform-api2.max.ru`). Люди пишут вашему боту в MAX — в личном диалоге или в группе, — и агент OpenClaw отвечает там же: с медиа, кнопками и голосом.
6
+
7
+ Плагин нужен тем, кто пользуется OpenClaw и хочет, чтобы ассистент был доступен в MAX. Понадобятся токен бота MAX (ботов создают организации на [business.max.ru](https://business.max.ru/self)) и собственный gateway OpenClaw.
8
+
9
+ Версия 0.8.0. Изменения: [CHANGELOG.md](https://github.com/aspalagin/openclaw-max/blob/main/CHANGELOG.md).
10
+
11
+ ## Содержание
12
+
13
+ - [Поддерживаемые типы сообщений](#поддерживаемые-типы-сообщений)
14
+ - [Требования и совместимость](#требования-и-совместимость)
15
+ - [Установка](#установка)
16
+ - [Быстрый старт](#быстрый-старт)
17
+ - [Транспорты: webhook и long polling](#транспорты-webhook-и-long-polling)
18
+ - [Справочник настроек](#справочник-настроек)
19
+ - [Доступ и политики](#доступ-и-политики)
20
+ - [Действия инструмента `message` и где они разрешены](#действия-инструмента-message-и-где-они-разрешены)
21
+ - [Медиа и локальные файлы](#медиа-и-локальные-файлы)
22
+ - [Голосовые сообщения](#голосовые-сообщения)
23
+ - [Команды и меню](#команды-и-меню)
24
+ - [Статус хода](#статус-хода)
25
+ - [Несколько аккаунтов и наследование](#несколько-аккаунтов-и-наследование)
26
+ - [Прокси, адрес API и сертификаты Russian Trusted CA](#прокси-адрес-api-и-сертификаты-russian-trusted-ca)
27
+ - [Секреты и SecretRef](#секреты-и-secretref)
28
+ - [Надёжность](#надёжность)
29
+ - [Лимиты MAX API](#лимиты-max-api)
30
+ - [Безопасность](#безопасность)
31
+ - [Конфиденциальность](#конфиденциальность)
32
+ - [Устранение неполадок](#устранение-неполадок)
33
+ - [Обновление с 0.7](#обновление-с-07)
34
+ - [Разработка](#разработка)
35
+ - [Благодарности, авторы и лицензия](#благодарности-авторы-и-лицензия)
36
+
37
+ ## Поддерживаемые типы сообщений
38
+
39
+ | Тип | Входящие (MAX → агент) | Исходящие (агент → MAX) |
40
+ |---|---|---|
41
+ | Текст | Да; упоминания распознаются по разметке MAX, `@username` и шаблонам упоминаний ядра | Да; markdown переводится в диалект MAX (`++underline++`) и делится на части по 4000 символов; если MAX отклоняет разметку, текст один раз отправляется заново без неё |
42
+ | Изображение | Скачивается и передаётся агенту как медиа | Да; несколько изображений и видео уходят альбомами до 12 штук; публичная https-ссылка на изображение передаётся в MAX ссылкой |
43
+ | Видео | Скачивается (лимит размера — `mediaMaxMb`) | Да |
44
+ | Аудио и голосовые | Скачиваются; если MAX прислал расшифровку, используется она, иначе запись распознаёт ядро OpenClaw | Да; `asVoice` / `audioAsVoice` отправляет вложение MAX `audio` |
45
+ | Файл | Скачивается | Да: из разрешённого локального пути, по URL или содержимым в `buffer` |
46
+ | Стикер | Код стикера и изображение | Действие `sticker` по коду |
47
+ | Контакт | Имя и телефоны из VCard, профиль MAX | `sendAttachment` с `type="contact"` |
48
+ | Геолокация | Координаты и ссылка на карту | `sendAttachment` с `type="location"` |
49
+ | Карточка ссылки (share) | Заголовок, описание и ссылка | — |
50
+ | Пересланное сообщение | Текст и вложения с исходным автором; собственный комментарий отправителя идёт первым | — |
51
+ | Ответ на сообщение | Цитата исходного сообщения (до 1000 символов) и его автор | Да, ответом MAX (`replyTo`) |
52
+ | Inline-клавиатура | Нажатия кнопок: callback, подтверждения, меню команд | 7 типов кнопок (callback, link, message, clipboard, open_app, request_contact, request_geo_location); блоки `presentation` OpenClaw отрисовываются текстом и клавиатурами |
53
+ | Изменённое сообщение | Обрабатывается заново как новое | Действие `edit`; при стриминге черновика правится одно сообщение |
54
+ | Удалённое сообщение | Игнорируется (запись в лог на уровне debug) | Действие `delete` |
55
+ | Закрепление | — | `pin` / `unpin` в группах и каналах; `delivery.pin` |
56
+ | Запуск бота (deep link) | Доходит до агента как `/start` или `/start <payload>`; как и сообщения, пропускается, если старше `maxEventAgeMinutes` | — |
57
+ | Индикатор набора, отметка о прочтении | — | `typing_on` на весь ход; `mark_seen` для входящих сообщений (`markSeen`) |
58
+
59
+ Реакции и опросы не поддерживаются.
60
+
61
+ ## Требования и совместимость
62
+
63
+ - 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.
64
+ - Node.js ≥ 22.
65
+ - MAX Bot API в том виде, в каком его описывают схема 0.0.33 и [dev.max.ru/docs-api](https://dev.max.ru/docs-api).
66
+ - Для режима webhook: публичный HTTPS-адрес на порту 443 с доверенным сертификатом, который ведёт на HTTP-сервер gateway.
67
+
68
+ **Что проверено.** Версия 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.
82
69
 
83
70
  ## Установка
84
71
 
85
- ### 1. Создать бота в MAX
86
-
87
- 1. Зайти на [business.max.ru](https://business.max.ru/self) (нужно юрлицо/ИП)
88
- 2. Создать профиль организации и пройти верификацию
89
- 3. Раздел **Чат-боты** → **Создать** (название, лого 500x500, описание)
90
- 4. Дождаться модерации (до 48ч по рабочим дням)
91
- 5. После модерации: **Чат-боты → Интеграция → Получить токен**
92
-
93
- ### 2. Установить плагин
94
-
95
- Через ClawHub (рекомендуется):
72
+ Из ClawHub:
96
73
 
97
74
  ```bash
98
75
  openclaw plugins install clawhub:@aspalagin/openclaw-max
99
76
  ```
100
77
 
101
- Через npm:
78
+ Из npm:
102
79
 
103
80
  ```bash
104
81
  openclaw plugins install npm:@aspalagin/openclaw-max
105
82
  ```
106
83
 
107
- Вручную из исходников (для разработки):
84
+ > Указывайте имя со scope — `@aspalagin/openclaw-max`. Пакет npm без scope `openclaw-max` — другой проект (см. [Благодарности](#благодарности-авторы-и-лицензия)).
85
+
86
+ После установки перезапустите gateway, чтобы он загрузил плагин. Id плагина в OpenClaw — `openclaw-max`, id канала — `max`.
87
+
88
+ Обновление: `openclaw plugins update openclaw-max` для установки из npm или `openclaw plugins install clawhub:@aspalagin/openclaw-max --force` для установки из ClawHub; затем перезапустите gateway. Перед обновлением прочитайте раздел [Обновление с 0.7](#обновление-с-07).
89
+
90
+ ## Быстрый старт
91
+
92
+ 1. **Создайте бота.** На [business.max.ru](https://business.max.ru/self) (нужна зарегистрированная организация или ИП) откройте **Чат-боты → Создать**, дождитесь модерации и скопируйте токен в разделе **Чат-боты → Интеграция**.
93
+ 2. **Сохраните токен** в файл, который может читать пользователь gateway, например `/home/you/.openclaw/secrets/max-bot-token` (права `0600`). Путь используется как есть: указывайте абсолютный путь; `~` не раскрывается. Нужен обычный файл: символическая ссылка не читается — как если бы файла не было.
94
+ 3. **Настройте** `~/.openclaw/openclaw.json`:
95
+
96
+ ```jsonc
97
+ {
98
+ "channels": {
99
+ "max": {
100
+ "enabled": true,
101
+ "tokenFile": "/home/you/.openclaw/secrets/max-bot-token",
102
+ "dmPolicy": "pairing",
103
+ // Webhook (MAX рекомендует его для рабочей эксплуатации); для long polling уберите оба ключа
104
+ "webhookUrl": "https://bot.example.com/max/webhook",
105
+ "webhookSecretFile": "/home/you/.openclaw/secrets/max-webhook-secret"
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ 4. **Перезапустите gateway**: `openclaw gateway restart`.
112
+ 5. **Проверьте аккаунт**: `openclaw channels status` показывает аккаунт, `tokenSource`, `tokenStatus` и `mode`; `openclaw channels status --probe` дополнительно обращается к MAX API.
113
+ 6. **Напишите боту** в MAX. При `dmPolicy: "pairing"` вы получите код сопряжения (pairing); подтвердите его командой `openclaw pairing approve max <code>` (`openclaw pairing list max` показывает ожидающие запросы с id пользователя-отправителя). Чтобы обойтись без сопряжения, добавьте свой id пользователя в `allowFrom`.
114
+
115
+ Токен можно задать и через `botToken` (строкой или [SecretRef](#секреты-и-secretref)) или переменной окружения `MAX_BOT_TOKEN` (только для аккаунта верхнего уровня). Порядок: `botToken`, затем `tokenFile`, затем `MAX_BOT_TOKEN`.
116
+
117
+ ## Транспорты: webhook и long polling
118
+
119
+ **Для рабочей эксплуатации используйте webhook.** MAX описывает long polling как режим для разработки и тестирования, ограниченный по скорости и сроку хранения событий, и просит в рабочей эксплуатации использовать webhook ([GET /updates](https://dev.max.ru/docs-api/methods/GET/updates)). Авторы других плагинов также сообщают, что через long polling голосовые сообщения могут приходить пустыми или не приходить вовсе (в частности, с Android); MAX этого не документирует, и мы пока этого не подтвердили — через webhook голосовые приходят и распознаются.
120
+
121
+ `transport` выбирает режим: `webhook`, если задан `webhookUrl`, иначе `polling`. `transport: "webhook"` без `webhookUrl` — ошибка конфигурации.
122
+
123
+ ### Webhook
124
+
125
+ - **Требования MAX:** HTTPS на порту 443 с сертификатом доверенного удостоверяющего центра; ответ `200` в течение 30 секунд; неудавшиеся доставки MAX повторяет, а после 8 часов сбоев снимает подписку. Каждый запрос несёт заголовок `X-Max-Bot-Api-Secret`. Пока подписка webhook активна, long polling не работает.
126
+ - **Маршрут:** плагин принимает webhook на собственном HTTP-сервере gateway (отдельный порт не нужен). Путь: `webhookPath`, иначе путь из `webhookUrl`, иначе `/max/webhook`. Поставьте перед gateway обратный прокси или туннель с действительным сертификатом и пробрасывайте только этот путь.
127
+ - **Секрет:** `webhookSecret`, иначе `webhookSecretFile`, иначе секрет генерируется один раз и хранится в файле состояния аккаунта. Формат, который требует MAX: 5–256 символов `A–Z a–z 0–9 _ -`. Секрет сравнивается за постоянное время до чтения тела запроса; при несовпадении — ответ `401`.
128
+ - **Подписка:** при старте плагин удаляет подписки этого бота на другие URL и подписывает `webhookUrl`. Каждые 12 минут он проверяет, что подписка на месте, и создаёт её заново, если MAX её снял. При остановке подписка сохраняется, поэтому события, пришедшие во время рестарта, MAX доставит повторно.
129
+ - **Обработка:** HTTP-обработчик проверяет секрет, тело и дубликаты, записывает событие (см. [Надёжность](#надёжность)) и отвечает; агент работает в задаче аккаунта — по порядку внутри чата, до 4 чатов параллельно.
130
+ - **Включение:** сначала разверните плагин и перезапустите gateway, затем добавьте `webhookUrl` и перезапустите ещё раз; убедитесь, что в логе есть `MAX webhook subscribed` и что сообщение доходит до агента.
131
+ - **Откат:** задайте `transport: "polling"` (или уберите `webhookUrl`) и перезапустите gateway; при старте polling подписка удаляется. Если плагин не запускается, удалите подписку вручную: `curl -X DELETE "<api>/subscriptions?url=<webhookUrl>" -H "Authorization: <token>"`, где `<api>` — ваш `apiBaseUrl`, а если он не задан — `https://platform-api2.max.ru`.
108
132
 
109
- ```bash
110
- cd ~/.openclaw/extensions
111
- git clone https://github.com/aspalagin/openclaw-max openclaw-max
112
- cd openclaw-max && npm install && npm run build
113
- ```
133
+ ### Long polling
114
134
 
115
- ### 3. Настроить конфиг
135
+ Плагин опрашивает `GET /updates` и после каждой пачки сохраняет маркер в файле состояния аккаунта, поэтому после рестарта продолжает с того же места. После ошибки он ждёт 2 с, удваивая паузу до 60 с со случайным разбросом (jitter), соблюдает `Retry-After`, а после `401` повторяет попытку раз в 5 минут. При старте активная подписка webhook этого бота удаляется с предупреждением (пока она есть, MAX не обслуживает `GET /updates`).
116
136
 
117
- Добавить секцию в `~/.openclaw/openclaw.json`:
137
+ ## Справочник настроек
118
138
 
119
- ```jsonc
120
- {
121
- "channels": {
122
- "max": {
123
- // Токен бота из business.max.ru → Чат-боты → Интеграция
124
- "botToken": "ваш_токен_бота",
139
+ Все опции находятся в `channels.max`; те же ключи (кроме `accounts` и `commands`) принимаются в `channels.max.accounts.<id>`. «Собственные» опции относятся к одному боту и не наследуются именованными аккаунтами; всё остальное наследуется с уровня канала (см. [Несколько аккаунтов](#несколько-аккаунтов-и-наследование)).
125
140
 
126
- // Список user_id, которым разрешено писать боту
127
- // Узнать свой user_id: написать боту, посмотреть в логах
128
- "allowFrom": ["12345678"],
141
+ | Опция | Тип | По умолчанию | Описание |
142
+ |---|---|---|---|
143
+ | `enabled` | логический | `true` | `false` на уровне канала выключает все аккаунты |
144
+ | `botToken` | строка или SecretRef | — | Токен бота. Собственная |
145
+ | `tokenFile` | строка | — | Абсолютный путь к обычному файлу с токеном. Собственная |
146
+ | `name` | строка | — | Отображаемое имя аккаунта. Собственная |
147
+ | `transport` | `polling` \| `webhook` | `webhook`, если задан `webhookUrl`, иначе `polling` | Собственная |
148
+ | `webhookUrl` | строка | — | Публичный HTTPS-адрес, на который MAX отправляет события. Собственная |
149
+ | `webhookSecret` | строка или SecretRef | генерируется | Секрет webhook. Собственная |
150
+ | `webhookSecretFile` | строка | — | Файл с секретом webhook (обычный файл, не символическая ссылка). Собственная |
151
+ | `webhookPath` | строка | путь из `webhookUrl`, иначе `/max/webhook` | Путь маршрута в gateway. Собственная |
152
+ | `webhookQueue.mode` | `durable` \| `memory` | `durable` | Записывать принятые события webhook в журнал на диске или держать только в памяти |
153
+ | `webhookQueue.maxPending` | целое | `5000` | Сколько принятых, но ещё не обработанных событий может ждать |
154
+ | `webhookQueue.overflow` | `reject` \| `drop` | `reject` | При переполнении: ответить `503`, чтобы MAX повторил доставку, или подтвердить событие и отбросить |
155
+ | `maxEventAgeMinutes` | целое ≥ 0 | `60` | Пропускать сообщения, правки, нажатия кнопок и запуски бота старше этого срока (по времени события в MAX); `0` — без ограничения |
156
+ | `dmPolicy` | `pairing` \| `allowlist` \| `open` \| `disabled` | `pairing` | Кто может писать в личных диалогах |
157
+ | `allowFrom` | массив id пользователей | `[]` | Разрешённые отправители личных сообщений; `"*"` — кто угодно (обязательно при `open`) |
158
+ | `groupPolicy` | `allowlist` \| `open` \| `disabled` | `allowlist` (или `channels.defaults.groupPolicy` ядра) | В каких группах бот отвечает |
159
+ | `groups` | объект с ключами — id чатов или `"*"` | `{}` | Разрешённые группы и их настройки, см. ниже |
160
+ | `groupAllowFrom` | массив id пользователей | — | Отправители, допущенные в разрешённых группах (если у группы нет своего `allowFrom`) |
161
+ | `mentionPatterns` | `{ mode: "allow" \| "deny", allowIn, denyIn }` | allow | Где в группах MAX действуют шаблоны упоминаний ядра |
162
+ | `actionScope` | `admitted` \| `current` \| `off` | `admitted` | В каких чатах может действовать инструмент `message` |
163
+ | `notify` | логический | как в MAX (с уведомлением) | Push-уведомления при отправке; `false` — тихая отправка (в каналах невозможна) |
164
+ | `disableLinkPreview` | логический | как в MAX (превью включены), у сводок инструментов — выключены | Превью ссылок при отправке; заданное значение действует и на сводки инструментов |
165
+ | `markSeen` | логический | `true` | Отмечать входящие сообщения прочитанными |
166
+ | `mediaMaxMb` | число | `20` | Лимит размера скачиваемых и загружаемых медиа, МБ |
167
+ | `mediaMaxCount` | целое | `12` | Сколько медиавложений скачивается из одного входящего сообщения, включая пересланные |
168
+ | `streamMode` | `off` \| `partial` \| `block` | `off` | `partial` — одно сообщение-черновик правится, пока ответ стримится; `block` — блочные ответы ядра |
169
+ | `streaming` | настройка стриминга ядра | выключено | `streaming.mode` (`off`, `partial`, `block`, `progress`) сильнее `streamMode`; `streaming.progress.*` настраивает [статус хода](#статус-хода) |
170
+ | `textChunkLimit` | целое | `4000` | Символов в одном исходящем сообщении; значение аккаунта в приоритете; всё, что больше лимита MAX в 4000, урезается до него |
171
+ | `responsePrefix` | строка | — | Префикс ответов агента, его применяет ядро OpenClaw (`"auto"` — имя агента) |
172
+ | `historyLimit` | целое ≥ 0 | умолчание ядра | Читает ядро OpenClaw: сколько последних ходов групповой сессии встроенный рантайм агента держит в промпте (нативные CLI-рантаймы ведут историю сами) |
173
+ | `dmHistoryLimit`, `dms.<userId>.historyLimit` | целое ≥ 0 | без ограничения | Читает ядро OpenClaw: то же для личных диалогов с сессией на канал (`session.dmScope`); значение для пользователя в приоритете |
174
+ | `actions` | объект `{ <action>: boolean }` | всё включено | `false` выключает действие инструмента `message` (`send`, `edit`, `delete`, `sticker`, `sendAttachment`, `pin`, `unpin`): оно скрыто из инструмента и отклоняется |
175
+ | `apiBaseUrl` | строка | `https://platform-api2.max.ru` | Базовый адрес Bot API; только `https`, `http` — лишь для loopback |
176
+ | `httpProxy` | строка или SecretRef | — | HTTP(S)-прокси для всего трафика MAX; `""` в аккаунте отключает унаследованный прокси |
177
+ | `logMessagePreview` | логический | `false` | Добавлять в debug-логи превью текста длиной 50 символов |
178
+ | `commands` | массив `{ name, description }` | — | Только на уровне канала: команды бота, которые регистрируются в MAX (до 32; имя ≤ 64 символов без `/`, описание ≤ 128) |
179
+ | `accounts` | объект | — | Только на уровне канала: именованные аккаунты |
129
180
 
130
- // Политика DM-доступа:
131
- // "allowlist" — только из allowFrom (по умолчанию)
132
- // "open" — любой может писать
133
- // "pairing" — новые контакты проходят pairing-код
134
- "dmPolicy": "allowlist"
135
- }
136
- }
137
- }
138
- ```
181
+ Настройки группы (`groups.<chatId>` или `groups["*"]`):
139
182
 
140
- ### 4. Перезапустить gateway
183
+ | Ключ | По умолчанию | Описание |
184
+ |---|---|---|
185
+ | `requireMention` | `true` | Отвечать, только если бота упомянули, ответили на его сообщение или нажали его кнопку |
186
+ | `allowFrom` | — | Отправители, допущенные в этой группе (вместо `groupAllowFrom`) |
187
+ | `tools` | — | Политика инструментов ядра для этой группы |
188
+ | `disableAudioPreflight` | `false` | Не распознавать голосовые без подписи ради поиска упоминания |
189
+ | `enabled` | `true` | `false` — игнорировать группу при любой `groupPolicy` |
190
+ | `systemPrompt` | — | Дополнительный системный промпт для ходов в этой группе |
191
+ | `skills` | все | Навыки, которые может загружать ход в этой группе; `[]` — никакие |
192
+
193
+ Ключи `markdown` (отрисовка таблиц), `blockStreaming` (используйте `streamMode` или `streaming.mode: "block"`) и `blockStreamingCoalesce` (ядро читает `streaming.block.coalesce`) схема принимает для совместимости с общим форматом конфигурации каналов, но **в MAX они ни на что не влияют**: ни плагин, ни ядро OpenClaw не читают их для этого канала. Они оставлены в схеме, чтобы существующие конфиги по-прежнему проходили проверку.
194
+
195
+ ## Доступ и политики
196
+
197
+ **Личные диалоги** (`dmPolicy`):
198
+
199
+ - `pairing` (по умолчанию) — незнакомый отправитель получает код сопряжения; владелец подтверждает его командой `openclaw pairing approve max <code>`. Отправителям из `allowFrom` сопряжение не нужно.
200
+ - `allowlist` — только id пользователей из `allowFrom`.
201
+ - `open` — кто угодно; требует `allowFrom: ["*"]`.
202
+ - `disabled` — личные сообщения не принимаются.
203
+
204
+ **Группы** (`groupPolicy`):
205
+
206
+ - `allowlist` (по умолчанию) — только чаты, перечисленные в `groups` (или любой чат, если в `groups` есть запись `"*"`).
207
+ - `open` — любая группа, в которой состоит бот.
208
+ - `disabled` — сообщения из групп не принимаются.
141
209
 
142
- ```bash
143
- openclaw gateway restart
144
- ```
210
+ В допущенной группе непустой `groups.<id>.allowFrom` (а если его нет — `groupAllowFrom`) ограничивает, кто может обращаться к боту: сообщения, нажатия кнопок, команды и голосовые для проверки упоминания от остальных участников игнорируются, их вложения не скачиваются. `"*"` разрешает всем; в id принимается префикс `max:`. Список `allowFrom` для личных сообщений на группы **не** распространяется.
145
211
 
146
- ## Настройка — описание полей
147
-
148
- | Поле | Тип | Обязательно | Описание |
149
- |------|-----|-------------|----------|
150
- | `botToken` | string | да | API-токен бота (или env `MAX_BOT_TOKEN`) |
151
- | `allowFrom` | string[] | да* | Список разрешённых user_id |
152
- | `dmPolicy` | string | нет | Политика DM: pairing (по умолчанию) / allowlist / open / disabled |
153
- | `groupPolicy` | string | нет | Политика групп: allowlist (по умолчанию) / open / disabled |
154
- | `groups` | object | нет | Пер-групповые настройки (requireMention, tools, …) |
155
- | `transport` | string | нет | `polling` / `webhook`; по умолчанию `webhook`, если задан `webhookUrl`, иначе `polling` |
156
- | `webhookUrl` | string | нет | Публичный HTTPS-адрес для MAX; включает webhook-режим (см. «Webhook») |
157
- | `webhookSecretFile` | string | нет | Файл с секретом вебхука (как `tokenFile`) |
158
- | `webhookSecret` | string | нет | Секрет вебхука строкой; если не задан ни он, ни файл — генерируется и хранится в state-файле аккаунта |
159
- | `webhookPath` | string | нет | Путь роута на gateway, если отличается от pathname `webhookUrl` (по умолчанию `/max/webhook`) |
160
- | `streamMode` | string | нет | off (по умолчанию) / partial / block |
161
- | `mediaMaxMb` | number | нет | Лимит скачивания медиа, МБ (по умолчанию 20) |
162
- | `markSeen` | boolean | нет | Слать mark_seen на входящие (по умолчанию true) |
163
- | `commands` | array | нет | Команды бота `[{name, description}]` — регистрируются через PATCH /me/commands (до 32) |
164
-
165
- \* Обязательно при `dmPolicy: "allowlist"`.
166
-
167
- ## Webhook
168
-
169
- По умолчанию плагин получает события long polling'ом. В webhook-режиме MAX сам
170
- присылает `POST` на публичный адрес, а плагин обслуживает его роутом на
171
- HTTP-сервере gateway (тот же порт, что и Control UI; отдельный сервер не
172
- поднимается).
173
-
174
- ### Требования MAX
175
-
176
- - Только HTTPS на порту 443 с сертификатом доверенного CA (самоподписанный не подойдёт).
177
- - Ответ 200 не позже чем через 30 с. Плагин отвечает сразу после проверки
178
- секрета и формы тела, а обработку (агент может думать минутами) ведёт асинхронно.
179
- - При неудаче MAX повторяет доставку до 10 раз с растущим интервалом, а после
180
- 8 часов без успеха **сам снимает подписку**. Повторы одного и того же
181
- обновления плагин отсекает (LRU по `update_type` + `timestamp` + `mid`/`callback_id`).
182
- - Каждый запрос несёт заголовок `X-Max-Bot-Api-Secret`; без совпадения — 401.
183
- - **Пока подписка активна, long polling у MAX не работает.** Поэтому в
184
- polling-режиме плагин при старте удаляет найденную подписку (с warning в логе).
185
-
186
- ### Как устроено
187
-
188
- 1. При старте аккаунта регистрируется роут `auth: "plugin"`, `match: "exact"` на
189
- пути `webhookPath` → pathname `webhookUrl` → `/max/webhook`. Не удалось
190
- зарегистрировать (например, путь занят другим плагином) — старт аккаунта падает.
191
- 2. `GET /subscriptions`: подписки этого бота на другие URL удаляются
192
- (`DELETE /subscriptions?url=`).
193
- 3. `POST /subscriptions {url, update_types, secret}` — на каждом старте, чтобы
194
- секрет и список событий совпадали с текущим конфигом.
195
- 4. При остановке роут снимается, **подписка остаётся**: рестарт gateway не
196
- теряет события (MAX повторит недоставленные). Чтобы выключить webhook,
197
- переключите `transport: "polling"` — плагин удалит подписку при старте, —
198
- или удалите её вручную `DELETE /subscriptions?url=<webhookUrl>`.
199
- 5. Пока аккаунт работает, раз в 12 минут `GET /subscriptions` сверяет, что
200
- подписка на `webhookUrl` жива. Если MAX её снял (8 часов недоставки —
201
- упал туннель или gateway), плагин пересоздаёт её `POST /subscriptions` и
202
- пишет warning; ошибка сети при проверке только логируется. Таймер
203
- останавливается вместе с аккаунтом.
204
- 6. **Обработка идёт в задаче аккаунта, а не в HTTP-запросе.** Обработчик роута
205
- проверяет секрет, тело и дубликат, сразу отвечает `200 {"ok":true}` и только
206
- кладёт апдейт в очередь аккаунта. Задача аккаунта (та же, что держит роут)
207
- забирает апдейты и вызывает обработку: в одном чате строго по порядку,
208
- разные чаты — параллельно, не больше 4 одновременно. Причина: gateway
209
- выдаёт HTTP-обработчику допуск работы только на время запроса; запуск
210
- агента из контекста уже завершённого запроса отвергается как
211
- `GatewayDrainingError: Gateway is draining` (так было в 0.7.0). При
212
- остановке аккаунта начатые апдейты дорабатывают (до 3 с ожидания), а
213
- принятые, но не начатые передаются следующему старту аккаунта в этом же
214
- процессе — MAX их уже не повторит.
215
-
216
- Секрет берётся из `webhookSecret`, иначе из `webhookSecretFile`, иначе
217
- генерируется один раз и сохраняется в `~/.openclaw/max/state-<account>.json`
218
- (переживает рестарты). Формат MAX: 5–256 символов `A-Z a-z 0-9 _ -`.
219
-
220
- ### Пример (эта установка)
221
-
222
- Публичный адрес `https://max.kotbanzai.com/max/webhook` проксируется Cloudflare
223
- Tunnel на `http://127.0.0.1:18789` (наружу открыт только путь `/max/webhook`).
212
+ **Упоминания.** При `requireMention` (по умолчанию `true`) бот отвечает в группе, когда его упомянули (`@username` или упоминанием MAX), когда кто-то ответил на его сообщение или нажал его кнопку либо когда текст совпал с шаблонами упоминаний, настроенными в ядре OpenClaw (`messages.groupChat.mentionPatterns` или `groupChat.mentionPatterns` агента). Шаблоны, которые ядро выводит из имени агента, в MAX не используются — только явно заданные. `channels.max.mentionPatterns` ограничивает, где действуют шаблоны: `{ "mode": "deny" }` отключает их для MAX, `allowIn` / `denyIn` перечисляют id групп. Голосовое без подписи в такой группе проверяется по шаблонам — по расшифровке MAX или после однократного распознавания ядром (только в допущенных группах и от допущенных отправителей).
224
213
 
225
- ```bash
226
- umask 077
227
- head -c 48 /dev/urandom | base64 | tr -dc 'A-Za-z0-9_-' | head -c 48 \
228
- > /root/.openclaw/secrets/max-webhook-secret
229
- ```
214
+ **Подтверждения и вопросы.** Кнопки подтверждения (approval) могут нажимать только отправители, явно перечисленные в `allowFrom`; для вопросов `ask_user` достаточно записи `"*"` — но в группе такой ответ засчитывается, только если группа допущена и нажавший есть в её списке отправителей (`groups.<id>.allowFrom` / `groupAllowFrom`), когда список задан.
230
215
 
231
- ```json
232
- {
233
- "channels": {
234
- "max": {
235
- "tokenFile": "/root/.openclaw/secrets/gateway-max-bot-token",
236
- "transport": "webhook",
237
- "webhookUrl": "https://max.kotbanzai.com/max/webhook",
238
- "webhookSecretFile": "/root/.openclaw/secrets/max-webhook-secret"
239
- }
240
- }
241
- }
216
+ **Как узнать id пользователя.** `openclaw pairing list max` показывает id отправителя, который ждёт сопряжения. Id групповых чатов — отрицательные числа.
217
+
218
+ Именованный аккаунт, унаследовавший политику `open`, при старте пишет в лог предупреждение с путём опции, которую стоит задать. Помните об этом наследовании, когда добавляете второго бота.
219
+
220
+ ## Действия инструмента `message` и где они разрешены
221
+
222
+ Действия: `send`, `sendAttachment`, `sticker`, `edit`, `delete`, `pin`, `unpin`.
223
+
224
+ Цели: `user:<id>` для личного диалога, числовой id чата для группы или канала (например, `-70000000000001`). `@username` и ссылки max.ru MAX Bot API не поддерживает, и такие цели отклоняются с понятной ошибкой. Положительное число без префикса, на которое MAX ответил `dialog.not.found`, один раз пробуется заново как id пользователя.
225
+
226
+ ```
227
+ message(action="send", target="user:12345678", message="Hello")
228
+ message(action="send", target="CHAT_ID", message="Choose:",
229
+ buttons=[[{"text":"Yes","type":"callback","payload":"yes"},
230
+ {"text":"More","type":"message"}]])
231
+ message(action="sendAttachment", target="CHAT_ID", path="report.pdf", caption="Report")
232
+ message(action="sendAttachment", target="CHAT_ID", buffer="data:text/plain;base64,SGVsbG8=", filename="hello.txt")
233
+ message(action="send", target="CHAT_ID", path="reply.ogg", asVoice=true)
234
+ message(action="send", target="CHAT_ID", message="Quiet", silent=true)
235
+ message(action="sendAttachment", target="CHAT_ID", type="location", latitude="55.75", longitude="37.62")
236
+ message(action="sendAttachment", target="CHAT_ID", type="contact", contactName="Name", vcfPhone="+70000000000")
237
+ message(action="sticker", target="CHAT_ID", stickerId="CODE")
238
+ message(action="pin", target="CHAT_ID", messageId="MID")
242
239
  ```
243
240
 
244
- ### Порядок включения
241
+ - `attachments=[…]` отправляет несколько файлов: изображения и видео уходят альбомами до 12 штук, аудио и файлы — по одному. В результате есть `messageIds`; элементы, которые не удалось отправить, перечислены в `mediaErrors`.
242
+ - `silent=true` отправляет без push-уведомления; в каналах MAX уведомление приходит всегда.
243
+ - `pin=true` или `delivery.pin` закрепляет отправленное сообщение. В личных диалогах MAX закреплять нельзя: плагин пропускает вызов и возвращает `pinned: false` с причиной.
245
244
 
246
- 1. Сначала задеплоить эту версию плагина и **полностью перезапустить gateway**
247
- с новым кодом, оставаясь в polling-режиме. Старые версии плагина при
248
- `webhookUrl` подписывались, но роут не регистрировали — бот глох.
249
- 2. Затем добавить в конфиг `transport`/`webhookUrl`/`webhookSecretFile`
250
- (поля `transport` и `webhookSecretFile` знает только новая схема) и
251
- перезапустить канал/gateway. Подписку создаёт сам плагин при старте.
252
- 3. Проверить: `GET /subscriptions` показывает ровно один URL, в логе
253
- `MAX webhook subscribed`, сообщение боту доходит до агента.
245
+ **Где разрешены действия (`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` задан); ответ на сообщение другого чата или сообщение, чат которого определить нельзя, получает отказ.
254
246
 
255
- Пошаговый runbook деплоя 0.7.0 на этой установке (бэкапы, проверки, откаты):
256
- `/root/.openclaw/workspace-arseniy/deliverables/max-plugin-upgrade-20260926/deploy-runbook.md`
257
- (рабочий каталог ассистента, вне репозитория).
247
+ - `admitted` (по умолчанию) — как описано выше.
248
+ - `current` — ходы не владельца действуют только в своём чате.
249
+ - `off` — плагин ничего не проверяет (поведение 0.7).
258
250
 
259
- Откат: `transport: "polling"` (или убрать `webhookUrl`) и перезапуск — подписка
260
- удаляется при старте polling. Если плагин не стартует, снять подписку руками:
261
- `curl -X DELETE "https://platform-api2.max.ru/subscriptions?url=<webhookUrl>" -H "Authorization: <token>"`.
251
+ Так инъекция промпта в одном чате не сможет править, удалять или публиковать сообщения в другом.
262
252
 
263
- ## Использование
253
+ ## Медиа и локальные файлы
264
254
 
265
- ### Как написать боту
255
+ - **Входящие** медиа скачиваются после проверок доступа, в пределах `mediaMaxMb` (по умолчанию 20 МБ) и `mediaMaxCount` (по умолчанию 12 на сообщение); о медиа, которые не загрузились, агент получает пометку. Файлы хранит медиахранилище ядра OpenClaw.
256
+ - **Исходящие локальные файлы** читаются только загрузчиком 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 …`. Отключить эти проверки нельзя.
257
+ - **Содержимое в вызове:** `buffer` (base64 или data URL) с `filename` и `contentType`; лимит размера проверяется до декодирования. Если заданы и путь, и `buffer`, используется путь.
258
+ - **Удалённые URL** скачиваются в память через загрузчик ядра с защитой от SSRF. Ссылка на изображение передаётся в MAX как есть, только если её хост — публичный https; иначе изображение скачивается и загружается в MAX.
266
259
 
267
- 1. Найти бота в MAX по нику (например `@idИНН_bot`)
268
- 2. Нажать **Старт** или отправить любое сообщение
269
- 3. Бот ответит, если ваш `user_id` в `allowFrom`
260
+ ## Голосовые сообщения
270
261
 
271
- ### Как узнать свой user_id
262
+ **Входящие голосовые** передаются ядру OpenClaw как аудиомедиа. Если MAX прислал расшифровку, агент получает её как `[Voice transcript: …]`, и ядро не распознаёт запись повторно; иначе ядро распознаёт аудио провайдером, настроенным в `tools.media.audio`. Голосовое, которое не удалось скачать и у которого нет расшифровки, доходит до агента как `[Voice message: audio unavailable, no transcript]`.
272
263
 
273
- Написать боту, затем посмотреть в логах OpenClaw:
264
+ **Голосовые ответы.** Канал сообщает ядру, что принимает для TTS аудиофайлы (`mp3`, `m4a`, `wav`, `ogg`, `opus`). Когда ответ помечен как голосовой — TTS ядра (например, `/tts`), директива `[[audio_as_voice]]` или `asVoice=true` у `send` / `sendAttachment`, — аудио уходит вложением MAX `audio`: отдельного типа «голосовое сообщение» в Bot API нет. Текст ответа доставляется один раз, отдельным сообщением. Если MAX отклоняет аудио, те же байты отправляются файлом.
274
265
 
275
- ```bash
276
- openclaw logs --follow
277
- # В логах будет: sender.user_id: 12345678
278
- ```
266
+ ## Команды и меню
279
267
 
280
- Или запустить тест-скрипт:
268
+ - `channels.max.commands` при старте аккаунта регистрирует в MAX список команд бота (`PATCH /me/commands`): до 32 команд.
269
+ - Сообщение, которое начинается с `/`, обрабатывается как команда OpenClaw.
270
+ - Команды, для которых ядро описывает варианты, — `/think`, `/fast`, `/reasoning`, `/verbose`, `/usage`, `/elevated`, `/trace`, `/activation`, `/send`, `/tts`, `/session`, `/subagents`, `/acp`, `/tools` — без аргумента отвечают меню из кнопок, текущий вариант отмечен ✓. Нажатие применяет команду так же, как ввод `/think high`. Меню и нажатия доступны только отправителям, которым разрешено выполнять команды.
271
+ - Запуск бота по deep link (`max.ru/<bot>?start=<payload>`) доходит до агента как `/start <payload>`.
281
272
 
282
- ```bash
283
- MAX_BOT_TOKEN=xxx node scripts/test-api.mjs
284
- # В разделе updates будет виден ваш user_id
285
- ```
273
+ ## Статус хода
286
274
 
287
- ### allowFrom и pairing
275
+ При стриминге прогресса ядра одно служебное сообщение показывает, чем занят агент: строку статуса, план, запросы подтверждения, а с `toolProgress` — и строки инструментов. Оно правится не чаще раза в секунду, отправляется без уведомления и удаляется после доставки ответа. По умолчанию статус выключен.
276
+
277
+ ```jsonc
278
+ "channels": { "max": { "streaming": { "mode": "progress", "progress": { "toolProgress": true } } } }
279
+ ```
288
280
 
289
- - **allowlist** (по умолчанию): только user_id из списка могут общаться с ботом
290
- - **open**: любой пользователь MAX может писать боту
291
- - **pairing**: новый пользователь получает код, который нужно подтвердить у владельца
281
+ Остальные ключи `streaming.progress` (`label`, `labels`, `maxLines`, `maxLineChars`, `commandText`) — настройки ядра. Если статус не удалось отправить, останавливается только статус; на ход это не влияет.
292
282
 
293
- ## Мультиаккаунт
283
+ ## Несколько аккаунтов и наследование
294
284
 
295
- Можно подключить несколько ботов MAX — например, рабочий и личный:
285
+ Один gateway может обслуживать несколько ботов MAX. `channels.max` верхнего уровня — аккаунт по умолчанию; именованные аккаунты находятся в `accounts`:
296
286
 
297
287
  ```jsonc
298
288
  {
299
289
  "channels": {
300
290
  "max": {
301
- // Аккаунт по умолчанию
302
- "botToken": "токен_основного_бота",
291
+ "tokenFile": "/home/you/.openclaw/secrets/max-main-token",
292
+ "dmPolicy": "allowlist",
303
293
  "allowFrom": ["12345678"],
304
-
305
- // Дополнительные аккаунты
294
+ "maxEventAgeMinutes": 30,
306
295
  "accounts": {
307
- "zaya": {
308
- "botToken": "токен_второго_бота",
309
- "allowFrom": ["87654321"],
310
- "dmPolicy": "open"
296
+ "support": {
297
+ "tokenFile": "/home/you/.openclaw/secrets/max-support-token",
298
+ "dmPolicy": "open",
299
+ "allowFrom": ["*"]
300
+ // наследует maxEventAgeMinutes, groupPolicy, groups, … из channels.max
311
301
  }
312
302
  }
313
303
  }
@@ -315,110 +305,188 @@ MAX_BOT_TOKEN=xxx node scripts/test-api.mjs
315
305
  }
316
306
  ```
317
307
 
318
- Обращение к конкретному аккаунту:
308
+ Именованный аккаунт берёт с уровня канала каждую опцию, которую не задал сам, включая политики доступа и списки; собственное значение аккаунта сильнее, а списки и `groups` заменяются целиком. Собственные опции не наследуются: `botToken`, `tokenFile`, `name`, `transport`, `webhookUrl`, `webhookSecret`, `webhookSecretFile`, `webhookPath`. `channels.max.enabled: false` выключает все аккаунты, `accounts.<id>.enabled: false` — один. `MAX_BOT_TOKEN` действует только для аккаунта верхнего уровня.
319
309
 
320
- ```
321
- openclaw --account zaya send "Привет из второго бота"
322
- ```
310
+ Аккаунт в CLI выбирается ключом `--account`: `openclaw message send --channel max --account support --target user:87654321 --message "Hi"`.
323
311
 
324
- ## Разработка
312
+ ## Прокси, адрес API и сертификаты Russian Trusted CA
313
+
314
+ - **`httpProxy`** (например, `http://proxy.example.com:3128`, можно с учётными данными) направляет весь трафик MAX этого аккаунта через HTTP(S)-прокси: вызовы Bot API, загрузку файлов, скачивание входящих вложений и удалённых медиа. Loopback-адреса идут мимо прокси. Учётные данные прокси никогда не попадают в логи и ошибки; в логе старта адрес выглядит как `http://***@host:port`.
315
+ - **`apiBaseUrl`** направляет плагин на другой адрес Bot API, например на тестовый стенд; префикс пути сохраняется. Принимается только `https` (`http` — лишь для loopback), поэтому токен никогда не передаётся открытым текстом.
316
+ - Неверное значение любой из этих опций останавливает аккаунт при старте ошибкой с именем опции.
317
+ - **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` не нужен.
318
+
319
+ ## Секреты и SecretRef
320
+
321
+ `botToken`, `webhookSecret` и `httpProxy` принимают обычную строку или SecretRef OpenClaw:
325
322
 
326
- ### Требования
323
+ ```jsonc
324
+ "botToken": { "source": "env", "provider": "default", "id": "MAX_BOT_TOKEN" }
325
+ ```
327
326
 
328
- - Node.js 22+
329
- - OpenClaw 2026.9.6+ (devDependency, SDK-подпути `openclaw/plugin-sdk/*`)
327
+ Провайдер должен быть описан в `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` обычной строкой.
328
+
329
+ `tokenFile` и `webhookSecretFile` по-прежнему поддерживаются. `openclaw channels status` показывает `tokenSource` (`config`, `file`, `env`, `none`) и `tokenStatus` (`available`, `configured_unavailable`, `missing`), не раскрывая токен.
330
+
331
+ ## Надёжность
332
+
333
+ - **Долговечная очередь webhook.** Каждое принятое событие webhook записывается в `<stateDir>/max/inbox-<account>/` до того, как MAX получит `200`. После рестарта или падения первыми обрабатываются необработанные события, в исходном порядке внутри каждого чата. Событие считается завершённым, как только ядро приняло ход агента (прерванный ход ядро возобновляет само). Если событие не удалось записать или очередь заполнена (`webhookQueue.maxPending`), MAX получает `503` и повторяет доставку позже (`overflow: "drop"` вместо этого подтверждает событие и отбрасывает его). Если каталог состояния недоступен для записи, очередь с предупреждением переходит в память. Собственная очередь входящих событий SDK в OpenClaw 2026.9.x доступна только встроенным и официальным плагинам, поэтому плагин ведёт свой журнал по тому же контракту.
334
+ - **Дедупликация.** Ключи обработанных событий (`update_type:timestamp:mid`; у событий без сообщения вместо `mid` — чат и пользователь) хранятся 24 часа, до 5000 штук, и переживают рестарт, поэтому повторные доставки MAX отбрасываются. Long polling использует те же ключи и, как очередь webhook, отмечает событие обработанным, как только ядро приняло ход: после рестарта или падения посреди пачки — даже посреди хода — обработанные события не повторяются, а прерванный ход ядро возобновляет само, заново он не запускается.
335
+ - **Завершение gateway.** Пока gateway штатно останавливается или перезапускается, ядро не принимает новые ходы агента, а webhook и long polling продолжают получать события. Такое событие не теряется: плагин держит его в очереди и повторяет через 2, 5, 10 и далее каждые 30 секунд, а следующие события того же чата ждут за ним. Если gateway остановился раньше, событие остаётся в журнале на диске и обрабатывается после старта (long polling к тому же не сохраняет маркер пачки). Отказ ядра после того, как оно приняло ход, и другие ошибки обработки не повторяются. В очереди в памяти (`webhookQueue.mode: "memory"` или каталог состояния недоступен для записи) такое событие, как и раньше, пропускается с ошибкой в журнале.
336
+ - **Возраст событий.** Сообщения, правки, нажатия кнопок и запуски бота старше `maxEventAgeMinutes` (по умолчанию 60) пропускаются с предупреждением — после долгого простоя бот не отвечает на сообщения многочасовой давности. События реестра чатов обрабатываются всегда.
337
+ - **Ошибки доставки** передаются ядру: не ушло ничего — ответ считается неотправленным; видна часть — это частичная доставка с id видимых сообщений. Если часть текста не отправилась, следующие части не отправляются.
338
+ - **Длинные ответы** делятся по 4000 символов (или по меньшему `textChunkLimit`); при `streamMode: "partial"` первая часть заменяет черновик, остальные идут новыми сообщениями, кнопки — на последнем.
339
+ - **Ответ из нескольких частей.** Если ядро отдаёт ответ несколькими частями, в черновик стриминга или в ответ на нажатие кнопки (`POST /answers`) идёт только первая; остальные части, а также блоки `streamMode: "block"`, приходят новыми сообщениями, и ни одна часть не затирает другую.
340
+ - **Черновик стриминга** (`streamMode: "partial"`). Обновления черновика отправляются по одному, второго черновика не бывает; финал ждёт отправки черновика, которая уже идёт. Выводы инструментов (режим verbose) приходят отдельными сообщениями, а черновик продолжает стримиться. Черновик, который не стал ответом (`NO_REPLY`, `/stop`, ответ через инструмент `message` или ответ только из медиа), в конце хода удаляется; после ошибки обработки остаётся.
341
+ - **Повторы:** запросы с ответом `429` и сетевыми ошибками повторяются; `attachment.not.ready` после загрузки повторяется; отправка текста ограничена лимитом MAX — 2 сообщения в секунду на чат.
342
+
343
+ ## Лимиты MAX API
344
+
345
+ По документации MAX Bot API:
346
+
347
+ | Лимит | Значение |
348
+ |---|---|
349
+ | Текст сообщения | 4000 символов (более длинный текст плагин делит) |
350
+ | Отправка | 2 сообщения в секунду на диалог, группу или канал (плагин ставит отправки в очередь) |
351
+ | Правка | Только собственные сообщения бота. В диалогах: сообщения с inline-клавиатурой — любой давности, остальные — в течение 7 дней. В групповых чатах и каналах — любой давности. Не больше 2 правок в секунду на чат |
352
+ | Удаление | Боту нужны права администратора с разрешением на удаление. В диалогах — только собственные сообщения бота, в группах и каналах — любые. Не больше 2 удалений в секунду на чат |
353
+ | Альбом | До 12 изображений и видео в одном сообщении; файлы и аудио — отдельно |
354
+ | Кнопки | Текст ≤ 128 символов, payload ≤ 1024 байт, ≤ 30 рядов, ≤ 210 кнопок |
355
+ | Команды бота | До 32 |
356
+ | Webhook | HTTPS на порту 443, доверенный сертификат, `200` в течение 30 с; подписка снимается после 8 часов неудачных доставок |
357
+ | Long polling | Не для рабочей эксплуатации; недоступен, пока есть подписка webhook |
358
+
359
+ ## Безопасность
360
+
361
+ - **Контроль доступа по умолчанию:** личные сообщения требуют сопряжения, группы — списка разрешённых, списки отправителей в группах соблюдаются.
362
+ - **Где разрешены действия:** инструмент `message` не может действовать в чатах, которые не допускает политика входящих (см. [выше](#действия-инструмента-message-и-где-они-разрешены)).
363
+ - **Локальные файлы:** только из каталогов, которые OpenClaw разрешает агенту; выйти за их пределы через `..` и символические ссылки нельзя. Агенту с чтением с хоста ядро разрешает любые пути, но только проверенные по содержимому типы файлов.
364
+ - **Удалённые медиа:** скачиваются через SSRF-защиту ядра; приватные, loopback- и link-local-адреса, а также адреса сервисов метаданных не передаются в MAX ссылками.
365
+ - **Webhook:** секрет проверяется за постоянное время до чтения тела запроса.
366
+ - **Секреты:** поддерживается SecretRef; токен, секрет webhook и учётные данные прокси не пишутся в логи.
367
+ - **TLS:** дополнительным российским удостоверяющим центрам плагин доверяет только в собственных соединениях.
368
+ - **Логи:** по умолчанию без текста сообщений.
369
+
370
+ **Прокси и внутренние адреса.** Если задан `httpProxy`, имена хостов скачиваемых медиа разрешает прокси, а не плагин: литеральные приватные адреса и заблокированные имена по-прежнему отклоняются, но имя, которое DNS прокси разрешает во внутренний адрес, будет скачано из сети прокси. Поэтому прокси, стоящий во внутренней сети, даёт путь к адресам этой сети. Используйте прокси, у которого нет доступа к внутренним ресурсам.
371
+
372
+ **Известные ограничения.**
373
+
374
+ - Если во время работы gateway кончилось место на диске или диск стал доступен только для чтения, webhook отвечает `503` на каждое событие и бот не отвечает, пока место не освободится.
375
+ - В допущенной группе голосовое без подписи, которое MAX не расшифровал, отправляется на распознавание речи, чтобы найти упоминание бота. Распознавание платное и не ограничено по частоте; оно включается только при заданных шаблонах упоминаний (`groups.<id>.disableAudioPreflight` отключает его для группы).
376
+ - Кнопка, нагрузка которой начинается с `/`, при нажатии выполняется как команда от имени нажавшего и с его правами — так же, как в каналах ядра.
377
+ - Событие, обработка которого роняет процесс gateway (а не вызывает ошибку, которую ловит плагин), обрабатывается снова после каждого старта, пока не станет старше `maxEventAgeMinutes`; при `0` — без ограничения.
378
+ - `pin` и `unpin` проверяют чат, но не автора сообщения: в допущенном чате, где бот — администратор, они закрепляют и открепляют любое сообщение (в отличие от `edit` и `delete` вне текущего чата).
379
+
380
+ Об уязвимостях сообщайте приватно, как описано в [SECURITY.md](https://github.com/aspalagin/openclaw-max/blob/main/SECURITY.md). Поддерживаемая версия: 0.8.x.
381
+
382
+ ## Конфиденциальность
383
+
384
+ Что плагин хранит на диске (`<stateDir>` — каталог состояния OpenClaw, по умолчанию `~/.openclaw`):
385
+
386
+ | Путь | Содержимое | Срок хранения |
387
+ |---|---|---|
388
+ | `<stateDir>/max/state-<account>.json` | Маркер long polling; реестр чатов (id чата, тип, название, когда бота добавили, удалили или остановили); сгенерированный секрет webhook, если он есть | Пока файл не удалят |
389
+ | `<stateDir>/max/inbox-<account>/` (каталог `0700`, файлы `0600`) | События webhook, ожидающие обработки, включая текст сообщений и метаданные вложений | Удаляются после обработки |
390
+ | `<stateDir>/max/inbox-<account>/completed.json` | Ключи обработанных событий (`update_type:timestamp:mid`), без содержимого | 24 часа, до 5000 ключей |
391
+
392
+ `webhookQueue.mode: "memory"` держит ожидающие события webhook только в памяти (файл ключей остаётся). Скачанные входящие медиа и историю разговоров хранит ядро OpenClaw, а не плагин.
393
+
394
+ Логи: тип сообщения, длина текста, id чата, сообщения и пользователя, ошибки и тайминги. Текст сообщений, подписи и расшифровки в логи не пишутся, если не включён `logMessagePreview: true` (превью в 50 символов на уровне debug). Токены, секреты и учётные данные прокси не пишутся в логи никогда.
395
+
396
+ Сетевые обращения плагина: MAX Bot API, его хосты загрузки и CDN (через `httpProxy`, если он задан) и хосты удалённых медиа, которые отправляет агент. Если у голосового нет расшифровки MAX, ядро OpenClaw отправляет аудио провайдеру распознавания речи, настроенному в `tools.media.audio`.
397
+
398
+ ## Устранение неполадок
399
+
400
+ | Симптом | Вероятная причина и что делать |
401
+ |---|---|
402
+ | Бот не отвечает в личных сообщениях | `dmPolicy` — `pairing`, а отправитель не подтверждён (`openclaw pairing list max`), или `allowlist`, а отправителя нет в `allowFrom` |
403
+ | Бот не отвечает в группе | Чата нет в `groups` (`groupPolicy: allowlist`); бота не упомянули (`requireMention`); отправителя нет в `groups.<id>.allowFrom` / `groupAllowFrom`; бот не администратор группы — тогда MAX не доставляет ему сообщения группы (замечено при long polling) |
404
+ | `MAX bot token not configured: …` в статусе | Не задан ни `botToken`, ни `tokenFile`, ни `MAX_BOT_TOKEN`; в сообщении указан путь опции. `tokenFile`, которого нет, который пуст или является символической ссылкой, не читается (`tokenFile is missing, empty or not a regular file`); аккаунт по умолчанию тогда берёт `MAX_BOT_TOKEN`, если он задан |
405
+ | Аккаунт не стартует, в ошибке упоминается SecretRef | Ссылка не разрешилась; проверьте `secrets.providers` и переменную, файл или команду, на которые она указывает |
406
+ | Webhook: события не приходят | Проверьте публичный URL, сертификат и то, что обратный прокси пробрасывает путь; после 8 часов сбоев MAX снимает подписку — плагин восстановит её в течение 12 минут; пока подписка есть, long polling не работает |
407
+ | MAX получает `503` от webhook | Журнал не удалось записать (диск заполнен, нет прав) или достигнут `webhookQueue.maxPending` |
408
+ | Старые сообщения после простоя остаются без ответа | Они старше `maxEventAgeMinutes` (по умолчанию 60); задайте `0`, чтобы отвечать на сообщения любой давности |
409
+ | `Local media path is not under an allowed directory` | Файл лежит вне каталогов, разрешённых агенту; скопируйте его в рабочий каталог |
410
+ | `Host-local media sends only allow …` | У агента чтение с хоста, а тип файла ядро не распознало или не разрешает; отправьте файл разрешённого типа или передайте содержимое через `buffer` |
411
+ | Инструмент `message` получает отказ в другом чате | `actionScope`: политика входящих не допускает этот чат; добавьте чат в политику или измените `actionScope` |
412
+ | `MAX API does not resolve @username …` | Используйте `user:<id>` или числовой id чата |
413
+ | `pinned: false` в личном диалоге | В диалогах MAX закреплять нельзя |
414
+ | В сообщении видны `**` или `_` | MAX отклонил разметку, и плагин отправил текст заново без неё |
415
+ | Аккаунт останавливается при старте с ошибкой прокси или адреса API | `httpProxy` или `apiBaseUrl` заданы неверно, или `apiBaseUrl` использует `http` для хоста, который не loopback |
416
+ | `/think` отвечает кнопками, а не текстом | Это меню команд: чтобы задать значение сразу, отправьте `/think high` |
417
+ | Предупреждение о том, что аккаунт наследует открытую политику | Именованный аккаунт без своих `dmPolicy` / `groupPolicy` наследует `open`; задайте политику в `accounts.<id>` |
418
+
419
+ ## Обновление с 0.7
420
+
421
+ Конфиг 0.7 работает без изменений. Что меняется в поведении и как вернуть поведение 0.7:
422
+
423
+ | Изменение в 0.8 | Поведение 0.7 |
424
+ |---|---|
425
+ | В ходе не владельца инструмент `message` действует только в текущем чате и в чатах, которые допускает политика входящих | `actionScope: "off"` (строже — `"current"`) |
426
+ | Вызов без `senderIsOwner: true` (heartbeat, субагент, запуск по расписанию без авторитета сообщений) ограничен допущенными чатами; в группе со списком отправителей — отказ | `actionScope: "off"` |
427
+ | Вне чата текущего хода `edit` и `delete` не трогают чужие сообщения — только сообщения бота | `actionScope: "off"` |
428
+ | `replyTo` отправки должен указывать на сообщение того же чата, куда она уходит; иначе отказ | `actionScope: "off"` |
429
+ | Локальные файлы читает защищённый загрузчик ядра: из разрешённых агенту каталогов, а при чтении с хоста — только проверенных типов | Переключателя нет: копируйте файлы в рабочий каталог агента |
430
+ | Сообщения, правки, нажатия кнопок и запуски бота старше 60 минут, включая повторные доставки MAX после простоя, остаются без ответа | `maxEventAgeMinutes: 0` |
431
+ | Webhook отвечает `200` после записи события на диск; `503` — если запись не удалась или очередь заполнена | `webhookQueue.overflow: "drop"`, `webhookQueue.mode: "memory"` |
432
+ | Ожидающие события webhook вместе с текстом сообщений хранятся в `<stateDir>/max/inbox-<account>/` до обработки | `webhookQueue.mode: "memory"` (файл ключей остаётся) |
433
+ | Именованные аккаунты наследуют все опции уровня канала, включая `dmPolicy`, `allowFrom`, `groupPolicy`, `groups` | Задайте политики в каждом аккаунте |
434
+ | `channels.max.enabled: false` выключает и именованные аккаунты | Оставьте канал включённым; чтобы работали только именованные аккаунты, не задавайте токен на верхнем уровне (`botToken`, `tokenFile`, `MAX_BOT_TOKEN`) |
435
+ | Непустой `groupAllowFrom` или `groups.<id>.allowFrom` ограничивает, кто может обращаться к боту в допущенной группе | Удалите списки или добавьте `"*"` |
436
+ | Из одного входящего сообщения скачивается не больше 12 медиа | Увеличьте `mediaMaxCount` |
437
+ | Пересланные сообщения запускают ход агента (раньше отбрасывались) | — |
438
+ | Сводки инструментов (режим verbose) приходят без превью ссылок | `disableLinkPreview: false` |
439
+ | `/think`, `/fast`, `/reasoning` и другие команды с меню без аргумента отвечают кнопками | — (с аргументом работают как раньше) |
440
+ | Заданные в ядре `messages.groupChat.mentionPatterns` теперь действуют в группах MAX | `mentionPatterns: { "mode": "deny" }` |
441
+ | Флаг `silent` ядра соблюдается; индикатор набора подчиняется `typingMode` ядра и держится весь ход | Настройки ядра |
442
+ | Если MAX отклонил разметку, ответ приходит обычным текстом (с видимыми `**`, `_`) | — |
443
+ | В debug-логах нет текста сообщений | `logMessagePreview: true` |
444
+ | `openclaw secrets audit` отмечает `botToken`, `webhookSecret` и `httpProxy`, записанные строкой | Перенесите их в SecretRef, `tokenFile` или `webhookSecretFile` |
445
+ | `streaming.mode` сильнее `streamMode` | Оставьте только один из ключей |
446
+ | Polling после ошибки ждёт 2–60 с (было 3 с), после `401` — 5 минут | — |
447
+ | Опубликованный пакет больше не содержит `scripts/` | Скрипты остаются в репозитории |
448
+ | Действуют `groups.<id>.enabled`, `groups.<id>.systemPrompt`, `groups.<id>.skills`, `actions` и `textChunkLimit` (в 0.7 схема их принимала, но они не читались) | Уберите эти ключи из конфига |
449
+
450
+ Полный список — в [журнале изменений](https://github.com/aspalagin/openclaw-max/blob/main/CHANGELOG.md).
330
451
 
331
- ### Сборка
452
+ ## Разработка
332
453
 
333
454
  ```bash
455
+ git clone https://github.com/aspalagin/openclaw-max.git
334
456
  cd openclaw-max
335
457
  npm ci
336
- npm run build # tsc → dist/index.js, dist/setup-entry.js, dist/src/*.js (без тестов)
458
+ npm run typecheck # запускать до build: build собирает и при ошибках типов
459
+ npm run build # dist/index.js, dist/setup-entry.js, dist/secret-contract-api.js, dist/src/*.js
337
460
  ```
338
461
 
339
- `build` собирает и при ошибках типов (`--noEmitOnError false`), поэтому перед ним
340
- запускайте `npm run typecheck`.
341
-
342
- ### Проверки (как в CI)
462
+ Проверки, как в CI:
343
463
 
344
464
  ```bash
345
465
  npm run format:check # prettier
346
- npm run lint # eslint, 0 ошибок и 0 предупреждений
347
- npm run check:cycles # циклические импорты между модулями src/
466
+ npm run lint # eslint
467
+ npm run check:cycles # циклы импортов между модулями src/
348
468
  npm run typecheck
349
- npm test # vitest, включая сверку со схемой MAX
469
+ npm test # vitest, включая сверку со снимком схемы MAX
470
+ npm audit --omit=dev --omit=peer # зависимости времени выполнения, которые входят в пакет
350
471
  ```
351
472
 
352
- ### Схема MAX Bot API
473
+ CI также запускает `typecheck` и тесты на `openclaw@latest` (с zod из lockfile и с минимальной поддерживаемой версией zod) и на `openclaw@beta`; сбой на `beta` попадает в отчёт, но сборку не проваливает.
353
474
 
354
- `src/schema-conformance.test.ts` сверяет подписку, типы update/кнопок/вложений и
355
- тела запросов со снимком `src/__fixtures__/max-schema-<версия>.yaml`
356
- ([max-messenger/api-schema](https://github.com/max-messenger/api-schema)).
357
- Обновить снимок:
475
+ - `src/__fixtures__/max-schema-0.0.33.yaml` — снимок схемы MAX Bot API; `npm run schema:update [ref]` его обновляет.
476
+ - `npm run test:api` обращается к живому API с `MAX_BOT_TOKEN` (`GET /me`, `GET /updates`); вспомогательные скрипты из `scripts/` есть только в репозитории, в пакет они не входят.
477
+ - В тестах — только вымышленные токены и id.
358
478
 
359
- ```bash
360
- npm run schema:update # последний коммит репозитория схемы
361
- npm run schema:update -- <commit> # конкретный коммит, тег или ветка
362
- npm test
363
- ```
479
+ Вопросы и предложения: [GitHub Issues](https://github.com/aspalagin/openclaw-max/issues). Правила участия: [CONTRIBUTING.md](https://github.com/aspalagin/openclaw-max/blob/main/CONTRIBUTING.md).
364
480
 
365
- Скрипт заменяет старый снимок новым (имя по `info.version`) и пишет в заголовок
366
- файла коммит и дату. Упавший тест сверки означает, что плагин отправляет или ждёт
367
- то, чего в новой схеме нет: поправить код или тип, затем закоммитить снимок.
481
+ ## Благодарности, авторы и лицензия
368
482
 
369
- ### Тест API (проверка что токен работает)
483
+ **Благодарности.** С 28 мая 2026 года (синхронизация с версией 0.5.0, коммит `d8d24bc`) плагин содержит код, производный от npm-пакета `openclaw-max` 0.5.0 Евгения Быстрова, опубликованного под лицензией MIT (Copyright (c) 2026 Evgeniy Bystrov): [github.com/evgeniyvbystrov/openclaw-max](https://github.com/evgeniyvbystrov/openclaw-max). Уведомление об авторских правах Евгения Быстрова сохранено в [LICENSE](LICENSE). Спасибо.
370
484
 
371
- ```bash
372
- # Проверить бота: GET /me + GET /updates
373
- MAX_BOT_TOKEN=xxx node scripts/test-api.mjs
485
+ **Авторы.**
374
486
 
375
- # С отправкой тестового сообщения в чат
376
- MAX_BOT_TOKEN=xxx node scripts/test-api.mjs <chat_id>
377
- ```
487
+ - [Petlevoy](https://github.com/petlevoy) — основатель проекта.
488
+ - [Арсений Палагин](https://github.com/aspalagin) — сопровождающий.
378
489
 
379
- ### Тест отправки (send + edit + delete)
380
-
381
- ```bash
382
- # Полный цикл: отправить → подождать → отредактировать → подождать → удалить
383
- MAX_BOT_TOKEN=xxx node scripts/test-send.mjs <chat_id> "Текст сообщения"
384
- ```
490
+ Разработка ведётся с помощью ИИ-агентов.
385
491
 
386
- ## Поддержка
387
-
388
- - Вопросы по использованию и предложения: [GitHub Issues](https://github.com/aspalagin/openclaw-max/issues)
389
- - Сообщения об уязвимостях: [Security policy](SECURITY.md)
390
- - Правила участия: [CONTRIBUTING.md](CONTRIBUTING.md)
391
-
392
- ## MAX Bot API — краткая справка
393
-
394
- Базовый URL: `https://platform-api2.max.ru` (старый `platform-api.max.ru` отключается 19.07.2026).
395
-
396
- | Метод | Endpoint | Описание |
397
- |-------|----------|----------|
398
- | GET /me | Информация о боте | user_id, name, username, commands |
399
- | PATCH /me/commands | Команды бота | commands (полная замена списка, до 32) |
400
- | GET /updates | Long polling | marker, timeout, types |
401
- | POST /messages | Отправить | ?chat_id или ?user_id |
402
- | PUT /messages | Редактировать | ?message_id (до 24ч) |
403
- | DELETE /messages | Удалить | ?message_id (до 24ч) |
404
- | POST /answers | Ответ на callback | ?callback_id |
405
- | GET /chats/{id}/members/me | Членство бота | is_admin (нужно для получения событий групп) |
406
- | PUT/DELETE /chats/{id}/pin | Закрепить/открепить | message_id |
407
- | GET /videos/{token} | Playback-ссылки видео | urls может быть null, пока видео обрабатывается |
408
- | POST /uploads | URL для загрузки медиа | type=image/video/audio/file |
409
-
410
- Авторизация: заголовок `Authorization: <token>`
411
- Лимит: 30 запросов/сек
412
- Документация: [dev.max.ru/docs-api](https://dev.max.ru/docs-api)
413
-
414
- Примечания:
415
- - `GET /chats` объявлен deprecated (июнь 2026) — плагин собирает чаты в собственный реестр из событий.
416
- - В группах long polling доставляет события только боту-администратору (проверяется в `openclaw channels status --audit`).
417
- - Webhook: только HTTPS:443 с доверенным сертификатом; MAX ждёт HTTP 200 не дольше 30с (плагин отвечает мгновенно, обработка асинхронная).
418
-
419
- ## Авторы
420
-
421
- - **[Petlevoy](https://github.com/petlevoy)** - отец проекта
422
- - **Яков** (@Helpdesk_VP_bot) — архитектура, координация
423
- - **Банзай** (@KotBanzaiBot) — реализация модулей, типы, тесты
424
- - **openclaw-max subagent** — интеграция с OpenClaw Plugin SDK
492
+ **Лицензия:** MIT, см. [LICENSE](LICENSE).