@retensy/mcp 0.26.0 → 0.28.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 +4 -3
- package/package.json +2 -2
- package/skills/build-bot-funnel/SKILL.md +2 -0
- package/src/index.mjs +11 -6
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
-
- 🤖 **Сценарии ботов**: `list_bots`, `list_graphs`, `list_channels`, `get_graph`, `create_graph`, `update_graph`, `edit_graph_live`, `patch_graph`, `dry_run`, `publish_graph`, `import_funnel`, `list_templates`, `create_graph_from_template`, `clone_graph`, `copy_graph`, `rename_graph`, `set_active_graph`, `delete_graph`, `upload_file`, `list_files`, `delete_file`, `graph_analytics`, `list_bot_users`, `list_links` (+ `setup`/`set_token`).
|
|
51
|
+
- 🤖 **Сценарии ботов**: `list_bots`, `list_graphs`, `list_channels`, `get_graph`, `create_graph`, `update_graph`, `edit_graph_live`, `patch_graph`, `dry_run`, `publish_graph`, `unpublish_graph`, `import_funnel`, `list_templates`, `create_graph_from_template`, `clone_graph`, `copy_graph`, `rename_graph`, `set_active_graph`, `delete_graph`, `upload_file`, `list_files`, `delete_file`, `graph_analytics`, `list_bot_users`, `list_links` (+ `setup`/`set_token`).
|
|
52
52
|
- 🔌 **Боты и сервисы**: `create_bot` (Telegram/MAX по токену), `bot_stop`/`bot_resume`, `connect_integration`/`disconnect_integration` (amoCRM, Битрикс24, GetCourse, Я.Метрика, ЮKassa; Google Таблицы — ссылкой на вход Google; креды хранятся зашифрованными и не возвращаются), `integration_catalog`/`integration_status`/`integration_test` (каталог Integration Core и живая проверка подключения), `integration_ingress_url` (URL ингресса «Внешних событий» — Tilda, JivoSite, WooCommerce, amoCRM, Битрикс24, GetCourse, CloudPayments, Robokassa, Т-Банк, Prodamus — для `TRIGGER_WEBHOOK`), `channel_post` (пост в канал Telegram/MAX).
|
|
53
53
|
- 📣 **Рассылки**: `broadcast_preview` (размер аудитории), `broadcast_send` (сейчас или по расписанию, сразу по нескольким ботам; сообщения или запуск сценария; полный фильтр аудитории `audienceFilter`, дедуп `dedup` и распределённый запуск `stagger` по ботам), `broadcast_list`/`broadcast_get`/`broadcast_cancel`, повторяющиеся (`broadcast_recurring`), черновики (`broadcast_drafts`, `broadcast_duplicate`), мульти-бот группы (`broadcast_group` — preview/list/get/pause/resume/cancel) + скилл `send-broadcast`.
|
|
54
54
|
- 👥 **Аудитория подписчиков**: `audience_search`/`audience_fields` — кросс-бот поиск и фильтр (теги, даты, статус, условия по переменным), `audience_tag_edit`/`audience_variable_edit` — массовое редактирование тегов/переменных, `audience_export` — потоковая выгрузка выборки в CSV/XLSX, `dialog_search` — поиск по «Диалогам» (включая текст сообщений).
|
|
@@ -184,7 +184,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
184
184
|
|---|---|
|
|
185
185
|
| `setup` | статус авторизации + пошаговая инструкция подключения |
|
|
186
186
|
| `set_token` | сохранить присланный токен `zmcp_…` (без env/рестарта) |
|
|
187
|
-
| `list_bots` | список ботов |
|
|
187
|
+
| `list_bots` | список ботов (`pausedReason: TOKEN_CONFLICT` / `pausedChannels` — бот или канал на паузе из-за вебхука другого сервиса на том же токене; `conflictWarning: TOKEN_CONFLICT_POLLING` — токен опрашивает кто-то ещё, бот работает, но теряет сообщения) |
|
|
188
188
|
| `create_bot(platform, token?, name?)` | подключить бота Telegram/MAX по токену; `WEB` — чат-виджет для сайта без токена → botId, key, snippet (Instagram → ссылка на кабинет `/bots/instagram`, вход через Instagram) |
|
|
189
189
|
| `web_widget_snippet(botId)` | код вставки чат-виджета на сайт |
|
|
190
190
|
| `bot_stop(botId)` / `bot_resume(botId)` | остановить / запустить бота |
|
|
@@ -204,7 +204,8 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
204
204
|
| `edit_graph_live(graphId, graphFile\|graph\|nodes,edges)` | правка живого графа НА МЕСТЕ + авто-бэкап (рекомендуется для прода) |
|
|
205
205
|
| `patch_graph(graphId, replacements)` | строковые замены в JSON графа на сервере (для больших/живых графов) |
|
|
206
206
|
| `dry_run(graphId, kind, value)` | прогон без публикации |
|
|
207
|
-
| `publish_graph(graphId)` | публикация (вернёт `errors[]` при провале) |
|
|
207
|
+
| `publish_graph(graphId, secondary?)` | публикация (вернёт `errors[]` при провале); `secondary: true` — дополнительный сценарий рядом с основным (запускают VK-лендинги и ссылки с `targetGraphId`) |
|
|
208
|
+
| `unpublish_graph(graphId)` | снять дополнительный сценарий с публикации (основной — 409) |
|
|
208
209
|
| `import_funnel(botId, name, graphFile\|graph)` | всё за раз: create → update → dry-run → publish |
|
|
209
210
|
| `list_templates()` | готовые шаблоны воронок |
|
|
210
211
|
| `create_graph_from_template(botId, templateId, name)` | граф из шаблона (DRAFT) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retensy/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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",
|
|
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",
|
|
25
25
|
"validate": "node skills/build-bot-funnel/validate.mjs"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
@@ -57,6 +57,7 @@ description: Собрать воронку (сценарий) бота для с
|
|
|
57
57
|
|
|
58
58
|
5. **Залей и опубликуй через MCP `bot-graph`** (если он подключён и пользователь просит публикацию):
|
|
59
59
|
- `list_bots` → выбрать `botId` (или `create_graph` в существующем боте).
|
|
60
|
+
Конфликт токена (тот же Telegram-токен подключён ещё в BotHelp/Salebot/своём скрипте): `active:false` + `pausedReason:"TOKEN_CONFLICT"` — вебхук другого сервиса, бот на паузе (отвечать не будет); `pausedChannels` — выключены отдельные каналы; `conflictWarning:"TOKEN_CONFLICT_POLLING"` — бот работает, но часть сообщений забирает другая программа. Во всех случаях попроси пользователя отключить бота в том сервисе (или `/revoke` в @BotFather → `bot_change_token`), при паузе — потом `bot_resume`. Справка: `/docs#token-conflict`.
|
|
60
61
|
- `create_graph(botId, name)` → получить `graphId`. Либо стартуй с готовой основы: `list_templates` → `create_graph_from_template(botId, templateId, name)`.
|
|
61
62
|
- `update_graph(graphId, nodes, edges, canvasMeta)` → залить узлы/рёбра.
|
|
62
63
|
- `dry_run(graphId, kind:"command", value:"start")` → прогнать стартовую ветку, проверить `runStatus`. `dry_run` ничего не делает снаружи: внешние запросы, CRM, таблицы, письма, уведомления, счета и ответы ИИ пропускаются (у шага `skipped: "dry-run"`), переменные из их ответов остаются пустыми — это проверяется только живым прогоном.
|
|
@@ -67,6 +68,7 @@ description: Собрать воронку (сценарий) бота для с
|
|
|
67
68
|
5b. **Правка СУЩЕСТВУЮЩЕГО / живого сценария — по умолчанию `edit_graph_live`, а НЕ clone+publish.**
|
|
68
69
|
- Когда пользователь просит «поправь сценарий X» (особенно если он уже открыт в редакторе или опубликован) — правь **ТОТ ЖЕ `graphId`** через **`edit_graph_live(graphId, nodes, edges)`**. Он сам снимает авто-бэкап предыдущего состояния (один rolling-граф «🔙 Авто-бэкап») и делает PUT на месте — **id не меняется**. Живой сценарий бота (бот-сценария) — граф с `isActive: true` в `list_graphs` (статус `PUBLISHED`; после `publish_graph` черновика это `publishedGraphId`, а **не** id черновика) — правь его id, иначе правка ляжет в черновик и до бота не дойдёт. Вебхук-сценарий отдельного опубликованного графа не имеет — правь его по его же id.
|
|
69
70
|
- Почему так: бэкенд при PUT/публикации шлёт `external_update` в WS-комнату → открытые редакторы перечитывают граф **вживую** (юзеру не надо перезаходить). Бот читает активный граф **заново из БД на каждое сообщение** → правка живого PUBLISHED-графа применяется **сразу, без отдельной публикации**.
|
|
71
|
+
- **Дополнительный сценарий** (отдельный живой граф под VK-лендинг/рекламную ссылку, основной не трогается): `publish_graph(graphId, secondary: true)` публикует граф НА МЕСТЕ (`publishedGraphId` = тот же id, снимка нет, `active` false, `activeGraphId` бота не меняется); проверки те же (422), нужен основной сценарий у бота (`SECONDARY_NO_MAIN`), основной сам дополнительным не делается (`SECONDARY_IS_MAIN`). Его запускают только `vk_landing_create/update` с `targetGraphId` (ответ показывает `targetGraphStatus`: MAIN/SECONDARY/NOT_PUBLISHED/MISSING) и `link_create` с `targetGraphId` (+`targetNodeId` узла в нём). Подписчик стартует с его /start (или первого триггера/блока); ответы на вопросы, кнопки, задержки продолжаются в нём, команды/ключевые слова — по основному, любой старт (/start, /start <код> не на дополнительный, VK «Начать») ведёт в основной (снимая ожидание вопроса). Нельзя в дополнительном: TRIGGER_SCHEDULE, TRIGGER_SITE_FORM, TRIGGER_TG_EVENT, TRIGGER_COMMENT, TRIGGER_WEBHOOK, TRIGGER_IG_* → 422 `SECONDARY_TRIGGER_UNSUPPORTED` (и при правке вживую); в режиме decoupled → `SECONDARY_UNSUPPORTED`. TRIGGER_PAYMENT в дополнительном — только для подписчика, который сейчас в нём. Клон основного при первой публикации дополнительным получает новые id совпавших узлов (рёбра/конфиги/ссылки на шаги переносятся) — после publish перечитай граф (`get_graph`), старые nodeId недействительны: PUT/`edit_graph_live` живого дополнительного со старыми id узлов основного → HTTP 409 `GRAPH_STALE`. Ссылки «Перейти в сценарий» (graphId другого графа) и текст при перенумерации не меняются. Подписчик возвращается в основной на блоке END дополнительного или по команде/ключевому слову основного; «Оплата прошла» в дополнительном отменяет основной для этой оплаты, только если его триггер реально сработал. Цель-черновик/чужая/основной — подписчик идёт в основной, как раньше. Правки дополнительного — тем же id через `edit_graph_live` (вживую, как основной). Снять — `unpublish_graph(graphId)`; «сделать основным» — `set_active_graph`. В `list_graphs` у него `secondary: true`. Публикация основного (`publish_graph` без `secondary`) дополнительные не удаляет. Дополнительный занимает место в лимите сценариев тарифа.
|
|
70
72
|
- `clone_graph`+`publish_graph` НЕ создают новый живой граф и НЕ песочница: публикация копирует клон (как любой черновик) в id ТЕКУЩЕГО опубликованного графа бота и затирает его содержимое без бэкапа — вместе с живыми правками, сделанными после прошлой публикации; `set_active_graph` на прежний id после этого вернёт уже новое содержимое; открытые редакторы живого графа перечитают его (у кого есть несохранённые правки — получат просьбу обновить страницу). Клон годится, чтобы спокойно собрать крупный рискованный рефактор в черновике, но перед его публикацией сохрани живой граф: `get_graph(graphId, saveToFile)` — откат = залить файл обратно через `edit_graph_live`/`update_graph` с `graphFile`.
|
|
71
73
|
- ⚠️ PUT **активного** (PUBLISHED) графа сервер проверяет как `publish`: валидатор, платные блоки, лимит блоков тарифа, платформа. Ошибка → `HTTP 422` со всеми `code@nodeId`, граф **не сохранён**, бот работает на прежней версии. Черновик (`DRAFT`) PUT сохраняет без проверок (кроме размера: больше 4 МБ → 422 `GRAPH_TOO_LARGE`), и на бота он не влияет: `publish_graph` черновика копирует его в отдельный `PUBLISHED`-граф (`publishedGraphId`), а сам черновик остаётся `DRAFT`. Всё равно прогоняй `validate.mjs` + `dry_run` заранее. Откат: версии активного не хранятся — опубликовать граф «🔙 Авто-бэкап» (или скопировать его содержимое обратно).
|
|
72
74
|
|
package/src/index.mjs
CHANGED
|
@@ -1010,7 +1010,7 @@ const STAGGER_SCHEMA = { type: "object", description: "«Распределит
|
|
|
1010
1010
|
const TOOLS = [
|
|
1011
1011
|
{ name: "setup", description: "Показать статус авторизации и пошаговую инструкцию подключения. Вызывай первым, если пользователь не знает, что делать, или при ошибке доступа. Токен (zmcp_...) определяет АККАУНТ, а не один проект: доступ к каждому боту/сайту — это твоя текущая роль в нём (владелец или приглашённый соредактор), и она проверяется заново при каждом вызове.", inputSchema: { type: "object", properties: {} } },
|
|
1012
1012
|
{ name: "set_token", description: "Сохранить персональный токен (zmcp_...), который пользователь создал на /bots/mcp-tokens. Применяется сразу, без рестарта. Токен определяет аккаунт целиком, не один проект — список доступных проектов (свои + те, куда пригласили) виден в list_bots/site_list. Если владелец проекта уберёт тебя из соредакторов, доступ к этому проекту исчезнет сразу же, без повторной настройки токена.", inputSchema: { type: "object", properties: { token: { type: "string", description: "Секрет токена, начинается с zmcp_" } }, required: ["token"] } },
|
|
1013
|
-
{ name: "list_bots", description: "Список ботов (id, имя, статус). Включает и свои боты, и боты, в которые тебя пригласили соредактором — у каждого есть accessRole (OWNER/ADMIN/EDITOR/VIEWER); у чужих (не OWNER) есть ownerName — имя владельца проекта. Доступ = твоя текущая роль в проекте, проверяется на каждый вызов; владелец мог отозвать её без предупреждения.", inputSchema: { type: "object", properties: {} } },
|
|
1013
|
+
{ name: "list_bots", description: "Список ботов (id, имя, статус). Включает и свои боты, и боты, в которые тебя пригласили соредактором — у каждого есть accessRole (OWNER/ADMIN/EDITOR/VIEWER); у чужих (не OWNER) есть ownerName — имя владельца проекта. Конфликт токена (тот же Telegram-токен подключён ещё в BotHelp/Salebot/своём скрипте, 10+ минут): pausedReason=TOKEN_CONFLICT (+ pausedAt, pausedConflictHost) — другой сервис держит вебхук, бот active=false (ничего не удалено); pausedChannels[] — у работающего мультиканального бота выключены только эти каналы; conflictWarning=TOKEN_CONFLICT_POLLING (+ conflictWarningUsername) — токен опрашивает кто-то ещё, бот НЕ остановлен, но теряет часть сообщений (снимается само). Чинится вне Retensy (отключить бота там или /revoke в @BotFather + bot_change_token), затем bot_resume для паузы. Доступ = твоя текущая роль в проекте, проверяется на каждый вызов; владелец мог отозвать её без предупреждения.", inputSchema: { type: "object", properties: {} } },
|
|
1014
1014
|
{ name: "list_graphs", description: "Список сценариев САМОГО бота (без узлов). Вебхук-сценарии, которые лишь отвечают через этого бота, сюда не входят — их публикуют в вебе, в «Сценариях» автора.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1015
1015
|
{ name: "list_channels", description: "Список каналов/групп, подключённых к боту (chatId, title, type, статус бота, дата). chatId — числовой id для условия SUBSCRIBED («Подписан на канал»).", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1016
1016
|
{ name: "integration_catalog", description: "Каталог сервисов Integration Core (GET /api/integrations/catalog): [{provider, name, category, authType, configSchema: [{key, label, hint, secret}] — поля подключения, actions: [{kind, label, inputs}] — действия для сценария и coreDelivery, healthCheck — умеет ли integration_test, inbound: {events: [{key, label, vars: [{key, label, example}], paymentSucceeded}]} | null — провайдер умеет только исходящие действия}]. inbound.events — события для узла TRIGGER_WEBHOOK с заполненным config.provider (см. integration_ingress_url): events[].key идёт в config.event, vars[].key — переменные {{body.<key>}} в сценарии. paymentSucceeded:true — событие сверх обычного вебхука ещё и заводит узел «Оплата прошла» (headless или в чате подписчика, если платёжная ссылка была привязана к нему действием *_link). 404 — Integration Core выключен в сервисе. Read-only.", inputSchema: { type: "object", properties: {} } },
|
|
@@ -1024,7 +1024,8 @@ const TOOLS = [
|
|
|
1024
1024
|
{ name: "edit_graph_live", description: "РЕКОМЕНДОВАННЫЙ способ правки СУЩЕСТВУЮЩЕГО (часто живого/опубликованного) сценария: редактирует ТОТ ЖЕ graphId НА МЕСТЕ (id не меняется) и сначала снимает авто-бэкап текущего состояния в один rolling-граф «🔙 Авто-бэкап». НЕ клонирует и НЕ создаёт новый активный граф. Открытые редакторы перечитают граф вживую (external_update), бот применит изменения сразу (читает активный граф заново из БД). Используй ВМЕСТО clone+publish, когда нужно поправить сценарий, который уже открыт/в проде. ВАЖНО: правку активного графа сервер проверяет как публикацию (валидатор, платные блоки, лимит блоков тарифа, платформа) — при ошибках HTTP 422 со всеми code@nodeId, граф НЕ изменён, бот работает на прежней версии. Прогоняй offline validate.mjs и dry_run заранее, чтобы не ловить 422. Живой граф бота — с isActive:true в list_graphs — для бот-сценария (после publish_graph черновика — publishedGraphId, не id черновика); правка черновика до бота не доходит.", inputSchema: { type: "object", properties: { graphId: { type: "string" }, graph: { type: "object" }, nodes: { type: "array" }, edges: { type: "array" }, canvasMeta: { type: "object" }, name: { type: "string" }, graphFile: { type: "string", description: "Путь к локальному JSON графа (вместо инлайн-передачи); поддерживается ~" }, backup: { type: "boolean", description: "Снимать авто-бэкап предыдущего состояния перед правкой (по умолчанию true)." } }, required: ["graphId"] } },
|
|
1025
1025
|
{ name: "patch_graph", description: "Точечная правка БОЛЬШОГО/живого графа без отправки графа целиком: сервер сам берёт граф по graphId, делает строковые замены в его JSON, проверяет валидность и заливает обратно НА МЕСТЕ (с авто-бэкапом). Идеально, когда граф слишком велик, чтобы передавать его целиком через update_graph/edit_graph_live — напр. сменить id канала в условиях SUBSCRIBED, ссылки кнопок, тексты. replacements: [{find, replace}] — заменяются ВСЕ вхождения; делай find максимально специфичным, чтобы не задеть лишнее. preview=true — только показать число совпадений, ничего не сохраняя. Бот применит изменения сразу только у опубликованного графа (читает активный граф заново из БД); патч черновика до бота не доходит. Результат для активного графа сервер проверяет как публикацию: ошибки → HTTP 422 со всеми code@nodeId, граф не изменён.", inputSchema: { type: "object", properties: { graphId: { type: "string" }, replacements: { type: "array", items: { type: "object", properties: { find: { type: "string" }, replace: { type: "string" } }, required: ["find", "replace"] } }, preview: { type: "boolean", description: "true = только отчёт о числе совпадений, без сохранения" }, backup: { type: "boolean", description: "снять авто-бэкап предыдущего состояния перед правкой (по умолчанию true)" } }, required: ["graphId", "replacements"] } },
|
|
1026
1026
|
{ name: "dry_run", description: "Прогнать сценарий без публикации. kind: command|callback|text. Внешние действия (HTTP, CRM, таблицы, письма, ИИ) пропускаются (skipped: dry-run). Статус прогона: OK, FAILED (прерван) или PARTIAL — «завершён с ошибками»: дошёл до конца, но хотя бы одно действие упало (у шага ok:false, error «КОД: …»).", inputSchema: { type: "object", properties: { graphId: { type: "string" }, kind: { type: "string", enum: ["command", "callback", "text"] }, value: { type: "string" }, fromUsername: { type: "string" }, presetVariables: { type: "object" }, presetTags: { type: "array", items: { type: "string" } } }, required: ["graphId", "kind", "value"] } },
|
|
1027
|
-
{ name: "publish_graph", description: "Опубликовать граф. У бота ОДИН активный сценарий: публикация черновика заменяет содержимое активного (id живого графа сохраняется); прежняя активная версия, которой нет ни в одном черновике бота (правки вживую), сохраняется черновиком «… (была активной до дата время)». Вернёт publishedGraphId; при отказе проверок — ошибка HTTP 422 со всеми причинами построчно (code@nodeId: message). Сценарий-вебхук (источник WEBHOOK) этим инструментом не публикуется — HTTP 409, его публикуют в вебе.", inputSchema: { type: "object", properties: { graphId: { type: "string" } }, required: ["graphId"] } },
|
|
1027
|
+
{ name: "publish_graph", description: "Опубликовать граф. У бота ОДИН активный сценарий: публикация черновика заменяет содержимое активного (id живого графа сохраняется); прежняя активная версия, которой нет ни в одном черновике бота (правки вживую), сохраняется черновиком «… (была активной до дата время)». Вернёт publishedGraphId; при отказе проверок — ошибка HTTP 422 со всеми причинами построчно (code@nodeId: message). Сценарий-вебхук (источник WEBHOOK) этим инструментом не публикуется — HTTP 409, его публикуют в вебе. secondary:true — «опубликовать как ДОПОЛНИТЕЛЬНЫЙ сценарий»: граф становится живым НА МЕСТЕ (publishedGraphId = graphId, снимка нет) рядом с основным, основной сценарий и activeGraphId НЕ меняются; его запускают только VK-лендинги (targetGraphId) и ссылки link_create с targetGraphId. Те же проверки (422), у бота должен быть основной (SECONDARY_NO_MAIN), основной сам дополнительным не делается (SECONDARY_IS_MAIN); триггеры расписания/заявки с сайта/событий канала/комментариев/Instagram/вебхука — 422 SECONDARY_TRIGGER_UNSUPPORTED; режим decoupled — SECONDARY_UNSUPPORTED. У клона основного совпавшие id узлов при первой такой публикации меняются — перечитай граф. Снять — unpublish_graph.", inputSchema: { type: "object", properties: { graphId: { type: "string" }, secondary: { type: "boolean", description: "true — дополнительный сценарий рядом с основным (для лендингов/ссылок); по умолчанию false — основной" } }, required: ["graphId"] } },
|
|
1028
|
+
{ name: "unpublish_graph", description: "Снять ДОПОЛНИТЕЛЬНЫЙ сценарий с публикации (POST /api/bots/graphs/{graphId}/unpublish): он снова черновик, лендинги и ссылки на него ведут в основной сценарий, подписчики в нём возвращаются в основной, его задержки отменяются. Основной сценарий так не снимается — HTTP 409.", inputSchema: { type: "object", properties: { graphId: { type: "string" } }, required: ["graphId"] } },
|
|
1028
1029
|
{ name: "import_funnel", description: "Всё за раз: создать граф, залить узлы/рёбра, (опц.) dry-run /start, опубликовать. Граф можно передать инлайном (graph) или файлом (graphFile).", inputSchema: { type: "object", properties: { botId: { type: "string" }, name: { type: "string" }, graph: { type: "object" }, graphFile: { type: "string", description: "Путь к локальному JSON графа вместо инлайн graph; поддерживается ~" }, dryRun: { type: "boolean" }, publish: { type: "boolean" } }, required: ["botId"] } },
|
|
1029
1030
|
{ name: "list_templates", description: "Список готовых шаблонов воронок (id, имя, описание). Можно стартовать граф из шаблона вместо сборки с нуля.", inputSchema: { type: "object", properties: {} } },
|
|
1030
1031
|
{ name: "create_graph_from_template", description: "Создать граф (DRAFT) из шаблона (см. list_templates). Возвращает граф с id — дальше правь через update_graph.", inputSchema: { type: "object", properties: { botId: { type: "string" }, templateId: { type: "string" }, name: { type: "string" } }, required: ["botId", "templateId"] } },
|
|
@@ -1084,11 +1085,11 @@ const TOOLS = [
|
|
|
1084
1085
|
// ---- VK-лендинги (growth tools, Mini App) ----
|
|
1085
1086
|
{ name: "vk_landing_list", description: "Список VK-лендингов (Mini App, GET /api/growth/vk-landings): контент, сценарий-цель, авто-режим, готовые ссылки links.{base,auto}, статистика views/clicks/subscribes. Read-only.", inputSchema: { type: "object", properties: {} } },
|
|
1086
1087
|
{ name: "vk_landing_get", description: "Один VK-лендинг по id (GET /api/growth/vk-landings/{id}). 404 — не найден/чужой.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1087
|
-
{ name: "vk_landing_create", description: "Создать VK-лендинг (POST /api/growth/vk-landings): botId — VK-бот (create_bot platform VK), title обязателен, остальное опционально — subtitle, imageUrl, buttonText, targetGraphId (сценарий —
|
|
1088
|
+
{ name: "vk_landing_create", description: "Создать VK-лендинг (POST /api/growth/vk-landings): botId — VK-бот (create_bot platform VK), title обязателен, остальное опционально — subtitle, imageUrl, buttonText, targetGraphId (сценарий ЭТОГО бота из list_graphs, иначе HTTP 400: пусто или основной — подписчик идёт в основной сценарий по /start; опубликованный как ДОПОЛНИТЕЛЬНЫЙ (publish_graph secondary:true) — стартует в нём; черновик — пока основной; ответ показывает targetGraphStatus MAIN|SECONDARY|NOT_PUBLISHED|MISSING), tags (добавляются подписчику при переходе), autoSubscribe (true = &auto=1, подписка сразу при открытии), bothelpLandingId (маппинг старой ссылки BotHelp). Возвращает лендинг с готовыми ссылками links.base/links.auto вида vk.com/app<id>_-<groupId>#l=<id>[&auto=1] — публикуй их в сообществе.", inputSchema: { type: "object", properties: { botId: { type: "string" }, title: { type: "string" }, subtitle: { type: "string" }, imageUrl: { type: "string" }, buttonText: { type: "string" }, targetGraphId: { type: "string" }, tags: { type: "array", items: { type: "string" } }, autoSubscribe: { type: "boolean" }, bothelpLandingId: { type: "string" } }, required: ["botId", "title"] } },
|
|
1088
1089
|
{ name: "vk_landing_update", description: "Изменить VK-лендинг (PUT /api/growth/vk-landings/{id}) — те же поля, что vk_landing_create, полностью заменяют текущие (не частичный патч).", inputSchema: { type: "object", properties: { id: { type: "string" }, botId: { type: "string" }, title: { type: "string" }, subtitle: { type: "string" }, imageUrl: { type: "string" }, buttonText: { type: "string" }, targetGraphId: { type: "string" }, tags: { type: "array", items: { type: "string" } }, autoSubscribe: { type: "boolean" }, bothelpLandingId: { type: "string" } }, required: ["id", "botId", "title"] } },
|
|
1089
1090
|
{ name: "vk_landing_delete", description: "Удалить VK-лендинг (DELETE /api/growth/vk-landings/{id}) — ссылки перестают работать.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1090
1091
|
{ name: "bot_stop", description: "Остановить бота (POST /api/bots/{botId}/stop): снимает вебхук, бот перестаёт отвечать, сценарии и подписчики сохраняются. Запуск обратно — bot_resume.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1091
|
-
{ name: "bot_resume", description: "Запустить остановленного бота или бота, приостановленного лимитом тарифа (POST /api/bots/{botId}/resume). Если лимит ботов тарифа исчерпан — HTTP 402 со ссылкой на смену тарифа.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1092
|
+
{ name: "bot_resume", description: "Запустить остановленного бота или бота, приостановленного лимитом тарифа или конфликтом токена (POST /api/bots/{botId}/resume). Снимает pausedReason; у работающего бота с pausedChannels включает обратно только эти каналы. Если вебхук другого сервиса остался, через 10 минут пауза повторится — сначала попроси пользователя отключить бота там. Если лимит ботов тарифа исчерпан — HTTP 402 со ссылкой на смену тарифа.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1092
1093
|
// ---- Подключения ----
|
|
1093
1094
|
{ name: "connect_integration", description: "Подключить сервис (POST /api/bots/integrations) — дальше его id (= connectionId) ставится в действия сценария и в site_lead_settings. provider и creds: AMOCRM {subdomain, longToken} · BITRIX24 {webhookUrl} · GETCOURSE {account, apiKey} · YAMETRIKA {counterId, oauthToken} · YOOKASSA {shopId, secretKey}. Остальные ~25 сервисов (smsru, retailcrm, cloudpayments, rest_api, yandex_market и т.д.) берутся из каталога Integration Core (integration_catalog) — provider передавай его provider-ключом в НИЖНЕМ регистре, нужные creds — из configSchema каждого провайдера (hint подскажет, где взять; поле с «необязательно»/optional в hint можно не слать). Без нужных creds вернёт, какие поля и где их взять. connectionId — обновить креды/название существующего подключения (PUT). Сервисы со входом через браузер не падают, а возвращают ссылку для пользователя: GOOGLE_SHEETS → ссылка согласия Google (OAuth; после неё таблицы выбираются в узле «Google Таблицы»), INSTAGRAM → ссылка на кабинет /bots/instagram (вход через Instagram, нужен Instagram-аккаунт Business/Creator). TELEGRAM/MAX — это боты: используй create_bot. Провайдер каталога со статусом COMING_SOON/IN_DEVELOPMENT подключить нельзя (вернёт ошибку). Если каталог недоступен (офлайн/выключен) — работают только 5 легаси-провайдеров выше. Креды хранятся в сервисе зашифрованными и НИКОГДА не возвращаются — ни здесь, ни в list_integrations (только маска hint); в отчёты не попадают. Проверить ключ после подключения — integration_test.", inputSchema: { type: "object", properties: { provider: { type: "string", description: "AMOCRM | BITRIX24 | GETCOURSE | YAMETRIKA | YOOKASSA | GOOGLE_SHEETS | INSTAGRAM, либо провайдер из integration_catalog (его ключ в нижнем регистре, напр. smsru, retailcrm, cloudpayments)" }, title: { type: "string", description: "Название подключения в кабинете (например «amoCRM продажи»)" }, creds: { type: "object", description: "Поля провайдера: для легаси — см. описание, для остальных — ключи из configSchema провайдера в integration_catalog" }, connectionId: { type: "string", description: "id существующего подключения (list_integrations) — обновить его" } }, required: ["provider"] } },
|
|
1094
1095
|
{ name: "disconnect_integration", description: "Удалить подключение сервиса по id из list_integrations (DELETE /api/bots/integrations/{id}). Действия сценария с этим connectionId перестанут работать.", inputSchema: { type: "object", properties: { connectionId: { type: "string" } }, required: ["connectionId"] } },
|
|
@@ -1150,7 +1151,7 @@ const TOOLS = [
|
|
|
1150
1151
|
{ name: "bot_channel_list", description: "Дополнительные Telegram-боты (мультиканальность) этого бота (GET /api/bots/{botId}/channels): id, username, name, active. Это НЕ каналы/группы для постинга — те в list_channels. Read-only.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1151
1152
|
{ name: "bot_channel_add", description: "Добавить к боту ещё одного Telegram-бота как канал (POST /api/bots/{botId}/channels {token, name?}): он отвечает теми же сценариями. Токен проверяется в Telegram; токен, уже подключённый где-то ещё, не принимается.", inputSchema: { type: "object", properties: { botId: { type: "string" }, token: { type: "string" }, name: { type: "string" } }, required: ["botId", "token"] } },
|
|
1152
1153
|
{ name: "bot_channel_delete", description: "Отключить дополнительного Telegram-бота от бота (DELETE /api/bots/{botId}/channels/{channelId}). channelId — из bot_channel_list. Только по явной просьбе, с confirm:true.", inputSchema: { type: "object", properties: { botId: { type: "string" }, channelId: { type: "string" }, confirm: { type: "boolean" } }, required: ["botId", "channelId", "confirm"] } },
|
|
1153
|
-
{ name: "link_create", description: "Создать стартовую ссылку бота (POST /api/bots/{botId}/links {name, targetNodeId?}): code для t.me/<бот>?start=<code>, счётчик стартов. targetNodeId — узел сценария, на который ведёт диплинк; для одного узла повторный вызов вернёт существующую ссылку.", inputSchema: { type: "object", properties: { botId: { type: "string" }, name: { type: "string" }, targetNodeId: { type: "string" } }, required: ["botId"] } },
|
|
1154
|
+
{ name: "link_create", description: "Создать стартовую ссылку бота (POST /api/bots/{botId}/links {name, targetNodeId?}): code для t.me/<бот>?start=<code>, счётчик стартов. targetNodeId — узел сценария, на который ведёт диплинк; для одного узла повторный вызов вернёт существующую ссылку. targetGraphId — дополнительный сценарий этого бота (publish_graph secondary:true), в котором стартует подписчик (targetNodeId тогда — узел в нём); не опубликован как дополнительный — ведёт в основной; чужой граф — HTTP 400.", inputSchema: { type: "object", properties: { botId: { type: "string" }, name: { type: "string" }, targetNodeId: { type: "string" }, targetGraphId: { type: "string" } }, required: ["botId"] } },
|
|
1154
1155
|
{ name: "link_delete", description: "Удалить стартовую ссылку (DELETE /api/bots/links/{linkId}); id — из list_links. Разосланные ссылки перестанут считаться. Только по явной просьбе, с confirm:true.", inputSchema: { type: "object", properties: { linkId: { type: "string" }, confirm: { type: "boolean" } }, required: ["linkId", "confirm"] } },
|
|
1155
1156
|
{ name: "utm_sources", description: "UTM-источники подписчиков бота (GET /api/bots/{botId}/utm-sources): по каким меткам utm_* люди входили в сценарии и сколько их. Read-only.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1156
1157
|
{ name: "ab_results", description: "Результаты A/B-теста (GET /api/bots/graphs/{graphId}/ab-results?branchNodeId&period): статистика по вариантам узла-развилки. branchNodeId — id узла A/B в графе; period — «24h», «7d», «30d» (по умолчанию 7d). Read-only.", inputSchema: { type: "object", properties: { graphId: { type: "string" }, branchNodeId: { type: "string" }, period: { type: "string" } }, required: ["graphId", "branchNodeId"] } },
|
|
@@ -1368,7 +1369,8 @@ async function handleCall(params) {
|
|
|
1368
1369
|
return okResult(await api(`/api/bots/graphs/${a.graphId}/dry-run`, { method: "POST", body: { kind: a.kind, value, fromUsername: a.fromUsername, presetVariables: a.presetVariables, presetTags: a.presetTags } }));
|
|
1369
1370
|
}
|
|
1370
1371
|
case "publish_graph": {
|
|
1371
|
-
const
|
|
1372
|
+
const qs = a.secondary === true ? "?secondary=true" : "";
|
|
1373
|
+
const pub = await api(`/api/bots/graphs/${a.graphId}/publish${qs}`, { method: "POST" });
|
|
1372
1374
|
// errors[] приходит с HTTP 200, но для пользователя это «не получилось» — и самый
|
|
1373
1375
|
// ценный сигнал: видно, какого узла/возможности ему не хватило.
|
|
1374
1376
|
if (Array.isArray(pub?.errors) && pub.errors.length) {
|
|
@@ -1761,9 +1763,12 @@ async function handleCall(params) {
|
|
|
1761
1763
|
await api(`/api/bots/${botId}/channels/${channelId}`, { method: "DELETE" });
|
|
1762
1764
|
return okResult(`🗑️ Канал ${channelId} отключён от бота ${botId}.`);
|
|
1763
1765
|
}
|
|
1766
|
+
case "unpublish_graph":
|
|
1767
|
+
return okResult(await api(`/api/bots/graphs/${uuidArg(a.graphId, "graphId")}/unpublish`, { method: "POST" }));
|
|
1764
1768
|
case "link_create": {
|
|
1765
1769
|
const body = { name: a.name || undefined };
|
|
1766
1770
|
if (a.targetNodeId) body.targetNodeId = uuidArg(a.targetNodeId, "targetNodeId");
|
|
1771
|
+
if (a.targetGraphId) body.targetGraphId = uuidArg(a.targetGraphId, "targetGraphId");
|
|
1767
1772
|
return okResult(await api(`/api/bots/${uuidArg(a.botId, "botId")}/links`, { method: "POST", body }));
|
|
1768
1773
|
}
|
|
1769
1774
|
case "link_delete": {
|