@retensy/mcp 0.25.0 → 0.25.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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/skills/build-publication-chain/SKILL.md +49 -4
- package/src/index.mjs +61 -5
package/README.md
CHANGED
|
@@ -263,7 +263,8 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
263
263
|
| `bothelp_scenario_commit(botId, scenarioFile, externalRequestsDir?)` | то же + сохраняет граф (DRAFT); идемпотентно по `(botId, bothelpScenarioId)` — повторный вызов обновляет тот же граф |
|
|
264
264
|
| `chain_list()` / `chain_get(chainId)` | цепочки публикаций («Публикации» → «Цепочки», автопостинг source → граф обработки/назначений) / одна цепочка как хранится (legacy либо граф) |
|
|
265
265
|
| `chain_graph_get(chainId)` | граф цепочки ВСЕГДА graph-shaped `{nodes, edges}`, даже для старых линейных цепочек (синтезируется на лету) |
|
|
266
|
-
| `chain_create(chain)` / `chain_update(chainId, chain)` | создать / изменить цепочку целиком (`chain`: `name`, `source` TELEGRAM\|VK\|RSS, `approvalMode?`, `utmDefaults?`, и либо legacy `destinations[]`+`steps[]`, либо граф `nodes[]`+`edges[]` — дерево с ветвлением, не произвольный DAG: один узел может кормить несколько назначений, слияния путей нет); HTTP 409 — прямой цикл с другой цепочкой; графовый 400 — «Схема цепочки: …» (см. `chain_validate`) |
|
|
266
|
+
| `chain_create(chain)` / `chain_update(chainId, chain)` | создать / изменить цепочку целиком (`chain`: `name`, `source` TELEGRAM\|VK\|RSS, `approvalMode?`, `utmDefaults?`, и либо legacy `destinations[]`+`steps[]`, либо граф `nodes[]`+`edges[]` — дерево с ветвлением, не произвольный DAG: один узел может кормить несколько назначений, слияния путей нет); граф-узлы — опциональные `x?`/`y?` (координаты холста, UI-подсказка, бэкенд не интерпретирует, опусти → авто-layout); каждое `destination`/`destinations[]` — опциональный `aiOverride?: {enabled, aiMode, tone?, instructions?}`, персональная адаптация ИИ сверх общего `AI_ADAPT` только для этого назначения; HTTP 409 — прямой цикл с другой цепочкой; графовый 400 — «Схема цепочки: …» (см. `chain_validate`) |
|
|
267
|
+
| `chain_create_draft(name)` | создать минимальный черновик цепочки (`enabled:false`, без источника/назначений) для потока «создать → сразу донастроить на холсте „Схема“»; не проходит обычную валидацию `chain_create` |
|
|
267
268
|
| `chain_validate(nodes, edges)` / `chain_validate(chain)` | офлайн-проверка графа (зеркало серверного `ChainGraphValidator`, не строже сервера) — перед `chain_create`/`chain_update` на сложном графе |
|
|
268
269
|
| `chain_destinations_catalog(botIds?)` | одним вызовом: все TG/MAX-каналы по ботам владельца + VK-аккаунты — собрать «1 источник → N каналов» без ручного обхода `list_bots`+`list_channels`+`vk_status` |
|
|
269
270
|
| `chain_delete(chainId, confirm:true)` | удалить цепочку (теряется история заданий) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retensy/mcp",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.1",
|
|
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": {
|
|
@@ -38,10 +38,14 @@ legacy (steps[]+destinations[]) ЛИБО граф (nodes[]+edges[])}`.
|
|
|
38
38
|
```
|
|
39
39
|
nodes: [{ id, type: SOURCE|FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY|DESTINATION, x?, y?,
|
|
40
40
|
step?: {...поля шага, см. ниже — только для type≠SOURCE/DESTINATION},
|
|
41
|
-
destination?: {id, type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?,
|
|
41
|
+
destination?: {id, type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?,
|
|
42
|
+
utmOverride?, aiOverride?} }]
|
|
42
43
|
edges: [{ fromNodeId, toNodeId }]
|
|
43
44
|
```
|
|
44
|
-
`x`/`y` — координаты на
|
|
45
|
+
`x`/`y` (number, опционально) — координаты узла на холсте «Схема», чисто UI-подсказка, бэкенд их не
|
|
46
|
+
интерпретирует и ничего не ломается, если их не передать: фронтенд сам раскладывает узлы
|
|
47
|
+
BFS-авто-layout'ом по столбцам/строкам. Не придумывай координаты сам — просто опускай поле (или
|
|
48
|
+
оставляй `null`), если пользователь явно не просит конкретную раскладку.
|
|
45
49
|
Узел — «мешок полей»: только поля своего `type` что-то значат.
|
|
46
50
|
|
|
47
51
|
- **Legacy (линейный режим, `nodes` пуст)** — проще для одного source → один путь обработки → N
|
|
@@ -49,7 +53,7 @@ legacy (steps[]+destinations[]) ЛИБО граф (nodes[]+edges[])}`.
|
|
|
49
53
|
потом веер в destinations»):
|
|
50
54
|
```
|
|
51
55
|
steps: [{ type: FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY, ...поля }] // применяются по порядку ко всем
|
|
52
|
-
destinations: [{ type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride? }]
|
|
56
|
+
destinations: [{ type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?, aiOverride? }]
|
|
53
57
|
```
|
|
54
58
|
Используй legacy, когда обработка ОДИНАКОВА для всех назначений (линия, без развилок). Переходи на
|
|
55
59
|
граф, когда назначениям нужна РАЗНАЯ обработка (например, свой тон ИИ под каждую площадку) или нужно
|
|
@@ -68,12 +72,46 @@ legacy (steps[]+destinations[]) ЛИБО граф (nodes[]+edges[])}`.
|
|
|
68
72
|
- `DELAY`: `delaySeconds`.
|
|
69
73
|
|
|
70
74
|
- **`destination`/`destinations[]`**: `{id (строка, стабильный ключ идемпотентности — не меняй после
|
|
71
|
-
первого сохранения), type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride
|
|
75
|
+
первого сохранения), type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?,
|
|
76
|
+
aiOverride?}`.
|
|
72
77
|
TELEGRAM/MAX — `botId`+`chatId` (свой привязанный канал, бот должен быть админом). VK — `accountId`.
|
|
73
78
|
**INSTAGRAM принимается моделью, но публикация ГАРАНТИРОВАННО уходит в `SKIPPED`** (не `FAILED`) —
|
|
74
79
|
нет медиа-процесса в этой волне; не предлагай его пользователю как рабочее назначение, только если
|
|
75
80
|
он сам просит «добавить на будущее».
|
|
76
81
|
|
|
82
|
+
- **Общий ИИ vs персональный `aiOverride` — когда что использовать.** Два РАЗНЫХ способа применить
|
|
83
|
+
адаптацию ИИ, и их можно комбинировать:
|
|
84
|
+
- **Общий узел `AI_ADAPT`** (`step`/`steps[]`) — обрабатывает пост ОДИН раз и кормит ВСЕ назначения
|
|
85
|
+
на этом пути одинаковым результатом. Оплачивается один раз на пост источника, даже если на выходе
|
|
86
|
+
10 назначений. Используй, когда у всех (или у большинства) каналов один и тот же тон/формат.
|
|
87
|
+
- **`destination.aiOverride`** — `{enabled, aiMode: NONE|LIGHT|FULL, tone?, instructions?}`,
|
|
88
|
+
персональная ВТОРАЯ адаптация ТОЛЬКО для этого одного назначения: применяется ПОСЛЕ результата
|
|
89
|
+
общего `AI_ADAPT` на пути (или поверх сырого текста источника, если общего узла на пути к этому
|
|
90
|
+
назначению нет), своими `aiMode`/`tone`/`instructions`, и не трогает соседние назначения под тем
|
|
91
|
+
же общим узлом. `null`/`enabled:false` (по умолчанию) — поведение не меняется, работает только
|
|
92
|
+
общий узел как обычно. Каждое назначение с `aiOverride.enabled:true` — это ОТДЕЛЬНЫЙ оплачиваемый
|
|
93
|
+
вызов ИИ (сверх общего узла, если он есть), не путай с общим узлом с точки зрения AI-бюджета.
|
|
94
|
+
- Используй `aiOverride`, когда нужен один общий тон для большинства каналов, но для одного-двух —
|
|
95
|
+
особый (например, более формальный для делового VK-сообщества при неформальном общем тоне), без
|
|
96
|
+
заведения отдельного `AI_ADAPT`-узла и ветки в графе ради одного назначения — проще и легаси, и
|
|
97
|
+
граф. Если РАЗНЫХ тонов больше двух-трёх и они устойчивые — удобнее явные ветки (Пример 3), чтобы
|
|
98
|
+
схема была видна в UI; `aiOverride` — для точечных исключений из общего правила.
|
|
99
|
+
- Пример (легаси, общий `AI_ADAPT` деловым тоном + одно назначение с персональным разговорным
|
|
100
|
+
тоном):
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"steps": [{ "type": "AI_ADAPT", "aiMode": "LIGHT", "tone": "деловой, нейтральный" }],
|
|
104
|
+
"destinations": [
|
|
105
|
+
{ "type": "TELEGRAM", "botId": "<botId>", "chatId": 2001 },
|
|
106
|
+
{ "type": "VK", "accountId": "<accountId>",
|
|
107
|
+
"aiOverride": { "enabled": true, "aiMode": "LIGHT", "tone": "разговорный, с эмодзи",
|
|
108
|
+
"instructions": "короче, без канцелярита" } }
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
TG получает результат общего узла как есть; VK получает ЕЩЁ ОДНУ адаптацию поверх общего —
|
|
113
|
+
разговорным тоном, с отдельной оплатой ИИ только за это назначение.
|
|
114
|
+
|
|
77
115
|
- **`utmDefaults`** (на уровне цепочки) / **`destination.utmOverride`** (на уровне назначения, `null` =
|
|
78
116
|
взять `utmDefaults`): `{enabled, source, medium, campaign, content, term,
|
|
79
117
|
overridePolicy: KEEP_EXISTING|OVERRIDE (default KEEP_EXISTING), skipInternalLinks (default true)}`.
|
|
@@ -90,6 +128,13 @@ legacy (steps[]+destinations[]) ЛИБО граф (nodes[]+edges[])}`.
|
|
|
90
128
|
|
|
91
129
|
## Рабочий процесс
|
|
92
130
|
|
|
131
|
+
0. **(Опционально) `chain_create_draft`** — если пользователь хочет «создать цепочку и донастроить
|
|
132
|
+
в UI на холсте» без того, чтобы ты сразу собирал полный граф/legacy: передай только `{name}`,
|
|
133
|
+
получишь `PublishChain` с `enabled:false`, без источника/назначений — черновик не проходит обычную
|
|
134
|
+
валидацию `chain_create` и не может случайно уйти в работу недонастроенным. Обычно НЕ нужен агенту:
|
|
135
|
+
если у тебя уже есть все вводные (источник, назначения, обработка), просто используй `chain_create`
|
|
136
|
+
с полным объектом за один вызов — это быстрее для пользователя.
|
|
137
|
+
|
|
93
138
|
1. **Собери вводные.** Источник (платформа + канал/аккаунт/URL), что меняется в тексте по пути
|
|
94
139
|
(фильтры, переписывание, ИИ), куда публикуется (один список площадок или разные пути на разные
|
|
95
140
|
площадки), нужно ли согласование, нужна ли UTM-метка и различается ли она по каналам.
|
package/src/index.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import os from "node:os";
|
|
|
27
27
|
import fs from "node:fs";
|
|
28
28
|
import path from "node:path";
|
|
29
29
|
|
|
30
|
-
const VERSION = "0.25.
|
|
30
|
+
const VERSION = "0.25.1";
|
|
31
31
|
/** С чего начать пустой сайт (init у /document/ops; на сайте с черновиком игнорируется). */
|
|
32
32
|
const SITE_INITS = ["starter", "blank", "mini-landing"];
|
|
33
33
|
/** Безвредная операция, когда нужен только init: бэкенд не принимает пустой ops[]. */
|
|
@@ -630,6 +630,20 @@ function uuidArg(v, label) {
|
|
|
630
630
|
if (!UUID_RE.test(s)) throw new Error(`${label} — UUID (например, из list_bots), получено: ${s || "пусто"}.`);
|
|
631
631
|
return s;
|
|
632
632
|
}
|
|
633
|
+
/** Тело vk_landing_create/vk_landing_update — одна форма на оба (PUT полностью заменяет поля). */
|
|
634
|
+
function landingBody(a) {
|
|
635
|
+
return {
|
|
636
|
+
botId: uuidArg(a.botId, "botId"),
|
|
637
|
+
title: String(a.title),
|
|
638
|
+
subtitle: a.subtitle ?? null,
|
|
639
|
+
imageUrl: a.imageUrl ?? null,
|
|
640
|
+
buttonText: a.buttonText ?? null,
|
|
641
|
+
targetGraphId: a.targetGraphId ? uuidArg(a.targetGraphId, "targetGraphId") : null,
|
|
642
|
+
tags: Array.isArray(a.tags) ? a.tags.map(String) : [],
|
|
643
|
+
autoSubscribe: !!a.autoSubscribe,
|
|
644
|
+
bothelpLandingId: a.bothelpLandingId ?? null,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
633
647
|
// id из Mongo/строковые ключи: только безопасный сегмент пути, без «/», «..», пробелов.
|
|
634
648
|
function idArg(v, label) {
|
|
635
649
|
const s = String(v ?? "").trim();
|
|
@@ -1028,7 +1042,13 @@ const TOOLS = [
|
|
|
1028
1042
|
{ name: "kb_reindex", description: "Переиндексировать документ-файл базы знаний из сохранённого оригинала (POST /api/bots/kb/{kbId}/docs/{docId}/reindex) — «Повторить» после ошибки. headerRow — необязательно, для табличных файлов: номер строки с шапкой (1..50), если автоопределение ошиблось. Только для источника FILE.", inputSchema: { type: "object", properties: { kbId: { type: "string" }, docId: { type: "string" }, headerRow: { type: "number" } }, required: ["kbId", "docId"] } },
|
|
1029
1043
|
{ name: "agent_unanswered", description: "Вопросы без ответа агента за период (GET /api/bots/agents/{agentId}/unanswered): days — 7|30|90 (по умолчанию 30). Группы вопросов, на которые агент не нашёл ответ в базе знаний — подсказка, что туда добавить. Read-only.", inputSchema: { type: "object", properties: { agentId: { type: "string" }, days: { type: "number", enum: [7, 30, 90] } }, required: ["agentId"] } },
|
|
1030
1044
|
// ---- Боты ----
|
|
1031
|
-
{ name: "create_bot", description: "Подключить бота по токену (POST /api/bots): platform TELEGRAM (токен от @BotFather) или MAX (токен от MasterBot в MAX). Вебхук настраивается сам; name — отображаемое имя (иначе @username). Возвращает бота с id. Число ботов ограничено тарифом — HTTP 402 со ссылкой на смену тарифа. platform WEB — чат-виджет для сайта без токена (POST /api/bots/web): вернёт botId, key и snippet — код вставки на сайт (у существующего виджета — web_widget_snippet). platform INSTAGRAM по токену не подключается — только входом через Instagram (OAuth) в кабинете (/bots/instagram), нужен Instagram-аккаунт Business или Creator — инструмент вернёт ссылку на кабинет вместо ошибки.", inputSchema: { type: "object", properties: { platform: { type: "string", enum: ["TELEGRAM", "MAX", "WEB", "INSTAGRAM"] }, token: { type: "string", description: "Токен
|
|
1045
|
+
{ name: "create_bot", description: "Подключить бота по токену (POST /api/bots): platform TELEGRAM (токен от @BotFather) или MAX (токен от MasterBot в MAX). Вебхук настраивается сам; name — отображаемое имя (иначе @username). Возвращает бота с id. Число ботов ограничено тарифом — HTTP 402 со ссылкой на смену тарифа. platform WEB — чат-виджет для сайта без токена (POST /api/bots/web): вернёт botId, key и snippet — код вставки на сайт (у существующего виджета — web_widget_snippet). platform INSTAGRAM по токену не подключается — только входом через Instagram (OAuth) в кабинете (/bots/instagram), нужен Instagram-аккаунт Business или Creator — инструмент вернёт ссылку на кабинет вместо ошибки. platform VK — сообщество как полноценный канал (Callback API, НЕ путать с vk_connect — тот для публикаций на стену): token — токен СООБЩЕСТВА (Управление сообществом → Работа с API → Создать ключ, доступ «Сообщения сообщества»), groupId — числовой id сообщества (обязателен). После подключения нужно вручную включить Callback API в настройках сообщества — см. webhookSecret в ответе и URL /vk/hooks/{botId} (подробный чек-лист у разработчика).", inputSchema: { type: "object", properties: { platform: { type: "string", enum: ["TELEGRAM", "MAX", "WEB", "INSTAGRAM", "VK"] }, token: { type: "string", description: "Токен бота/сообщества: 123456789:AA… (Telegram), токен MAX, или community token VK" }, groupId: { type: "string", description: "Только platform=VK: числовой id сообщества" }, name: { type: "string" } }, required: ["platform"] } },
|
|
1046
|
+
// ---- VK-лендинги (growth tools, Mini App) ----
|
|
1047
|
+
{ 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: {} } },
|
|
1048
|
+
{ name: "vk_landing_get", description: "Один VK-лендинг по id (GET /api/growth/vk-landings/{id}). 404 — не найден/чужой.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1049
|
+
{ name: "vk_landing_create", description: "Создать VK-лендинг (POST /api/growth/vk-landings): botId — VK-бот (create_bot platform VK), title обязателен, остальное опционально — subtitle, imageUrl, buttonText, targetGraphId (сценарий — список scenario_list/list_graphs), 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"] } },
|
|
1050
|
+
{ 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"] } },
|
|
1051
|
+
{ name: "vk_landing_delete", description: "Удалить VK-лендинг (DELETE /api/growth/vk-landings/{id}) — ссылки перестают работать.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1032
1052
|
{ name: "bot_stop", description: "Остановить бота (POST /api/bots/{botId}/stop): снимает вебхук, бот перестаёт отвечать, сценарии и подписчики сохраняются. Запуск обратно — bot_resume.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1033
1053
|
{ name: "bot_resume", description: "Запустить остановленного бота или бота, приостановленного лимитом тарифа (POST /api/bots/{botId}/resume). Если лимит ботов тарифа исчерпан — HTTP 402 со ссылкой на смену тарифа.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1034
1054
|
// ---- Подключения ----
|
|
@@ -1127,8 +1147,9 @@ const TOOLS = [
|
|
|
1127
1147
|
{ name: "chain_list", description: "Цепочки публикаций пользователя (GET /api/bots/chains): автоматический репост source → граф обработки/назначений. Цепочка привязана к владельцу, не к конкретному боту. Read-only.", inputSchema: { type: "object", properties: {} } },
|
|
1128
1148
|
{ name: "chain_get", description: "Цепочка по id целиком, как хранится в базе — легаси-поля (source/steps/destinations) ИЛИ нативный граф (nodes/edges), смотря что заполнено (GET /api/bots/chains/{id}). Для гарантированно графовой формы (в т.ч. у старых линейных цепочек) используй chain_graph_get. 404 — не своя/не найдена. Read-only.", inputSchema: { type: "object", properties: { chainId: { type: "string" } }, required: ["chainId"] } },
|
|
1129
1149
|
{ name: "chain_graph_get", description: "Граф цепочки В ЛЮБОМ случае (GET /api/bots/chains/{id}/graph): {nodes, edges} — если цепочка легаси (линейная), бэкенд синтезирует эквивалентный граф на лету (ничего не сохраняет). Используй это вместо самостоятельной сборки графа из steps/destinations. Read-only.", inputSchema: { type: "object", properties: { chainId: { type: "string" } }, required: ["chainId"] } },
|
|
1130
|
-
{ name: "chain_create", description: "Создать цепочку публикаций (POST /api/bots/chains): chain — PublishChain draft, {name, enabled?, approvalMode?: NONE|AI_ONLY|ALL, source: {type: TELEGRAM|VK|RSS, botId?, chatId?, accountId?, rssUrl?}, utmDefaults?, И ЛИБО легаси-поля (steps?, destinations?) ЛИБО нативный граф (nodes?, edges?)}. `source` ВСЕГДА отдельное top-level поле — даже в графовом режиме узел SOURCE в nodes его не дублирует, это просто маркер-корень графа. ГРАФ (nodes непустой — приоритетнее steps/destinations, которые тогда игнорируются): nodes — [{id, type: SOURCE|FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY|DESTINATION, x?, y?, step?: {...поля шага, форма как у steps[] в legacy, см. chain_update}, destination?: {id, type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?} — только для type=DESTINATION}], edges — [{fromNodeId, toNodeId}]. Модель — дерево-с-ветвлением (out-tree), НЕ произвольный DAG: у любого не-SOURCE узла ровно ОДНО входящее ребро; ветвление — через НЕСКОЛЬКО исходящих у одного родителя (напр. один общий AI_ADAPT → много DESTINATION = «1 источник → общий ИИ → N каналов»); слияния путей (несколько родителей) нет. Перед сохранением сложного графа прогони chain_validate (клиентское зеркало серверных правил) и chain_destinations_catalog (список доступных назначений). ЛЕГАСИ (nodes пуст): destinations: [{type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?}], steps?: [{type: FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY, ...поля шага}]. TELEGRAM/MAX источник или назначение — свой привязанный канал бота (bot_channel_list/list_channels), VK — подключённый аккаунт (vk_status); chain_destinations_catalog отдаёт оба списка сразу. INSTAGRAM в destinations принимается моделью, но публикация требует медиа и подключённый Instagram-аккаунт — без них задание уходит в SKIPPED с причиной, не FAILED. HTTP 400 — не хватает полей, источник/назначение не найдены/не ваши, ИЛИ (графовый режим) структурная ошибка графа: «Схема цепочки: <причины>» (нет/>1 SOURCE, цикл, узел без входящего ребра или с >1 входящих, DESTINATION с исходящими рёбрами, недостижимое DESTINATION) — ровно те же правила, что проверяет chain_validate. HTTP 409 — прямой цикл с другой вашей цепочкой (A→B при уже существующей B→A).", inputSchema: { type: "object", properties: { chain: { type: "object", description: "PublishChain draft — см. описание инструмента" } }, required: ["chain"] } },
|
|
1131
|
-
{ name: "
|
|
1150
|
+
{ name: "chain_create", description: "Создать цепочку публикаций (POST /api/bots/chains): chain — PublishChain draft, {name, enabled?, approvalMode?: NONE|AI_ONLY|ALL, source: {type: TELEGRAM|VK|RSS, botId?, chatId?, accountId?, rssUrl?}, utmDefaults?, И ЛИБО легаси-поля (steps?, destinations?) ЛИБО нативный граф (nodes?, edges?)}. `source` ВСЕГДА отдельное top-level поле — даже в графовом режиме узел SOURCE в nodes его не дублирует, это просто маркер-корень графа. ГРАФ (nodes непустой — приоритетнее steps/destinations, которые тогда игнорируются): nodes — [{id, type: SOURCE|FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY|DESTINATION, x?, y?, step?: {...поля шага, форма как у steps[] в legacy, см. chain_update}, destination?: {id, type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?, aiOverride?} — только для type=DESTINATION}], edges — [{fromNodeId, toNodeId}]. `x`/`y` (number, опционально) — координаты узла для отрисовки на холсте «Схема»; чисто UI-подсказка, бэкенд их не интерпретирует и не валидирует. Опусти (или оставь null) — фронтенд сам раскладывает узлы BFS-авто-layout'ом по столбцам/строкам; не нужно придумывать координаты самостоятельно. `destination.aiOverride` (опционально, та же форма и в легаси destinations[], см. ниже) — персональная адаптация ИИ ТОЛЬКО для этого назначения: {enabled: bool, aiMode: NONE|LIGHT|FULL, tone?: string, instructions?: string}. null/enabled:false (по умолчанию) — ничего не меняется, применяется только общий узел AI_ADAPT на пути к этому назначению (если он есть), как обычно. enabled:true — ПОСЛЕ результата общего узла (или сырого текста источника, если общего AI_ADAPT на пути нет) вызывается ЕЩЁ ОДНА адаптация ИИ с параметрами override, результат идёт ТОЛЬКО в это назначение — соседние назначения под тем же общим узлом не затрагиваются (означает лишний вызов ИИ и лишнее списание только за это назначение). Используй, когда нужен один общий тон для большинства каналов, но для одного-двух — особый (напр. более формальный) без заведения отдельного AI_ADAPT узла в графе. Модель — дерево-с-ветвлением (out-tree), НЕ произвольный DAG: у любого не-SOURCE узла ровно ОДНО входящее ребро; ветвление — через НЕСКОЛЬКО исходящих у одного родителя (напр. один общий AI_ADAPT → много DESTINATION = «1 источник → общий ИИ → N каналов»); слияния путей (несколько родителей) нет. Перед сохранением сложного графа прогони chain_validate (клиентское зеркало серверных правил) и chain_destinations_catalog (список доступных назначений). Для быстрого создания черновика без источника/назначений (поток «создать → сразу в Схему») используй chain_create_draft вместо этого инструмента. ЛЕГАСИ (nodes пуст): destinations: [{type: TELEGRAM|MAX|VK|INSTAGRAM, botId?, chatId?, accountId?, utmOverride?, aiOverride?}], steps?: [{type: FILTER|TEXT_TRANSFORM|AI_ADAPT|UTM|DELAY, ...поля шага}]. `aiOverride` работает в легаси-режиме так же, как в графовом (см. выше) — применяется поверх общего шага AI_ADAPT в steps[], если он есть. TELEGRAM/MAX источник или назначение — свой привязанный канал бота (bot_channel_list/list_channels), VK — подключённый аккаунт (vk_status); chain_destinations_catalog отдаёт оба списка сразу. INSTAGRAM в destinations принимается моделью, но публикация требует медиа и подключённый Instagram-аккаунт — без них задание уходит в SKIPPED с причиной, не FAILED. HTTP 400 — не хватает полей, источник/назначение не найдены/не ваши, ИЛИ (графовый режим) структурная ошибка графа: «Схема цепочки: <причины>» (нет/>1 SOURCE, цикл, узел без входящего ребра или с >1 входящих, DESTINATION с исходящими рёбрами, недостижимое DESTINATION) — ровно те же правила, что проверяет chain_validate. HTTP 409 — прямой цикл с другой вашей цепочкой (A→B при уже существующей B→A).", inputSchema: { type: "object", properties: { chain: { type: "object", description: "PublishChain draft — см. описание инструмента" } }, required: ["chain"] } },
|
|
1151
|
+
{ name: "chain_create_draft", description: "Создать минимальный черновик цепочки (POST /api/bots/chains/draft) по имени: {name}. Возвращает PublishChain с enabled=false, source={type:TELEGRAM} БЕЗ конкретного botId/chatId, без steps/destinations/nodes/edges — обходит обычную валидацию chain_create (которая потребовала бы источник и ≥1 назначение). Только для потока «новая цепочка → сразу открыть на холсте „Схема“ и донастроить источник/назначения там» — черновик не может случайно уйти в работу недонастроенным (enabled всегда false, обычный chain_update с теми же правилами валидации, что и chain_create, не даст сохранить его без источника и назначений). Для обычного создания цепочки сразу с источником и назначениями используй chain_create.", inputSchema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } },
|
|
1152
|
+
{ name: "chain_update", description: "Изменить цепочку целиком (PUT /api/bots/chains/{id}): chain — PublishChain draft (та же форма, что в chain_create, легаси ИЛИ граф), заменяет текущую конфигурацию полностью — сначала прочитай chain_get (или chain_graph_get, если нужен гарантированно графовый вид) и поменяй нужное. Те же 400/409, что у chain_create (включая x?/y? на узлах и aiOverride? на destination — см. chain_create).", inputSchema: { type: "object", properties: { chainId: { type: "string" }, chain: { type: "object", description: "PublishChain draft целиком" } }, required: ["chainId", "chain"] } },
|
|
1132
1153
|
{ name: "chain_delete", description: "УДАЛИТЬ цепочку публикаций (DELETE /api/bots/chains/{id}). История заданий (chain_runs) теряется вместе с ней. Необратимо: только по явной просьбе, с confirm:true.", inputSchema: { type: "object", properties: { chainId: { type: "string" }, confirm: { type: "boolean" } }, required: ["chainId", "confirm"] } },
|
|
1133
1154
|
{ name: "chain_enable", description: "Включить цепочку (POST /api/bots/chains/{id}/enable) — начинает принимать новые посты источника (TELEGRAM — сразу по событию, VK/RSS — со следующего опроса, раз в 3 мин).", inputSchema: { type: "object", properties: { chainId: { type: "string" } }, required: ["chainId"] } },
|
|
1134
1155
|
{ name: "chain_pause", description: "Поставить цепочку на паузу (POST /api/bots/chains/{id}/pause) — перестаёт принимать новые посты источника (VK/RSS — опрос останавливается, TELEGRAM — события игнорируются); история и настройки сохраняются.", inputSchema: { type: "object", properties: { chainId: { type: "string" } }, required: ["chainId"] } },
|
|
@@ -1891,8 +1912,26 @@ async function handleCall(params) {
|
|
|
1891
1912
|
const platform = String(a.platform || "").trim().toUpperCase();
|
|
1892
1913
|
if (platform === "INSTAGRAM") return instagramAnswer();
|
|
1893
1914
|
if (platform === "WEB") return okResult(await api("/api/bots/web", { method: "POST", body: { name: a.name || undefined } }));
|
|
1894
|
-
if (platform !== "TELEGRAM" && platform !== "MAX") throw new Error("platform: TELEGRAM | MAX | WEB (Instagram подключается только в кабинете).");
|
|
1915
|
+
if (platform !== "TELEGRAM" && platform !== "MAX" && platform !== "VK") throw new Error("platform: TELEGRAM | MAX | VK | WEB (Instagram подключается только в кабинете).");
|
|
1895
1916
|
const token = String(a.token || "").trim();
|
|
1917
|
+
if (platform === "VK") {
|
|
1918
|
+
const groupId = String(a.groupId || "").trim();
|
|
1919
|
+
if (!token || !groupId) {
|
|
1920
|
+
return okResult("Нужны токен сообщества и его id: Управление сообществом → Работа с API → Создать ключ (доступ «Сообщения сообщества») — пришли token и groupId (create_bot {platform:\"VK\", token, groupId}).");
|
|
1921
|
+
}
|
|
1922
|
+
let vkBot;
|
|
1923
|
+
try {
|
|
1924
|
+
vkBot = await api("/api/bots", { method: "POST", body: { token, platform: "VK", groupId } });
|
|
1925
|
+
} catch (e) {
|
|
1926
|
+
if (e.status === 400) throw new Error(`VK-бот не подключён: ${typeof e.data === "string" && e.data ? e.data : "токен или groupId не приняты"}. Проверь токен и id сообщества.`);
|
|
1927
|
+
throw e;
|
|
1928
|
+
}
|
|
1929
|
+
if (a.name && vkBot?.id) {
|
|
1930
|
+
try { vkBot = await api(`/api/bots/${vkBot.id}`, { method: "PATCH", body: { name: a.name } }); }
|
|
1931
|
+
catch (e) { return okResult({ ...vkBot, warning: `Бот подключён, но имя не задано: ${(e.message || "").split("\n")[0]}` }); }
|
|
1932
|
+
}
|
|
1933
|
+
return okResult({ ...vkBot, note: "Дальше включи Callback API в настройках сообщества (Управление → Работа с API → Callback API): URL https://<домен>/vk/hooks/" + vkBot.id + ", секретный ключ — поле vkCallbackSecret в этом же ответе, тип события «Входящее сообщение», затем «Подтвердить сервер»." });
|
|
1934
|
+
}
|
|
1896
1935
|
if (!token) {
|
|
1897
1936
|
return okResult(platform === "MAX"
|
|
1898
1937
|
? "Нужен токен MAX-бота: создай бота у MasterBot в MAX и пришли токен — я подключу его (create_bot {platform:\"MAX\", token})."
|
|
@@ -1912,6 +1951,19 @@ async function handleCall(params) {
|
|
|
1912
1951
|
}
|
|
1913
1952
|
return okResult(bot);
|
|
1914
1953
|
}
|
|
1954
|
+
case "vk_landing_list": return okResult(await api("/api/growth/vk-landings"));
|
|
1955
|
+
case "vk_landing_get": return okResult(await api(`/api/growth/vk-landings/${uuidArg(a.id, "id")}`));
|
|
1956
|
+
case "vk_landing_create": {
|
|
1957
|
+
if (!a.botId || !a.title) throw new Error("botId и title обязательны");
|
|
1958
|
+
return okResult(await api("/api/growth/vk-landings", { method: "POST", body: landingBody(a) }));
|
|
1959
|
+
}
|
|
1960
|
+
case "vk_landing_update": {
|
|
1961
|
+
if (!a.id || !a.botId || !a.title) throw new Error("id, botId и title обязательны");
|
|
1962
|
+
return okResult(await api(`/api/growth/vk-landings/${uuidArg(a.id, "id")}`, { method: "PUT", body: landingBody(a) }));
|
|
1963
|
+
}
|
|
1964
|
+
case "vk_landing_delete":
|
|
1965
|
+
await api(`/api/growth/vk-landings/${uuidArg(a.id, "id")}`, { method: "DELETE" });
|
|
1966
|
+
return okResult({ deleted: true });
|
|
1915
1967
|
case "bot_stop": return okResult(await api(`/api/bots/${a.botId}/stop`, { method: "POST" }));
|
|
1916
1968
|
case "bot_resume": return okResult(await api(`/api/bots/${a.botId}/resume`, { method: "POST" }));
|
|
1917
1969
|
// ---- Подключения ----
|
|
@@ -2174,6 +2226,10 @@ async function handleCall(params) {
|
|
|
2174
2226
|
if (!a.chain || typeof a.chain !== "object") throw new Error("Передай chain — объект PublishChain (см. описание chain_create).");
|
|
2175
2227
|
return okResult(await api("/api/bots/chains", { method: "POST", body: a.chain }));
|
|
2176
2228
|
}
|
|
2229
|
+
case "chain_create_draft": {
|
|
2230
|
+
if (!a.name || typeof a.name !== "string") throw new Error("Передай name — название черновика цепочки.");
|
|
2231
|
+
return okResult(await api("/api/bots/chains/draft", { method: "POST", body: { name: a.name } }));
|
|
2232
|
+
}
|
|
2177
2233
|
case "chain_update": {
|
|
2178
2234
|
const chainId = uuidArg(a.chainId, "chainId");
|
|
2179
2235
|
if (!a.chain || typeof a.chain !== "object") throw new Error("Передай chain — объект PublishChain целиком (сначала прочитай chain_get).");
|