@retensy/mcp 0.34.0 → 0.36.0
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.
- package/README.md +9 -3
- package/package.json +2 -2
- package/skills/build-bot-funnel/SKILL.md +2 -1
- package/skills/build-bot-funnel/reference/schema.md +9 -4
- package/skills/build-bot-funnel/reference/validation.md +1 -1
- package/skills/build-bot-funnel/validate.mjs +5 -4
- package/src/agent-tools.mjs +88 -0
- package/src/index.mjs +18 -3
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
57
57
|
- 📰 **Публикации и VK**: `publications_usage` — месячная квота (общая для Telegram/MAX/Instagram/VK), `vk_status`/`vk_connect`/`vk_disconnect`/`vk_post` — подключение VK-сообщества токеном и публикация на стену.
|
|
58
58
|
- 🔗 **Ссылка вместо отказа**: что нельзя сделать через API (вход через Google/Instagram, оплата тарифа, вход в аккаунт) — инструмент возвращает прямую ссылку и одну строку, что сделать.
|
|
59
59
|
- 📝 **Статьи блога** (тот же токен `zmcp_…`): `article_publish`, `article_update`, `article_list`, `article_get` — публикация статей в Markdown (как README на GitHub) в раздел **/articles**.
|
|
60
|
-
- 🧠 **ИИ-агенты и база знаний**: `agent_list`/`agent_get`/`agent_create`/`agent_update`/`agent_publish`/`agent_health`/`agent_test_chat`/`agent_unanswered`, `kb_docs`/`kb_add_qa`/`kb_add_text`/`kb_add_site`/`kb_reindex` — настройка ИИ-агента и его базы знаний (раздел «ИИ-агенты»).
|
|
60
|
+
- 🧠 **ИИ-агенты и база знаний**: `agent_list`/`agent_get`/`agent_create`/`agent_update`/`agent_publish`/`agent_health`/`agent_test_chat`/`agent_unanswered`, `agent_tool_calls`, `kb_docs`/`kb_add_qa`/`kb_add_text`/`kb_add_site`/`kb_reindex` — настройка ИИ-агента и его базы знаний (раздел «ИИ-агенты»).
|
|
61
61
|
- 📎 **Медиа**: `upload_file` грузит фото/видео/документы в библиотеку **/bots/files** (до 50 МБ) и возвращает публичный URL — его вставляешь в медиа-карточку сценария.
|
|
62
62
|
- 🌐 **Сайты из блоков** (раздел «Страницы»): создание, правка операциями (блоки, Zero-блок со свободной вёрсткой, код блока, папки страниц, дизайны, шаблоны из библиотеки), публикация и откат, свои домены, заявки из форм и куда их доставлять — инструменты `site_*` + скилл `build-site`.
|
|
63
63
|
- 🧠 **Скилл `build-bot-funnel`**: учит агента собирать корректный граф (типы узлов, ветки, кнопки, задержки) и проверять его перед публикацией. Поддерживает Telegram, MAX и Instagram.
|
|
@@ -69,7 +69,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
69
69
|
|
|
70
70
|
| Платформа | Онбординг | Триггеры входа | Ограничения |
|
|
71
71
|
|---|---|---|---|
|
|
72
|
-
| **Telegram** | Токен бота (BotFather) → `create_bot` | `/start`, команды, callback, текст,
|
|
72
|
+
| **Telegram** | Токен бота (BotFather) → `create_bot` | `/start`, команды, callback, текст, рассылки, **Telegram Business** (бот отвечает от имени аккаунта владельца: `business_message`, условие `BUSINESS`) | Полный функционал |
|
|
73
73
|
| **MAX** | Токен бота (MasterBot в MAX) → `create_bot` | Команды, callback, текст | Без SUBSCRIBED/reply-клавиатур (мягкие предупреждения) |
|
|
74
74
|
| **Instagram** | Вход через Instagram (OAuth) в кабинете `/bots/instagram` (без токена) — нужен Instagram-аккаунт Business или Creator | Комментарий/Direct/Ответ на историю/Упоминание | Ограниченный набор узлов; DELAY ≤ 24ч; ASK_QUESTION только TEXT/EMAIL/PHONE/NUMBER/CONTACT (CONTACT = ручной ввод номера); без рассылок |
|
|
75
75
|
|
|
@@ -319,7 +319,8 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
319
319
|
| `site_analytics_events(siteId, from?, to?, page?, size?)` | сырые события сайта постранично (`PAGE_VIEW`\|`CLICK`\|`FORM_SUBMIT`, UTM) |
|
|
320
320
|
| `bot_subscriber_analytics(botId, from, to)` | новые подписчики/активность бота по дням (дашборд аналитики, диапазон до 92 дней) |
|
|
321
321
|
| `scenario_timeseries(graphId, from, to)` | дневной ряд прогонов сценария `{ok, failed, other}` (по узлам — `graph_analytics`) |
|
|
322
|
-
| `agent_tool_test(agentId, toolId, params?)` | проверить инструмент агента
|
|
322
|
+
| `agent_tool_test(agentId, toolId, params?, live?)` | проверить инструмент агента напрямую, без модели — бюджет ИИ не тратится. Чтение: `AMO_LOOKUP`/`SHEETS_LOOKUP`/`HTTP_API`. Запись (`AMO_CREATE_LEAD`, `AMO_UPDATE_LEAD`, `AMO_UPDATE_CONTACT`, `SHEETS_APPEND`, `HTTP_POST`, `SET_FIELD`, `ADD_TAG`, `HANDOFF`): по умолчанию песочница (ничего не уходит вовне), `live:true` отправляет по-настоящему |
|
|
323
|
+
| `agent_tool_calls(agentId, toolId?, limit?)` | журнал вызовов инструментов записи агента: кто/когда/аргументы без секретов/результат |
|
|
323
324
|
| `site_lead_file_download(siteId, leadId, fileId)` | метаданные вложения заявки + ссылка на скачивание (сам файл через MCP не передаётся — HEAD-запрос, не GET) |
|
|
324
325
|
| `scenario_list()` | сценарии автора, включая вебхук-сценарии (`sourceType: WEBHOOK`\|`BOT`) — в `list_graphs` вебхук-сценарии не входят |
|
|
325
326
|
| `scenario_create(name, botId?)` | создать сценарий: без `botId` — вебхук-сценарий (сервер сам выпускает путь входа и секрет), с `botId` — бот-сценарий, сразу привязанный к боту |
|
|
@@ -481,3 +482,8 @@ RETENSY_MCP_TOKEN=zmcp_... node src/index.mjs # стартует stdio MCP-с
|
|
|
481
482
|
## Лицензия
|
|
482
483
|
|
|
483
484
|
MIT — см. [LICENSE](LICENSE).
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
## Инструменты агента на запись
|
|
488
|
+
|
|
489
|
+
`agent_update {patch:{tools:[...]}}` включает агенту инструменты записи по одному: создать/обновить сделку и контакт в amoCRM (через подключение из «Подключений»), добавить строку в Google Таблицу, POST на свой API (SSRF-защита, без редиректов), сохранить поле/тег подписчика, передать оператору. У каждого — JSON-схема аргументов (`argsSchema`), шаблоны `{аргумент}`, подтверждение у пользователя (`confirmBeforeRun`, по умолчанию включено для внешних записей), лимиты на диалог и сутки, идемпотентность. Список проверяется локально до отправки. Проверка — `agent_tool_test` (песочница по умолчанию), журнал — `agent_tool_calls`. Не работает в публичном веб-чате агента.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retensy/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "MCP server to build and publish Telegram and MAX bot funnels/automations, broadcasts, service integrations, block-based websites (incl. free-layout Zero blocks, designs, templates, custom domains, form leads) and Markdown blog articles in the Retensy Bots service. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"start": "node src/index.mjs",
|
|
23
23
|
"check": "node --check src/index.mjs",
|
|
24
|
-
"test": "node scripts/smoke.mjs && node scripts/telemetry.test.mjs && node scripts/live-edit-422.test.mjs && node scripts/broadcast-connect.test.mjs && node scripts/integration-core.test.mjs && node scripts/site-init.test.mjs && node scripts/ai-agents.test.mjs && node scripts/battery-tools.test.mjs && node scripts/parity-tools.test.mjs && node scripts/parity-2.test.mjs && node scripts/validate-graph.test.mjs && node scripts/call-scenario.test.mjs && node scripts/audience-publications.test.mjs && node scripts/chains-analytics.test.mjs && node scripts/platform-decisions.test.mjs && node scripts/token-scopes.test.mjs && node scripts/secondary-graphs.test.mjs && node scripts/graph-versions.test.mjs",
|
|
24
|
+
"test": "node scripts/smoke.mjs && node scripts/telemetry.test.mjs && node scripts/live-edit-422.test.mjs && node scripts/broadcast-connect.test.mjs && node scripts/integration-core.test.mjs && node scripts/site-init.test.mjs && node scripts/ai-agents.test.mjs && node scripts/agent-write-tools.test.mjs && node scripts/battery-tools.test.mjs && node scripts/parity-tools.test.mjs && node scripts/parity-2.test.mjs && node scripts/validate-graph.test.mjs && node scripts/call-scenario.test.mjs && node scripts/audience-publications.test.mjs && node scripts/chains-analytics.test.mjs && node scripts/platform-decisions.test.mjs && node scripts/token-scopes.test.mjs && node scripts/secondary-graphs.test.mjs && node scripts/graph-versions.test.mjs",
|
|
25
25
|
"validate": "node skills/build-bot-funnel/validate.mjs"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
@@ -9,6 +9,7 @@ description: Собрать воронку (сценарий) бота для с
|
|
|
9
9
|
|
|
10
10
|
**Различия платформ (кратко):**
|
|
11
11
|
- **Telegram** — полный функционал: `/start` и другие команды, все типы узлов, SUBSCRIBED, кнопки-контакты, рассылки.
|
|
12
|
+
- **Telegram Business** — тот же Telegram-бот может отвечать клиентам от имени личного/бизнес-аккаунта владельца (подключает владелец в настройках Telegram; сообщения идут в отдельной сессии, триггер `business_message`, условие `BUSINESS`; подробности в schema.md).
|
|
12
13
|
- **MAX** — те же узлы, но без SUBSCRIBED/reply-клавиатур (мягкие предупреждения при публикации, не блокируют).
|
|
13
14
|
- **Instagram** — ограниченный набор узлов; вход только через комментарий/директ/историю (нет `/start`); нет рассылок; DELAY не более 24ч; ASK_QUESTION только TEXT/EMAIL/PHONE/NUMBER/CONTACT (CONTACT = ручной ввод номера, кнопки «Поделиться номером» в IG нет); подключение только в браузере — вход через Instagram (OAuth) на странице `/bots/instagram`, нужен Instagram-аккаунт Business/Creator; без токена бота.
|
|
14
15
|
|
|
@@ -30,7 +31,7 @@ description: Собрать воронку (сценарий) бота для с
|
|
|
30
31
|
- **Не добавляй узлы `END`** — ветка завершается сама на узле без исходящих рёбер; явный «конец сценария» убран из редактора.
|
|
31
32
|
- `SEND_MESSAGE` умеет быть и сообщением, и **вопросом** (`awaitReply:true` + `saveTo`/`inputKind`/`validator`). У кнопок-ссылок (`kind:"URL"`) есть флаг `track:true` — на такой шаг ссылается условие `LINK_CLICKED`.
|
|
32
33
|
- **Медиа** (фото/видео/аудио/документ/кружок/галерея): загрузи файл инструментом `upload_file` (локальный `path` или `url` для перезаливки) → получишь публичный URL → вставь его в медиа-карточку `SEND_MESSAGE` (`url`, у `gallery` — `urls[]`) или в `SEND_PHOTO.photoUrl`. Уже загруженное — `list_files`. Бинарь в графе не хранится, только ссылки. Подробности — в schema.md.
|
|
33
|
-
- `CONDITION` умеет: теги, переменные, UTM-метки, имя/email/телефон из профиля, @username, подписку на канал (`SUBSCRIBED`), клик по ссылке шага (`LINK_CLICKED`), дату/время/день недели. Полная таблица `kind`/`op` — в schema.md.
|
|
34
|
+
- `CONDITION` умеет: теги, переменные, UTM-метки, имя/email/телефон из профиля, @username, подписку на канал (`SUBSCRIBED`), клик по ссылке шага (`LINK_CLICKED`), канал Telegram Business (`BUSINESS`), дату/время/день недели. Полная таблица `kind`/`op` — в schema.md.
|
|
34
35
|
- Пакет `ACTIONS` — до 30 действий (метки, профиль, HTTP, уведомления, интеграции GetCourse/amoCRM/Битрикс24/Google Sheets/Я.Метрика, приём оплат, модерация группы). Список — в schema.md. **Интеграции РАБОТАЮТ** (это не заглушки), но каждой нужен `connectionId` — возьми его через **`list_integrations`**; без него действие упадёт «не выбрано подключение». Для Google Таблиц вместо `connectionId` нужен `googleEmail` (аккаунт подключается в вебе).
|
|
35
36
|
- `AI_REPLY` («AI-агент») — два режима, поле `mode` (`"simple"`/не задан, либо `"agent"`). Платный узел в обоих режимах (`PREMIUM_NODE_FORBIDDEN` на бесплатном тарифе) и недоступен в Instagram-графах (нет в IG-allowlist). Тратит месячный AI-бюджет тарифа — он в токенах, не в рублях.
|
|
36
37
|
- **`mode` не задан или `"simple"`** — ответ модели по `systemPrompt`/`userPromptTemplate` (обязателен, иначе выход `error`). Исчерпание бюджета — не ошибка: узел шлёт `quotaFallbackText` (если задан) и идёт дальше по `next`, как обычно.
|
|
@@ -60,15 +60,17 @@
|
|
|
60
60
|
`callback_query`, `channel_post`, `edited_channel_post`, `chat_member`, `chat_join_request`,
|
|
61
61
|
`my_chat_member`, `message_reaction`, `message_reaction_count`, `chat_boost`,
|
|
62
62
|
`removed_chat_boost`, `poll`, `poll_answer`, `inline_query`, `chosen_inline_result`,
|
|
63
|
-
`shipping_query`, `pre_checkout_query
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
`shipping_query`, `pre_checkout_query`, а также Telegram Business: `business_message`,
|
|
64
|
+
`edited_business_message`, `deleted_business_messages` (см. «Telegram Business» ниже).
|
|
65
|
+
**НЕ работают** (Telegram их шлёт, но рантайм не разбирает): `business_connection` (подключения
|
|
66
|
+
бэкенд ведёт сам, в сценарий они не попадают), `chat_shared`, `users_shared`,
|
|
67
|
+
`write_access_allowed`, `purchased_paid_media` — такой триггер не сработает.
|
|
66
68
|
`filter` (все условия по И, пустое = не задано):
|
|
67
69
|
`{"status":"member"|"left"|"kicked"|…}` — только там, где есть `member.status`
|
|
68
70
|
(`chat_member`, `my_chat_member`); `{"chatId":"-1001234567890"}` — конкретный чат/канал
|
|
69
71
|
(сравнение строкой, id каналов не влезают в int), доступен у 12 событий с `chat`;
|
|
70
72
|
`{"text":"купить","textMode":"contains"|"equals"}` — только `message`/`edited_message`/
|
|
71
|
-
`channel_post`/`edited_channel_post`. У `poll`/`poll_answer`/`inline_query`/
|
|
73
|
+
`channel_post`/`edited_channel_post`/`business_message`/`edited_business_message`. У `poll`/`poll_answer`/`inline_query`/
|
|
72
74
|
`chosen_inline_result`/`shipping_query`/`pre_checkout_query` чата нет → фильтров тоже.
|
|
73
75
|
Бот должен быть админом канала/группы, иначе события оттуда не придут.
|
|
74
76
|
- `TRIGGER_ANY_UPDATE` — `{}`, ловит ЛЮБОЙ апдейт. Приоритет самый низкий: срабатывает, только
|
|
@@ -447,6 +449,7 @@ IG-боты не поддерживают команды (`/start`). Вход
|
|
|
447
449
|
| `USERNAME` | `EQUALS`, `CONTAINS` | `value` | @username пользователя Telegram |
|
|
448
450
|
| `SUBSCRIBED` | `SUBSCRIBED`, `NOT_SUBSCRIBED` | `key` — **числовой** id канала/группы (напр. `-1001234567890`); узнать числовой id подключённых каналов: `list_channels(botId)` | подписан ли пользователь на канал бота |
|
|
449
451
|
| `LINK_CLICKED` | `CLICKED`, `NOT_CLICKED` | `key` — **`id` узла-шага** с отслеживаемой URL-кнопкой (тот же UUID, что у `SEND_MESSAGE`) | кликал ли пользователь по ссылке этого шага |
|
|
452
|
+
| `BUSINESS` | `IS`, `IS_NOT` | ничего не нужно | сообщение пришло через Telegram Business (сессия клиента бизнес-аккаунта) или нет |
|
|
450
453
|
| `CURRENT_DATE` | `BEFORE`, `AFTER`, `EQUALS` | `value` — дата `YYYY-MM-DD` | сегодняшнюю дату (МСК) |
|
|
451
454
|
| `CURRENT_TIME` | `BETWEEN` | `value`, `value2` — время `HH:mm` | текущее время в интервале (через полночь — если `value`>`value2`) |
|
|
452
455
|
| `DAY_OF_WEEK` | `IN` | `days` — массив из `MON`,`TUE`,`WED`,`THU`,`FRI`,`SAT`,`SUN` | день недели (МСК) |
|
|
@@ -455,6 +458,8 @@ IG-боты не поддерживают команды (`/start`). Вход
|
|
|
455
458
|
|
|
456
459
|
**`SUBSCRIBED`** работает только если бот **админ** в канале/группе и канал «привязан» (бот узнаёт о членстве через хук `my_chat_member` — добавь бота в канал админом). `key` должен парситься в число, иначе условие = `false`. Профильные поля (`NAME`/`EMAIL`/`PHONE`) и UTM заполняются по ходу воронки (`ASK_QUESTION`→`saveTo`, диплинк-клик с UTM).
|
|
457
460
|
|
|
461
|
+
**Telegram Business** (только Telegram-боты). Владелец подключает бота сам: Настройки Telegram → Telegram для бизнеса → Чат-боты (нужен Premium) и даёт право «Отвечать на сообщения» (`can_reply`). Статус подключения виден в кабинете: Настройки бота → «Telegram Business». После этого `business_message` клиента запускает сценарий как обычное сообщение: работают `TRIGGER_COMMAND`/`TRIGGER_TEXT`/ИИ-агент/вопросы/задержки, а все ответы уходят от имени аккаунта владельца (`business_connection_id`). Клиент Business живёт в **отдельной сессии** (не склеивается с его личным чатом с ботом): переменные и метки двух диалогов не общие. Бот игнорирует сообщения самого владельца и своих ботов, а при отключённом подключении или без `can_reply` сценарий не запускается. Отличить такие чаты: условие `BUSINESS` или триггер `TRIGGER_TG_EVENT` с `event:"business_message"` (срабатывает только для бизнес-сообщений). Не поддерживается в бизнес-чатах: рассылки и ответы оператора из «Диалогов», удаление сообщений, оплата Stars; отвечать можно только в чатах, где клиент писал за последние 24 часа (ограничение Telegram).
|
|
462
|
+
|
|
458
463
|
**`LINK_CLICKED`** проверяет факт клика по URL-кнопке конкретного шага. Чтобы условие работало: у нужного `SEND_MESSAGE` хотя бы одна кнопка `kind:"URL"` с `"track": true`, а в условии `key` = `id` этого узла-шага. Клик фиксируется через публичный редирект бота, поэтому условие имеет смысл ставить **после** `DELAY`/`ASK_QUESTION` (дай пользователю время кликнуть).
|
|
459
464
|
|
|
460
465
|
> **Платформа MAX.** Боты конструктора умеют работать и в мессенджере MAX. Там не поддерживаются `SUBSCRIBED`/`NOT_SUBSCRIBED` (нет членства в каналах) и reply-клавиатуры; публикация такого графа на MAX-бот вернёт **мягкие предупреждения** (не блокирует). Для Telegram-ботов всё работает как описано.
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
- `TRIGGER_TEXT`: `matchMode` ∈ {ANY,EQUALS,CONTAINS,REGEX}; для не-ANY нужен `value`.
|
|
46
46
|
- `ASK_QUESTION`: `promptText` обязателен (`ASK_NO_PROMPT`); `saveTo` ∈ `[a-z_][a-z0-9_]{0,63}` (`ASK_BAD_SAVE_TO`); при `validator:"REGEX"` — валидный `regex` (`ASK_BAD_REGEX`).
|
|
47
47
|
- `BRANCH`: ≥1 case (`BRANCH_NO_CASES`); вне `abTest` — непустое валидное `expression` (`BRANCH_EMPTY_EXPR`/`BRANCH_BAD_EXPR`) и подключённое ребро `case_<id>` (`BRANCH_CASE_UNCONNECTED`; `disabled:true` — если ветка намеренно пустая). В `abTest:true` рёбра нужны только у первых двух case.
|
|
48
|
-
- `CONDITION`: `match` ∈ {ALL, ANY} (`CONDITION_BAD_MATCH`); ≥1 элемент в `conditions` (`CONDITION_EMPTY`). Для `kind:"TAG"` — `value` ∈ `[a-z0-9_-]{1,64}` (`CONDITION_BAD_TAG`); для `kind:"VARIABLE"` — `key` ∈ `[a-z_][a-z0-9_]{0,63}` (`CONDITION_BAD_KEY`). Остальные `kind` (UTM/NAME/EMAIL/PHONE/USERNAME/SUBSCRIBED/LINK_CLICKED/CURRENT_*/DAY_OF_WEEK) бэкенд проверяет в рантайме — некорректный `kind`/`op`, нечисловой `key` у `SUBSCRIBED` или `key` у `LINK_CLICKED` без отслеживаемой кнопки молча дают `false` (выход `no`), публикацию не блокируют. Список `kind`/`op` — в schema.md.
|
|
48
|
+
- `CONDITION`: `match` ∈ {ALL, ANY} (`CONDITION_BAD_MATCH`); ≥1 элемент в `conditions` (`CONDITION_EMPTY`). Для `kind:"TAG"` — `value` ∈ `[a-z0-9_-]{1,64}` (`CONDITION_BAD_TAG`); для `kind:"VARIABLE"` — `key` ∈ `[a-z_][a-z0-9_]{0,63}` (`CONDITION_BAD_KEY`). Остальные `kind` (UTM/NAME/EMAIL/PHONE/USERNAME/SUBSCRIBED/LINK_CLICKED/BUSINESS/CURRENT_*/DAY_OF_WEEK) бэкенд проверяет в рантайме — некорректный `kind`/`op`, нечисловой `key` у `SUBSCRIBED` или `key` у `LINK_CLICKED` без отслеживаемой кнопки молча дают `false` (выход `no`), публикацию не блокируют. Список `kind`/`op` — в schema.md.
|
|
49
49
|
- `CALL_WEBHOOK`: `url` обязателен (`WEBHOOK_NO_URL`), http(s):// (`WEBHOOK_BAD_SCHEME`).
|
|
50
50
|
- `DELAY`: `kind` ∈ {FIXED,UNTIL,TOMORROW} (`DELAY_BAD_KIND`). `FIXED` — `durationSec>0` (секунды) ИЛИ `duration>0` (+`unit` ∈ {MINUTES,HOURS,DAYS}), иначе `DELAY_BAD_DURATION`. `TOMORROW` — `time` = `HH:mm` (`DELAY_BAD_TIME`). `UNTIL` — `isoTimestamp`: ISO-8601 или шаблон `{{var.x}}` (`DELAY_BAD_TIMESTAMP`; рантайм читает только его, не `isoDate`+`time`); опц. `offset` вида `-24h`/`-30m`/`+1d` (`DELAY_BAD_OFFSET`) и `timezone` (`DELAY_BAD_TIMEZONE`).
|
|
51
51
|
- `SET_VARIABLE`: `key` ∈ `[a-z_][a-z0-9_]{0,63}` (`VAR_BAD_KEY`). `ADD_TAG`/`REMOVE_TAG`: `tag` ∈ `[a-z0-9_-]{1,64}` (`TAG_BAD_NAME`).
|
|
@@ -59,6 +59,7 @@ const COND_OPS = {
|
|
|
59
59
|
USERNAME: ["EQUALS", "CONTAINS"],
|
|
60
60
|
SUBSCRIBED: ["SUBSCRIBED", "NOT_SUBSCRIBED"],
|
|
61
61
|
LINK_CLICKED: ["CLICKED", "NOT_CLICKED"],
|
|
62
|
+
BUSINESS: ["IS", "IS_NOT"],
|
|
62
63
|
CURRENT_DATE: ["BEFORE", "AFTER", "EQUALS"],
|
|
63
64
|
CURRENT_TIME: ["BETWEEN"],
|
|
64
65
|
DAY_OF_WEEK: ["IN"],
|
|
@@ -223,16 +224,16 @@ for (const n of nodes) {
|
|
|
223
224
|
"edited_channel_post", "chat_member", "chat_join_request", "my_chat_member",
|
|
224
225
|
"message_reaction", "message_reaction_count", "chat_boost", "removed_chat_boost",
|
|
225
226
|
"poll", "poll_answer", "inline_query", "chosen_inline_result", "shipping_query",
|
|
226
|
-
"pre_checkout_query"]);
|
|
227
|
-
const DEAD = new Set(["business_connection", "
|
|
228
|
-
"deleted_business_messages", "chat_shared", "users_shared", "write_access_allowed",
|
|
227
|
+
"pre_checkout_query", "business_message", "edited_business_message", "deleted_business_messages"]);
|
|
228
|
+
const DEAD = new Set(["business_connection", "chat_shared", "users_shared", "write_access_allowed",
|
|
229
229
|
"purchased_paid_media"]);
|
|
230
230
|
const ev = String(c.event || "");
|
|
231
231
|
if (blank(ev)) errors.push(`TG_EVENT_NO_EVENT: ${who} — нужен event (напр. chat_member, message_reaction).`);
|
|
232
232
|
else if (DEAD.has(ev)) errors.push(`TG_EVENT_UNSUPPORTED: ${who} — событие «${ev}» рантайм не разбирает, триггер не сработает.`);
|
|
233
233
|
else if (!LIVE.has(ev)) warns.push(`${who}: событие «${ev}» не из списка поддержанных — проверь, что рантайм его размечает.`);
|
|
234
234
|
// Фильтр по тексту есть только у событий с текстом сообщения.
|
|
235
|
-
const TEXTY = new Set(["message", "edited_message", "channel_post", "edited_channel_post"
|
|
235
|
+
const TEXTY = new Set(["message", "edited_message", "channel_post", "edited_channel_post",
|
|
236
|
+
"business_message", "edited_business_message"]);
|
|
236
237
|
const f = c.filter && typeof c.filter === "object" ? c.filter : {};
|
|
237
238
|
if (!blank(f.text) && !TEXTY.has(ev)) warns.push(`${who}: фильтр по тексту у события «${ev}» игнорируется — текста в апдейте нет.`);
|
|
238
239
|
if (!blank(f.status) && ev !== "chat_member" && ev !== "my_chat_member") warns.push(`${who}: фильтр по статусу работает только у chat_member/my_chat_member.`);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Проверка tools[] агента до отправки в agent_update: зеркало AiAgentService.tools + AgentWriteToolConfig
|
|
2
|
+
// на бэкенде (не строже его — иначе MCP отвергнет то, что сервер принял бы). Возвращает список ошибок.
|
|
3
|
+
|
|
4
|
+
const READ = ["AMO_LOOKUP", "SHEETS_LOOKUP", "HTTP_API"];
|
|
5
|
+
const WRITE = ["AMO_CREATE_LEAD", "AMO_UPDATE_LEAD", "AMO_UPDATE_CONTACT", "SHEETS_APPEND", "HTTP_POST", "SET_FIELD", "ADD_TAG", "HANDOFF"];
|
|
6
|
+
const ARG_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,39}$/;
|
|
7
|
+
const PROP_KEYS = new Set(["type", "description", "enum", "pattern", "maxLength", "minLength", "minimum", "maximum"]);
|
|
8
|
+
const SCHEMA_KEYS = new Set(["type", "properties", "required", "description"]);
|
|
9
|
+
const FORBIDDEN_HEADERS = new Set(["host", "content-length", "transfer-encoding", "connection", "content-type", "expect", "upgrade", "te", "trailer"]);
|
|
10
|
+
const MAX_TOOLS = 10;
|
|
11
|
+
|
|
12
|
+
export const AGENT_WRITE_TOOL_TYPES = WRITE;
|
|
13
|
+
|
|
14
|
+
function schemaErrors(schema, errs, id) {
|
|
15
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return errs.push(`${id}: argsSchema — объект {properties, required}`), new Set();
|
|
16
|
+
if (schema.type != null && schema.type !== "object") errs.push(`${id}: argsSchema.type должен быть object`);
|
|
17
|
+
for (const k of Object.keys(schema)) if (!SCHEMA_KEYS.has(k)) errs.push(`${id}: ключевое слово «${k}» в argsSchema не поддерживается`);
|
|
18
|
+
const props = schema.properties;
|
|
19
|
+
if (!props || typeof props !== "object" || Array.isArray(props) || Object.keys(props).length === 0) {
|
|
20
|
+
errs.push(`${id}: опишите хотя бы один аргумент в argsSchema.properties`);
|
|
21
|
+
return new Set();
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(props).length > 10) errs.push(`${id}: не больше 10 аргументов`);
|
|
24
|
+
for (const [name, p] of Object.entries(props)) {
|
|
25
|
+
if (!ARG_NAME.test(name)) errs.push(`${id}: имя аргумента «${name}» — латиница, цифры, подчёркивание`);
|
|
26
|
+
if (!p || typeof p !== "object") { errs.push(`${id}: описание аргумента ${name} — объект`); continue; }
|
|
27
|
+
for (const k of Object.keys(p)) if (!PROP_KEYS.has(k)) errs.push(`${id}: ключевое слово «${k}» у аргумента ${name} не поддерживается`);
|
|
28
|
+
if (!["string", "number", "integer", "boolean"].includes(p.type)) errs.push(`${id}: type аргумента ${name} — string, number, integer или boolean`);
|
|
29
|
+
if (p.enum != null && (!Array.isArray(p.enum) || p.enum.length === 0 || p.enum.length > 30)) errs.push(`${id}: enum аргумента ${name} — от 1 до 30 значений`);
|
|
30
|
+
if (p.pattern != null) {
|
|
31
|
+
try { new RegExp(p.pattern); } catch { errs.push(`${id}: pattern аргумента ${name} не разбирается`); }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const r of schema.required || []) if (!(r in props)) errs.push(`${id}: required содержит неизвестный аргумент ${r}`);
|
|
35
|
+
return new Set(Object.keys(props));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function placeholders(tpl, known, errs, id) {
|
|
39
|
+
for (const m of String(tpl ?? "").matchAll(/\{([A-Za-z_][A-Za-z0-9_]{0,39})\}/g)) {
|
|
40
|
+
if (!known.has(m[1]) && m[1] !== "now") errs.push(`${id}: в шаблоне {${m[1]}} нет в argsSchema.properties`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function validateAgentTools(tools) {
|
|
45
|
+
const errs = [];
|
|
46
|
+
if (!Array.isArray(tools)) return ["tools должен быть массивом"];
|
|
47
|
+
if (tools.length > MAX_TOOLS) errs.push(`не больше ${MAX_TOOLS} инструментов`);
|
|
48
|
+
const ids = new Set();
|
|
49
|
+
for (const t of tools) {
|
|
50
|
+
const id = t?.id || "?";
|
|
51
|
+
if (!t || typeof t !== "object") { errs.push("инструмент — объект"); continue; }
|
|
52
|
+
if (!/^[a-z0-9_]{1,40}$/.test(t.id || "")) errs.push(`${id}: id — a-z, 0-9, _ (до 40)`);
|
|
53
|
+
if (ids.has(t.id)) errs.push(`${id}: id повторяется`);
|
|
54
|
+
ids.add(t.id);
|
|
55
|
+
if (!t.title) errs.push(`${id}: нужен title`);
|
|
56
|
+
if (![...READ, ...WRITE].includes(t.type)) { errs.push(`${id}: неизвестный type ${t.type}`); continue; }
|
|
57
|
+
if (!WRITE.includes(t.type)) continue;
|
|
58
|
+
for (const [k, max] of [["limitPerConversation", 20], ["limitPerDay", 1000]]) {
|
|
59
|
+
if (t[k] != null && !(Number.isInteger(t[k]) && t[k] >= 1 && t[k] <= max)) errs.push(`${id}: ${k} — число от 1 до ${max}`);
|
|
60
|
+
}
|
|
61
|
+
let known = new Set();
|
|
62
|
+
if (!["SET_FIELD", "ADD_TAG", "HANDOFF"].includes(t.type)) known = schemaErrors(t.argsSchema, errs, id);
|
|
63
|
+
if (t.type.startsWith("AMO_") && !t.amoConnectionId) errs.push(`${id}: нужен amoConnectionId (подключение amoCRM)`);
|
|
64
|
+
if (t.type === "AMO_CREATE_LEAD" && !t.amoLeadName) errs.push(`${id}: нужен amoLeadName`);
|
|
65
|
+
if (t.type === "AMO_UPDATE_LEAD" && !t.amoLeadId) errs.push(`${id}: нужен amoLeadId`);
|
|
66
|
+
if (t.type === "AMO_UPDATE_CONTACT" && !t.amoContactId) errs.push(`${id}: нужен amoContactId`);
|
|
67
|
+
if (t.type === "SHEETS_APPEND") {
|
|
68
|
+
if (!t.sheetsEmail || !t.sheetsSpreadsheetId) errs.push(`${id}: нужны sheetsEmail и sheetsSpreadsheetId`);
|
|
69
|
+
if (!Array.isArray(t.sheetsRowTemplate) || t.sheetsRowTemplate.length === 0) errs.push(`${id}: нужен sheetsRowTemplate (ячейки)`);
|
|
70
|
+
}
|
|
71
|
+
if (t.type === "HTTP_POST") {
|
|
72
|
+
if (!/^https?:\/\//i.test(t.httpUrlTemplate || "")) errs.push(`${id}: httpUrlTemplate — http:// или https://`);
|
|
73
|
+
if (t.httpBodyTemplate) {
|
|
74
|
+
try { JSON.parse(t.httpBodyTemplate); } catch { errs.push(`${id}: httpBodyTemplate — невалидный JSON`); }
|
|
75
|
+
}
|
|
76
|
+
for (const h of t.httpHeaders || []) {
|
|
77
|
+
if (!/^[A-Za-z][A-Za-z0-9-]{0,39}$/.test(h?.name || "") || FORBIDDEN_HEADERS.has(String(h.name).toLowerCase())) errs.push(`${id}: недопустимый заголовок ${h?.name}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (t.type === "SET_FIELD" && !(t.allowedFields || []).length) errs.push(`${id}: нужен allowedFields`);
|
|
81
|
+
if (t.type === "ADD_TAG" && !(t.allowedTags || []).length) errs.push(`${id}: нужен allowedTags`);
|
|
82
|
+
for (const k of ["amoLeadId", "amoContactId", "amoLeadName", "amoPrice", "amoContactName", "amoPhone", "amoEmail", "httpUrlTemplate", "httpBodyTemplate"]) {
|
|
83
|
+
if (t[k]) placeholders(t[k], known, errs, id);
|
|
84
|
+
}
|
|
85
|
+
for (const c of t.sheetsRowTemplate || []) placeholders(c, known, errs, id);
|
|
86
|
+
}
|
|
87
|
+
return errs;
|
|
88
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { createInterface } from "node:readline";
|
|
24
|
+
import { validateAgentTools } from "./agent-tools.mjs";
|
|
24
25
|
import { fileURLToPath } from "node:url";
|
|
25
26
|
import { spawn } from "node:child_process";
|
|
26
27
|
import os from "node:os";
|
|
@@ -1098,7 +1099,7 @@ const TOOLS = [
|
|
|
1098
1099
|
{ name: "agent_list", description: "Список ИИ-агентов пользователя (GET /api/bots/agents): карточки — id, имя, статус. Read-only.", inputSchema: { type: "object", properties: {} } },
|
|
1099
1100
|
{ name: "agent_get", description: "ИИ-агент по id (GET /api/bots/agents/{agentId}): настройки (имя, язык, тон, длина/формат ответа, инструкции, запретные/передаточные темы, kbId базы знаний, статус). Чужой агент — 404. Read-only.", inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] } },
|
|
1100
1101
|
{ name: "agent_create", description: "Создать ИИ-агента (POST /api/bots/agents): name, description — необязательны. Вместе с агентом создаётся его база знаний (kbId в ответе) — дальше kb_add_qa/kb_add_text/kb_add_site.", inputSchema: { type: "object", properties: { name: { type: "string" }, description: { type: "string" } } } },
|
|
1101
|
-
{ name: "agent_update", description: "Изменить настройки агента (PATCH /api/bots/agents/{agentId}): patch — объект с полями для правки (name, description, language: RU|EN|AUTO, tone: FRIENDLY|NEUTRAL|FORMAL, answerLength: SHORT|MEDIUM|LONG, format: PLAIN|LIST_FRIENDLY, instructions, forbiddenTopics[], handoffTopics[], fallback). Применяется частично — передавай только то, что меняешь.", inputSchema: { type: "object", properties: { agentId: { type: "string" }, patch: { type: "object", description: "Поля агента для частичного обновления" } }, required: ["agentId", "patch"] } },
|
|
1102
|
+
{ name: "agent_update", description: "Изменить настройки агента (PATCH /api/bots/agents/{agentId}): patch — объект с полями для правки (name, description, language: RU|EN|AUTO, tone: FRIENDLY|NEUTRAL|FORMAL, answerLength: SHORT|MEDIUM|LONG, format: PLAIN|LIST_FRIENDLY, instructions, forbiddenTopics[], handoffTopics[], fallback, tools[]). Применяется частично — передавай только то, что меняешь. tools[] заменяет список целиком (до 10). Инструменты ЧТЕНИЯ: AMO_LOOKUP {amoConnectionId}, SHEETS_LOOKUP {sheetsEmail,sheetsSpreadsheetId,sheetsLookupColumn,…}, HTTP_API {httpUrlTemplate,httpParams}. Инструменты ЗАПИСИ (включаются явно по одному, id a-z0-9_): AMO_CREATE_LEAD {amoConnectionId, amoLeadName, amoContactName?, amoPhone?, amoEmail?, amoPrice?, amoPipelineId?, amoStatusId?}, AMO_UPDATE_LEAD {amoLeadId + что обновлять}, AMO_UPDATE_CONTACT {amoContactId, amoContactName?|amoPhone?|amoEmail?}, SHEETS_APPEND {sheetsEmail,sheetsSpreadsheetId,sheetsSheetName?, sheetsRowTemplate:[ячейки]}, HTTP_POST {httpUrlTemplate, httpBodyTemplate? (JSON-шаблон), httpHeaders?:[{name,value}], httpJsonPath?}, SET_FIELD {allowedFields[]}, ADD_TAG {allowedTags[]}, HANDOFF. Для записи (кроме SET_FIELD/ADD_TAG/HANDOFF) обязательна argsSchema — плоская JSON-схема {properties:{имя:{type:string|number|integer|boolean, description?, enum?, pattern?, maxLength?, minLength?, minimum?, maximum?}}, required?:[]}; в шаблонах {имя} подставляет аргумент, {now} — время. Общие поля записи: confirmBeforeRun (по умолчанию true для внешних записей — агент сначала спрашивает согласие пользователя), limitPerConversation (по умолч. 3, макс 20), limitPerDay (по умолч. 100, макс 1000). В публичном веб-чате инструменты записи не работают. Перед отправкой tools[] проверяются локально, ошибки схем возвращаются списком.", inputSchema: { type: "object", properties: { agentId: { type: "string" }, patch: { type: "object", description: "Поля агента для частичного обновления" } }, required: ["agentId", "patch"] } },
|
|
1102
1103
|
{ name: "agent_publish", description: "Опубликовать агента (POST /api/bots/agents/{agentId}/publish): агент начинает отвечать в подключённых сценариях. HTTP 409 CHECKLIST_FAILED с чеклистом, если агент ещё не готов (нет базы знаний, пустые инструкции и т.п.) — агент не меняется.", inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] } },
|
|
1103
1104
|
{ name: "agent_health", description: "Здоровье базы знаний агента (GET /api/bots/agents/{agentId}/health): счётчики документов/фрагментов по запросу, без кэша. Read-only.", inputSchema: { type: "object", properties: { agentId: { type: "string" } }, required: ["agentId"] } },
|
|
1104
1105
|
{ name: "agent_test_chat", description: "Проверить ответ агента в песочнице без отправки клиенту (POST /api/bots/agents/{agentId}/test-chat): question (1–1000 символов), history — необязательная история диалога [{role: client|agent, text}], до 12 реплик. ТРАТИТ бюджет ИИ, как настоящий ответ — не вызывай массово.", inputSchema: { type: "object", properties: { agentId: { type: "string" }, question: { type: "string" }, history: { type: "array", items: { type: "object", properties: { role: { type: "string", enum: ["client", "agent"] }, text: { type: "string" } } } } }, required: ["agentId", "question"] } },
|
|
@@ -1236,7 +1237,8 @@ const TOOLS = [
|
|
|
1236
1237
|
{ name: "bot_subscriber_analytics", description: "Новые подписчики/активность бота по дням (GET /api/bots/{botId}/analytics/subscribers?from&to): {newSubscribers, active, total, legacyExcluded, trend:[{day,newSubscribers,active}]}. from/to обязательны — ГГГГ-ММ-ДД, диапазон обрезается сервером до 92 дней. legacyExcluded — сессии без даты создания (заведены до этой фичи): не входят в newSubscribers/trend, но учтены в total. active — по updatedAt в диапазоне (одна сессия может посчитаться в несколько дней). Read-only.", inputSchema: { type: "object", properties: { botId: { type: "string" }, from: { type: "string", description: "ГГГГ-ММ-ДД" }, to: { type: "string", description: "ГГГГ-ММ-ДД" } }, required: ["botId", "from", "to"] } },
|
|
1237
1238
|
{ name: "scenario_timeseries", description: "Дневной ряд прогонов сценария (GET /api/bots/graphs/{graphId}/analytics/timeseries?from&to): [{day, ok, failed, other}] — по одной строке на каждый день диапазона (пустые дни — нулями), other = PARKED/NO_MATCH/PARTIAL (не отброшены). from/to обязательны — ГГГГ-ММ-ДД, диапазон обрезается до 92 дней. Разбивку по узлам сценария (не по дням) даёт graph_analytics — это разные срезы одних данных. Read-only.", inputSchema: { type: "object", properties: { graphId: { type: "string" }, from: { type: "string", description: "ГГГГ-ММ-ДД" }, to: { type: "string", description: "ГГГГ-ММ-ДД" } }, required: ["graphId", "from", "to"] } },
|
|
1238
1239
|
// ---- ИИ-агенты: инструменты (вызов внешних систем моделью) ----
|
|
1239
|
-
{ name: "agent_tool_test", description: "Проверить инструмент агента напрямую, БЕЗ модели (POST /api/bots/agents/{agentId}/tools/{toolId}/test): params — аргументы вызова (AMO_LOOKUP: query ИЛИ leadId; SHEETS_LOOKUP: value; HTTP_API: имена из httpParams этого инструмента). Проверяет только конфиг инструмента (подключение amoCRM/таблицу Sheets/URL) — модель не вызывается, бюджет ИИ не тратится. {ok:true, result} при успехе; ошибка конфига/внешнего сервиса — HTTP 400 {ok:false, error} с причиной на русском (никогда не 500). toolId — id инструмента из agent_get → tools[].id (настраивается через agent_update {patch:{tools:[...]}})." , inputSchema: { type: "object", properties: { agentId: { type: "string" }, toolId: { type: "string" }, params: { type: "object", description: "Аргументы вызова инструмента (см. описание)" } }, required: ["agentId", "toolId"] } },
|
|
1240
|
+
{ name: "agent_tool_test", description: "Проверить инструмент агента напрямую, БЕЗ модели (POST /api/bots/agents/{agentId}/tools/{toolId}/test): params — аргументы вызова (AMO_LOOKUP: query ИЛИ leadId; SHEETS_LOOKUP: value; HTTP_API: имена из httpParams этого инструмента). Проверяет только конфиг инструмента (подключение amoCRM/таблицу Sheets/URL) — модель не вызывается, бюджет ИИ не тратится. {ok:true, result} при успехе; ошибка конфига/внешнего сервиса — HTTP 400 {ok:false, error} с причиной на русском (никогда не 500). toolId — id инструмента из agent_get → tools[].id (настраивается через agent_update {patch:{tools:[...]}}). ИНСТРУМЕНТЫ ЗАПИСИ (AMO_CREATE_LEAD/…/HTTP_POST/SET_FIELD/ADD_TAG/HANDOFF): params — аргументы по argsSchema инструмента (для SET_FIELD: field,value; ADD_TAG: tag; HANDOFF: reason); по умолчанию это ПЕСОЧНИЦА (dryRun:true) — аргументы проверяются по схеме, возвращается что было бы отправлено, во внешние системы ничего не уходит; live:true отправляет по-настоящему (только внешние записи: amoCRM, Google Таблица, POST) — используй осознанно, это реальная запись." , inputSchema: { type: "object", properties: { agentId: { type: "string" }, toolId: { type: "string" }, params: { type: "object", description: "Аргументы вызова инструмента (см. описание)" }, live: { type: "boolean", description: "Только для инструментов записи: true — выполнить по-настоящему, иначе песочница (dry-run)" } }, required: ["agentId", "toolId"] } },
|
|
1241
|
+
{ name: "agent_tool_calls", description: "Журнал вызовов инструментов ЗАПИСИ агента (GET /api/bots/agents/{agentId}/tool-calls): когда, какой инструмент, кто инициатор (AGENT/OWNER_TEST), статус (OK, FAILED, PENDING_CONFIRM, REJECTED по лимиту, DRY_RUN песочницы), аргументы без секретов, результат/ошибка. Фильтр toolId, limit до 200 (по умолч. 50). Хранится 90 дней. Read-only.", inputSchema: { type: "object", properties: { agentId: { type: "string" }, toolId: { type: "string" }, limit: { type: "number" } }, required: ["agentId"] } },
|
|
1240
1242
|
// ---- Заявки сайта: файлы вложений ----
|
|
1241
1243
|
{ name: "site_lead_file_download", description: "Метаданные и ссылка на скачивание файла из заявки сайта (GET /api/bots/pages/{siteId}/leads/{leadId}/files/{fileId}, владелец/ADMIN сайта). Байты файла НЕ передаются через MCP (может быть большим/бинарным) — инструмент делает HEAD-запрос, чтобы подтвердить, что файл принадлежит этой заявке (404 иначе), и вернуть {fileId, fileName, contentType, sizeBytes, downloadUrl}. downloadUrl отдаёт файл только с тем же токеном, что у этого MCP (заголовок Authorization: Bearer zmcp_...), — скачай curl'ом с этим заголовком или открой заявку в кабинете (сайт → «Заявки»). Список файлов заявки с теми же полями уже есть в site_leads (поле files[] каждой заявки) — этот инструмент нужен, только когда нужна именно прямая ссылка на скачивание одного файла.", inputSchema: { type: "object", properties: { siteId: { type: "string" }, leadId: { type: "string" }, fileId: { type: "string" } }, required: ["siteId", "leadId", "fileId"] } },
|
|
1242
1244
|
// ---- Вебхук-сценарии (/api/scenarios, PAT-доступ открыт решением владельца 14.10) ----
|
|
@@ -1552,6 +1554,10 @@ async function handleCall(params) {
|
|
|
1552
1554
|
case "agent_create": return okResult(await api("/api/bots/agents", { method: "POST", body: { name: a.name, description: a.description } }));
|
|
1553
1555
|
case "agent_update": {
|
|
1554
1556
|
if (!a.patch || typeof a.patch !== "object") throw new Error("Передай patch — объект с полями агента для правки.");
|
|
1557
|
+
if (a.patch.tools !== undefined) {
|
|
1558
|
+
const errs = validateAgentTools(a.patch.tools);
|
|
1559
|
+
if (errs.length) throw new Error("Инструменты агента не прошли проверку:\n- " + errs.join("\n- "));
|
|
1560
|
+
}
|
|
1555
1561
|
return okResult(await api(`/api/bots/agents/${a.agentId}`, { method: "PATCH", body: a.patch }));
|
|
1556
1562
|
}
|
|
1557
1563
|
case "agent_publish": return okResult(await api(`/api/bots/agents/${a.agentId}/publish`, { method: "POST" }));
|
|
@@ -2445,7 +2451,16 @@ async function handleCall(params) {
|
|
|
2445
2451
|
case "agent_tool_test": {
|
|
2446
2452
|
if (!a.agentId) throw new Error("Передай agentId.");
|
|
2447
2453
|
const toolId = idArg(a.toolId, "toolId");
|
|
2448
|
-
|
|
2454
|
+
const body = { params: a.params || {} };
|
|
2455
|
+
if (a.live === true) body.live = true;
|
|
2456
|
+
return okResult(await api(`/api/bots/agents/${a.agentId}/tools/${toolId}/test`, { method: "POST", body }));
|
|
2457
|
+
}
|
|
2458
|
+
case "agent_tool_calls": {
|
|
2459
|
+
if (!a.agentId) throw new Error("Передай agentId.");
|
|
2460
|
+
const q = new URLSearchParams();
|
|
2461
|
+
if (a.toolId) q.set("toolId", idArg(a.toolId, "toolId"));
|
|
2462
|
+
if (a.limit != null) q.set("limit", String(Math.max(1, Math.min(200, Number(a.limit) || 50))));
|
|
2463
|
+
return okResult(await api(`/api/bots/agents/${a.agentId}/tool-calls${q.toString() ? "?" + q : ""}`));
|
|
2449
2464
|
}
|
|
2450
2465
|
// ---- Заявки сайта: файлы ----
|
|
2451
2466
|
case "site_lead_file_download": {
|