@retensy/mcp 0.28.0 → 0.29.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retensy/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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": {
|
|
@@ -155,6 +155,26 @@ IG-боты не поддерживают команды (`/start`). Вход
|
|
|
155
155
|
- `STOP_AND_ERROR` — `{ "message":"CRM не ответила: {{var.http_status}}" }`. Обрывает прогон и
|
|
156
156
|
помечает его в журнале как ошибочный (`FAILED`), шаг — `ok:false` с этим текстом. Выходов нет,
|
|
157
157
|
ничего подписчику не отправляет. Пустой `message` → «Сценарий остановлен с ошибкой».
|
|
158
|
+
- `DELETE_MESSAGE` — `{ "mode":"PREVIOUS"|"NODE"|"USER_LAST"|"NEXT", "nodeId":"<id узла>", "delay":30, "unit":"SECONDS"|"MINUTES"|"HOURS" }`.
|
|
159
|
+
Удаляет сообщение у подписчика, выход один — `next`. `mode`: `PREVIOUS` — последнее сообщение бота перед этим
|
|
160
|
+
узлом; `NODE` — последнее сообщение узла `nodeId` (узел этого же сценария, который шлёт сообщение:
|
|
161
|
+
`SEND_*`, `ASK_QUESTION`, `AI_REPLY`, `ACTIONS`, оплата); `USER_LAST` — последнее сообщение подписчика
|
|
162
|
+
(только Telegram, в личке); `NEXT` — сообщение, которое отправит следующий отправляющий узел (нужен `delay` > 0).
|
|
163
|
+
`delay`+`unit` — пауза до удаления без остановки сценария (по умолчанию удаляет сразу), меньше 24 часов, допускается
|
|
164
|
+
дробное значение (округляется вверх до секунды).
|
|
165
|
+
**Ограничения.** `NEXT` срабатывает только если следующий узел отправляет сообщение сразу: если между ними `DELAY` или
|
|
166
|
+
`SCHEDULE`, сценарий паркуется, флаг сгорает и ничего не удаляется (валидатор предупреждает `DELETE_NEXT_PARKED`,
|
|
167
|
+
а если отправляющего узла после вообще нет, `DELETE_NEXT_NO_SENDER`). `USER_LAST` работает только в личке Telegram
|
|
168
|
+
(в группах, VK и MAX шаг пропускается с ошибкой в журнале). `NODE`/`PREVIOUS` помнят последнее сообщение узла: если блок
|
|
169
|
+
`ACTIONS` отправил несколько сообщений, удалится только последнее. Отложенные удаления переживают рестарт, остановку
|
|
170
|
+
бота и снятие сценария с публикации.
|
|
171
|
+
Пример «выгнали из канала и удалили сообщение со ссылкой»: `ACTIONS(invite_link_create)` → `ACTIONS(group_kick)` →
|
|
172
|
+
`DELETE_MESSAGE{mode:"NODE", nodeId:<id блока со ссылкой>}`. Сценарий НЕ падает, если удалить нельзя (Telegram даёт
|
|
173
|
+
48 часов на сообщения бота в личке, VK 24 часа, сообщение уже удалено или платформа не умеет): идёт дальше по
|
|
174
|
+
`next`, а шаг помечается ошибкой в журнале и аналитике. Валидатор режет: неизвестный `mode`
|
|
175
|
+
(`DELETE_BAD_MODE`), `NODE` без существующего узла (`DELETE_NODE_MISSING`) или с узлом, который ничего не шлёт
|
|
176
|
+
(`DELETE_NODE_NOT_SENDER`), пауза не число (`DELETE_BAD_DELAY`) или от 24 часов (`DELETE_DELAY_TOO_LONG`),
|
|
177
|
+
`NEXT` без паузы (`DELETE_NEXT_NO_DELAY`). Instagram узел не поддерживает.
|
|
158
178
|
- `ASK_QUESTION` — вопрос со сбором ответа. `{ "promptText":"...","saveTo":"name","inputKind":"TEXT"|"PHOTO"|"DOCUMENT"|"CONTACT"|"LOCATION","validator":"ANY"|"PHONE"|"EMAIL"|"REGEX","regex":"...","retryText":"...","maxAttempts":3 }`. `inputKind` (по умолчанию `TEXT`) — что ждём в ответ (`CONTACT` → телефон: в Telegram показывается кнопка «Поделиться номером», в MAX/Instagram кнопки нет — номер вводится вручную и принимается как телефон на всех платформах; `LOCATION` → `lat,lon`, `PHOTO`/`DOCUMENT` → file_id). Выходы `valid` / `invalid`.
|
|
159
179
|
- `END` — `{}` (конец ветки). **Не добавляй `END`**: ветка и так завершается на узле без исходящих рёбер; явный «конец сценария» бесполезен и убран из палитры редактора. Тип оставлен лишь для совместимости со старыми графами.
|
|
160
180
|
|
|
@@ -483,6 +503,7 @@ node validate.mjs graph.json --platform=INSTAGRAM
|
|
|
483
503
|
| `ACTIONS` | `next`, плюс `error` — если внутри есть действие, которое может упасть (внешний запрос, CRM, Таблицы); `taken` — слот `booking_book` занят |
|
|
484
504
|
| `SWITCH` | `case_<id>`, `default` |
|
|
485
505
|
| `STOP_AND_ERROR` | выходов нет (терминатор) |
|
|
506
|
+
| `DELETE_MESSAGE` | `next` (всегда, даже если удалить не вышло) |
|
|
486
507
|
| `SCHEDULE` | `scheduled`, `past` |
|
|
487
508
|
| `DELAY` | `next`; у `UNTIL` ещё `late` (момент уже прошёл) и `error` (шаблон не разобрался как дата) |
|
|
488
509
|
| `AI_REPLY` (`mode` не задан/`simple`) | `next`, `error` |
|
|
@@ -279,6 +279,50 @@ for (const n of nodes) {
|
|
|
279
279
|
}
|
|
280
280
|
break;
|
|
281
281
|
}
|
|
282
|
+
case "DELETE_MESSAGE": {
|
|
283
|
+
// Зеркало GraphValidator.validateDeleteMessage (Java).
|
|
284
|
+
if (!["NODE", "PREVIOUS", "USER_LAST", "NEXT"].includes(c.mode)) {
|
|
285
|
+
errors.push(`DELETE_BAD_MODE: ${who} — mode ∈ {NODE,PREVIOUS,USER_LAST,NEXT}.`);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
if (c.mode === "NODE") {
|
|
289
|
+
const target = nodes.find((x) => x.id === c.nodeId);
|
|
290
|
+
const SENDERS = ["SEND_MESSAGE", "SEND_PHOTO", "SEND_VIDEO", "SEND_DOCUMENT", "SEND_VOICE", "SEND_VIDEO_NOTE",
|
|
291
|
+
"SEND_MEDIA_GROUP", "ASK_QUESTION", "AI_REPLY", "PAYMENT_LINK", "YOOKASSA_PAYMENT", "ACTIONS"];
|
|
292
|
+
if (!target) errors.push(`DELETE_NODE_MISSING: ${who} — nodeId должен указывать на узел этого сценария.`);
|
|
293
|
+
else if (!SENDERS.includes(target.type)) errors.push(`DELETE_NODE_NOT_SENDER: ${who} — узел nodeId ничего не отправляет.`);
|
|
294
|
+
}
|
|
295
|
+
// Зеркало FlowExecutor.deleteDelaySec: дробная пауза округляется вверх, потолок 24 часа.
|
|
296
|
+
const delay = c.delay === undefined || c.delay === null ? 0 : Number(c.delay);
|
|
297
|
+
const mult = c.unit === "HOURS" ? 3600 : c.unit === "MINUTES" ? 60 : 1;
|
|
298
|
+
const delaySec = Number.isFinite(delay) && delay > 0 ? Math.min(Math.ceil(delay * mult), 86400) : 0;
|
|
299
|
+
if (!Number.isFinite(delay) || delay < 0) errors.push(`DELETE_BAD_DELAY: ${who} — delay должен быть числом не меньше 0.`);
|
|
300
|
+
else if (delaySec >= 86400) errors.push(`DELETE_DELAY_TOO_LONG: ${who} — пауза должна быть меньше 24 часов.`);
|
|
301
|
+
if (c.mode === "NEXT" && delaySec <= 0) errors.push(`DELETE_NEXT_NO_DELAY: ${who} — для mode NEXT нужен delay больше нуля.`);
|
|
302
|
+
if (c.mode === "NEXT") {
|
|
303
|
+
// Флаг «удалить следующее» живёт в одном прогоне: пауза (DELAY/SCHEDULE) до отправки его сжигает.
|
|
304
|
+
const SENDERS_NEXT = ["SEND_MESSAGE", "SEND_PHOTO", "SEND_VIDEO", "SEND_DOCUMENT", "SEND_VOICE", "SEND_VIDEO_NOTE",
|
|
305
|
+
"SEND_MEDIA_GROUP", "ASK_QUESTION", "AI_REPLY", "PAYMENT_LINK", "YOOKASSA_PAYMENT", "ACTIONS"];
|
|
306
|
+
const out = {};
|
|
307
|
+
for (const e of edges) (out[e.sourceNodeId] = out[e.sourceNodeId] || []).push(e.targetNodeId);
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
const queue = [[n.id, false]];
|
|
310
|
+
let senderFound = false, parkedSender = false;
|
|
311
|
+
while (queue.length) {
|
|
312
|
+
const [id, parked] = queue.shift();
|
|
313
|
+
for (const t of out[id] || []) {
|
|
314
|
+
const tn = nodes.find((x) => x.id === t);
|
|
315
|
+
if (!tn || seen.has(`${t}:${parked}`)) continue;
|
|
316
|
+
seen.add(`${t}:${parked}`);
|
|
317
|
+
if (SENDERS_NEXT.includes(tn.type)) { senderFound = true; if (parked) parkedSender = true; }
|
|
318
|
+
else queue.push([t, parked || tn.type === "DELAY" || tn.type === "SCHEDULE"]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (parkedSender) warns.push(`DELETE_NEXT_PARKED: ${who} — между узлом и отправкой есть пауза (DELAY/SCHEDULE): сценарий паркуется, удаление следующего сообщения не сработает.`);
|
|
322
|
+
else if (!senderFound) warns.push(`DELETE_NEXT_NO_SENDER: ${who} — после узла нет блока, который отправляет сообщение.`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
282
326
|
case "STOP_AND_ERROR":
|
|
283
327
|
// message необязателен (рантайм подставит свой текст), но безымянная ошибка в журнале бесполезна.
|
|
284
328
|
if (blank(c.message)) warns.push(`${who}: STOP_AND_ERROR без message — в журнале будет обезличенное «Сценарий остановлен с ошибкой».`);
|