@besales/mcp 1.0.0-beta.7 → 1.0.0-beta.9
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 +41 -6
- package/dist/auth/oauth-client.d.ts +1 -1
- package/dist/auth/oauth-client.js +2 -0
- package/dist/auth/oauth-client.js.map +1 -1
- package/dist/resources/concepts/sandbox.md +8 -1
- package/dist/resources/concepts/triggers.md +1 -0
- package/dist/resources/docs/platforms.md +10 -1
- package/dist/resources/docs/trigger-actions.md +24 -0
- package/dist/resources/workflows/iterate-with-sandbox.md +6 -2
- package/dist/resources/workflows/platform-setup-from-scratch.md +27 -0
- package/dist/schemas/mcp-tools.json +360 -12
- package/dist/tools/definitions/agent-design.d.ts +1 -1
- package/dist/tools/definitions/agent-design.js +13 -7
- package/dist/tools/definitions/agent-design.js.map +1 -1
- package/dist/tools/definitions/broadcast-analytics.js +1 -1
- package/dist/tools/definitions/broadcast-analytics.js.map +1 -1
- package/dist/tools/definitions/broadcast-campaigns.d.ts +29 -0
- package/dist/tools/definitions/broadcast-campaigns.js +107 -0
- package/dist/tools/definitions/broadcast-campaigns.js.map +1 -0
- package/dist/tools/definitions/dialogue-export.js +2 -0
- package/dist/tools/definitions/dialogue-export.js.map +1 -1
- package/dist/tools/definitions/follow-up.d.ts +1 -1
- package/dist/tools/definitions/platform-setup-wave.d.ts +1 -1
- package/dist/tools/definitions/platform-setup.d.ts +11 -1
- package/dist/tools/definitions/telegram-outreach-calendar.d.ts +17 -0
- package/dist/tools/definitions/telegram-outreach-calendar.js +68 -0
- package/dist/tools/definitions/telegram-outreach-calendar.js.map +1 -0
- package/dist/tools/definitions/telegram-personal-profile.d.ts +2 -0
- package/dist/tools/definitions/telegram-personal-profile.js +14 -0
- package/dist/tools/definitions/telegram-personal-profile.js.map +1 -1
- package/dist/tools/definitions/telegram-session-import.d.ts +5 -0
- package/dist/tools/definitions/telegram-session-import.js +33 -0
- package/dist/tools/definitions/telegram-session-import.js.map +1 -0
- package/dist/tools/definitions/trigger-action-item.schema.d.ts +10 -0
- package/dist/tools/definitions/trigger-action-item.schema.js +28 -11
- package/dist/tools/definitions/trigger-action-item.schema.js.map +1 -1
- package/dist/tools/definitions/trigger-upsert.tool.d.ts +1 -0
- package/dist/tools/definitions/trigger-upsert.tool.js +13 -0
- package/dist/tools/definitions/trigger-upsert.tool.js.map +1 -1
- package/dist/tools/definitions.d.ts +48 -6
- package/dist/tools/definitions.js +6 -0
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/registry-mapping.cases.js +133 -93
- package/dist/tools/registry-mapping.cases.js.map +1 -1
- package/dist/tools/registry.js +10 -11
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/result.d.ts +1 -1
- package/dist/tools/result.js +60 -39
- package/dist/tools/result.js.map +1 -1
- package/dist/types/api-contract.gen.d.ts +2220 -216
- package/docs/async-operation-lifecycle.md +13 -2
- package/docs/failure-semantics.md +11 -1
- package/docs/secret-read-boundary.md +7 -0
- package/docs/telegram-personal-management.md +66 -22
- package/docs/telegram-session-import.md +53 -0
- package/package.json +8 -7
- package/scripts/body-idempotency-contract.mjs +95 -0
- package/scripts/generate-followup-session-contract.mjs +14 -7
- package/scripts/generated-output.mjs +9 -0
- package/scripts/sync-agent-model-schemas.mjs +11 -11
- package/scripts/sync-api-contracts.mjs +99 -40
- package/scripts/sync-crm-stage-pool-schema.mjs +6 -7
- package/scripts/sync-route-map.mjs +189 -35
- package/scripts/sync-sandbox-request-key.mjs +40 -0
- package/scripts/sync-telegram-profile-photo.mjs +57 -0
- package/scripts/sync-telegram-session-import.mjs +39 -0
- package/scripts/sync-tool-inventory.mjs +15 -7
- package/scripts/sync-trigger-argument-schema.mjs +31 -14
- package/scripts/sync-trigger-event-schema.mjs +11 -6
- package/scripts/sync-trigger-step-reference-schema.mjs +21 -0
- package/scripts/sync-wave-id-schemas.mjs +7 -6
- package/scripts/telegram-personal-profile-mutants.mjs +59 -18
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Фоновая операция MCP считается полной только тогда, когда у её стартового инструмента есть
|
|
4
4
|
read-only status-инструмент. Стартовый ответ не является доказательством завершения. При сетевом
|
|
5
|
-
обрыве
|
|
6
|
-
`
|
|
5
|
+
обрыве сначала вызывается доступное чтение исхода по исходному ключу. Если для status нужен
|
|
6
|
+
ещё не полученный идентификатор, запуск с `request_key` повторяется с тем же ключом и тем же
|
|
7
|
+
намерением: вернётся та же операция. Запуск без ключа вслепую повторять нельзя.
|
|
7
8
|
|
|
8
9
|
## Канон операций
|
|
9
10
|
|
|
@@ -41,6 +42,16 @@ read-only status-инструмент. Стартовый ответ не явл
|
|
|
41
42
|
`besales_improvement_aggregate_start`) status читает исход по исходному ключу. Исход `UNKNOWN` не
|
|
42
43
|
разрешает новый платный запуск автоматически: сначала выясняется, был ли первый принят.
|
|
43
44
|
|
|
45
|
+
У `besales_icp_import_dialogues` потерянный ответ сначала проверяется через
|
|
46
|
+
`besales_icp_import_start_result` с теми же `icp_id` и `request_key`. Если исход неизвестен
|
|
47
|
+
(`NOT_FOUND_UNKNOWN`), повторяется исходное намерение с тем же ключом.
|
|
48
|
+
|
|
49
|
+
У `besales_sandbox_run_start` чтение исхода по ключу пока не опубликовано в MCP.
|
|
50
|
+
При таймауте или 5xx повторите вызов с тем же `request_key` и тем же намерением: вернётся
|
|
51
|
+
тот же прогон. Только после получения `run_id` проверяйте `besales_sandbox_run_get`.
|
|
52
|
+
Новый осознанный прогон запускайте без `request_key` либо с новым UUID: прежний ключ вернёт
|
|
53
|
+
старый прогон, а при смене намерения — `START_KEY_CONFLICT`.
|
|
54
|
+
|
|
44
55
|
## Синхронные похожие имена
|
|
45
56
|
|
|
46
57
|
`besales_prompt_patch_start` синхронно создаёт draft; его дальнейший жизненный цикл —
|
|
@@ -47,12 +47,22 @@
|
|
|
47
47
|
«операция → проверка» отстала бы от первого же нового асинхронного инструмента, и он остался
|
|
48
48
|
бы без указания ровно там, где цена ошибки максимальна.
|
|
49
49
|
|
|
50
|
-
Пару, которую вывести нельзя (`besales_icp_import_dialogues` → `
|
|
50
|
+
Пару, которую вывести нельзя (`besales_icp_import_dialogues` → `besales_icp_import_start_result`,
|
|
51
51
|
`besales_model_comparison_run_start` → `besales_model_comparison_get`, …), объявляет поле
|
|
52
52
|
`verifyWithToolName` у определения; объявленное перекрывает выведенное. Тесты держат два
|
|
53
53
|
инварианта: объявленное имя существует, и инструмент проверки сам ничего не меняет — иначе
|
|
54
54
|
совет «сверьтесь» вносил бы новую побочку поверх неизвестного исхода.
|
|
55
55
|
|
|
56
|
+
Для запусков с `request_key` HTTP-ошибки возвращают отправленный ключ из разобранного входа
|
|
57
|
+
в тексте и в `error.requestIdentity.request_key`, в том числе UUID, сгенерированный мостом для
|
|
58
|
+
песочницы. Аварийные квитанции при превышении размера, сбое сериализации или постобработки
|
|
59
|
+
сохраняют `request_key` рядом с идентификаторами результата.
|
|
60
|
+
При `OUTCOME_UNKNOWN` инструкция разрешает повторить то же намерение с тем же ключом:
|
|
61
|
+
вернётся та же операция; новый ключ означает новую платную операцию. Для ICP сначала вызывается
|
|
62
|
+
`besales_icp_import_start_result`; если исход неизвестен, допускается повтор с прежним ключом.
|
|
63
|
+
У песочницы без `run_id` повтор с тем же ключом восстанавливает прогон, затем доступен
|
|
64
|
+
`besales_sandbox_run_get`. Исход неизвестен до ответа API, автоматического повтора нет.
|
|
65
|
+
|
|
56
66
|
Если проверки нет ни выведенной, ни объявленной, текст всё равно не советует повтор: он просит
|
|
57
67
|
свериться состоянием на стороне Besales.
|
|
58
68
|
|
|
@@ -72,6 +72,13 @@ Unicode-нормализация не превращает невалидное
|
|
|
72
72
|
|
|
73
73
|
## Полная опись
|
|
74
74
|
|
|
75
|
+
Параметр `stored_file_id` существующего `besales_platform_telegram_personal_profile_bulk`
|
|
76
|
+
учтён отдельно в `secret-read-inventory-additions.json#parameterSurfaces`. Читатель остаётся
|
|
77
|
+
`telegram-personal-profile.ts` (домен `channels-setup`): он возвращает квитанцию задания,
|
|
78
|
+
не байты файла, URL S3 или секреты Telegram. ID файла — метаданные библиотеки; доступ
|
|
79
|
+
повторно проверяет ядро. `photo-only registry response` проверяет настоящий реестр с одним
|
|
80
|
+
фото и секретоносным ответом в обоих режимах конверта. Полный снимок API не переписывается.
|
|
81
|
+
|
|
75
82
|
Канон — `besales-api/test/bridge-secret-read-inventory.json`, перенесённый в снимок
|
|
76
83
|
верификатором. В нём поимённо перечислены все инструменты `mcp-tools.json`, включая
|
|
77
84
|
setup/init/status/rotation, 1С, CRM, внешние API и их ключи, каналы, переменные,
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
только к ядру Besales; пароли, сессии и ключи Telegram не принимаются и не возвращаются.
|
|
5
5
|
Идентификаторы канала и номера берутся из навигации и таблицы номеров.
|
|
6
6
|
|
|
7
|
-
| Инструмент `besales_platform_telegram_personal_…` | Назначение
|
|
8
|
-
|
|
|
9
|
-
| `accounts`
|
|
10
|
-
| `warmup`
|
|
11
|
-
| `warmup_update`
|
|
12
|
-
| `account_warmup`
|
|
13
|
-
| `ramp`
|
|
14
|
-
| `profile_bulk`
|
|
15
|
-
| `profile_job`
|
|
7
|
+
| Инструмент `besales_platform_telegram_personal_…` | Назначение | Право |
|
|
8
|
+
| ------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------- |
|
|
9
|
+
| `accounts` | Таблица номеров, сводка и листание | `read:channels` |
|
|
10
|
+
| `warmup` | Настройки и аналитика прогрева | `read:channels` |
|
|
11
|
+
| `warmup_update` | Частичная правка настроек прогрева | `manage:channels` |
|
|
12
|
+
| `account_warmup` | Включение и ручной отказ отдельного номера | `manage:channels` |
|
|
13
|
+
| `ramp` | Чтение `newAccountRampEnabled` | `read:channels` |
|
|
14
|
+
| `profile_bulk` | Пачечная правка профилей своих номеров: фото, имя, описание, дата рождения, ники | `manage:channels` |
|
|
15
|
+
| `profile_job` | Итог задания правки профилей по номерам | `read:channels` |
|
|
16
16
|
|
|
17
17
|
У инструментов канала обязателен `channel_id`; у `account_warmup` также `account_id`
|
|
18
18
|
и булев `is_warming_up`. `workspace_id` можно не передавать при выбранной рабочей области.
|
|
@@ -54,18 +54,59 @@ Telegram неизвестен, сначала сверь профиль номе
|
|
|
54
54
|
|
|
55
55
|
Поля `profile_bulk` в snake_case, в HTTP уходят camelCase-поля `TelegramPersonalBulkProfileDto`:
|
|
56
56
|
|
|
57
|
-
| Вход
|
|
58
|
-
|
|
|
59
|
-
| `account_ids` или `all:true` | `accountIds` или `all`
|
|
60
|
-
| `first_name`, `last_name`
|
|
61
|
-
| `bio`
|
|
62
|
-
| `birthday`
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
| Вход | В теле ядра | Правило |
|
|
58
|
+
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
59
|
+
| `account_ids` или `all:true` | `accountIds` или `all` | ровно одно; `account_ids` — 1–100 уникальных UUID v4 |
|
|
60
|
+
| `first_name`, `last_name` | `patch.firstName`, `patch.lastName` | до 64 знаков, без нормализации |
|
|
61
|
+
| `bio` | `patch.bio` | до 140 знаков; лимит Telegram по Premium проверяет ферма |
|
|
62
|
+
| `birthday` | `patch.birthday` | `{day, month, year?}`; `null` снимает дату |
|
|
63
|
+
| `stored_file_id` | `patch.avatar` | одно фото библиотеки для всех выбранных номеров, UUID v4; статичный JPEG/PNG/WebP до 5 МиБ |
|
|
64
|
+
| `usernames` | `usernames[{accountId, username}]` | ники по номерам; `account_ids` и `all` не передаются |
|
|
65
|
+
| `username_base` | `usernameBase` | основа ника для номеров из `account_ids` или `all` |
|
|
66
|
+
| `idempotency_key` | заголовок `x-idempotency-key` | обязателен: 1–200 видимых ASCII-знаков без пробелов |
|
|
67
|
+
|
|
68
|
+
`stored_file_id` необязателен: прежняя форма вызова сохранена. Пропуск сохраняет фото;
|
|
69
|
+
`null`, URL и псевдонимы `avatar`/`avatar_file_id` отвергаются до HTTP.
|
|
70
|
+
|
|
71
|
+
### Лицо канала: фото, имя, ник, описание и дата рождения
|
|
72
|
+
|
|
73
|
+
1. В рабочей области канала вызови
|
|
74
|
+
`besales_file_upload_request({name:"Фото канала", file_type:"IMAGE", source:"USER_UPLOAD"})`,
|
|
75
|
+
без `owner_kind`/`owner_id`. Тип укажи явно: по умолчанию загрузчик создаёт DOCUMENT,
|
|
76
|
+
который не подходит для фото профиля. Человек загружает
|
|
77
|
+
фото по полученной ссылке; затем читай `besales_file_upload_status` с `upload_ref`
|
|
78
|
+
до `status:READY` и возьми `stored_file_id`. Файл должен быть доступен в этой области.
|
|
79
|
+
2. Покажи список номеров и новые значения; при смене ников получи ответ «делать?».
|
|
80
|
+
Вызови `besales_platform_telegram_personal_profile_bulk` одним запросом:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"channel_id": "23000000-0000-4000-8000-000000000001",
|
|
85
|
+
"all": true,
|
|
86
|
+
"stored_file_id": "26000000-0000-4000-8000-000000000001",
|
|
87
|
+
"first_name": "Анна",
|
|
88
|
+
"username_base": "anna_sales",
|
|
89
|
+
"bio": "Помогаю с заказами",
|
|
90
|
+
"birthday": { "day": 1, "month": 2 },
|
|
91
|
+
"idempotency_key": "channel-face-1"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Для части номеров замени `all:true` на `account_ids:[…]`. Одно фото уйдёт всем
|
|
96
|
+
целям того же задания. Для точных разных ников используй `usernames` вместо
|
|
97
|
+
`all`/`account_ids`/`username_base`.
|
|
98
|
+
|
|
99
|
+
3. По `job_id` читай `besales_platform_telegram_personal_profile_job` до `COMPLETED`;
|
|
100
|
+
проверь каждый номер и `outcome.fields.avatar`, а также остальные поля.
|
|
101
|
+
`PENDING` подтверждает только принятие задания.
|
|
102
|
+
|
|
103
|
+
404 `FILE_NOT_FOUND` — загрузи фото в область канала; 400 `TELEGRAM_AVATAR_INVALID`
|
|
104
|
+
или 413 `TELEGRAM_AVATAR_TOO_LARGE` — выбери корректное статичное фото до 5 МиБ;
|
|
105
|
+
409 `TELEGRAM_AVATAR_CHANGED` — выбери файл заново. Изменённое намерение требует нового
|
|
106
|
+
ключа; сетевой повтор прежнего вызова — тот же ключ и те же аргументы.
|
|
107
|
+
`PROFILE_PROVIDER_OUTCOME_UNKNOWN` требует сверки профиля, а не слепого повтора.
|
|
108
|
+
Файл чужой области закрыт 404 до фермы. Мост передаёт ID, не читает байты или секреты
|
|
109
|
+
Telegram и не возвращает подписанную ссылку S3.
|
|
69
110
|
|
|
70
111
|
Пропуск поля сохраняет прежнее значение; пустые строки и `null` передаются буквально, а
|
|
71
112
|
ложное или пустое значение выбора (`all:false`, `account_ids:[]`, `usernames:[]`) отвергается до HTTP.
|
|
@@ -97,7 +138,10 @@ Telegram неизвестен, сначала сверь профиль номе
|
|
|
97
138
|
`telegram-personal-profile.spec.ts`. `scripts/telegram-personal-confirmation-mutants.mjs`
|
|
98
139
|
проверяет, что снятие обязательного согласия в Zod или опубликованной схеме ломает именованное
|
|
99
140
|
утверждение теста. `scripts/telegram-personal-profile-mutants.mjs` делает то же для запрета
|
|
100
|
-
двух режимов ников сразу (в Zod и в опубликованной схеме)
|
|
141
|
+
двух режимов ников сразу (в Zod и в опубликованной схеме), имени заголовка ключа
|
|
142
|
+
и передачи фото в patch. Схему и варианты карты выводит
|
|
143
|
+
`node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-telegram-profile-photo.mjs`;
|
|
144
|
+
опись пересчитывает `scripts/sync-tool-inventory.mjs`. Оба поддерживают `--check`.
|
|
101
145
|
|
|
102
146
|
## Прокси личного Telegram
|
|
103
147
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Импорт партии номеров Telegram
|
|
2
|
+
|
|
3
|
+
`besales_platform_telegram_personal_session_import_link` выдаёт человеку ссылку на уже
|
|
4
|
+
существующую защищённую страницу подключения Telegram. Ввод: `channel_id` и необязательный
|
|
5
|
+
`workspace_id` (иначе используется выбранное пространство). Требуется `manage:channels`.
|
|
6
|
+
Профиль `platform`; профиль `readonly` этот инструмент не включает.
|
|
7
|
+
|
|
8
|
+
Человек входит в приложение, выбирает импорт до 50 файлов и задаёт прокси для каждого файла
|
|
9
|
+
или автоназначение. Содержимое файлов, манифест, ключи приложения и пароли остаются в браузере.
|
|
10
|
+
MCP принимает только идентификаторы; любые дополнительные поля отвергаются до HTTP.
|
|
11
|
+
Ссылка живёт 15 минут. Заказ ссылки сам по себе номера не добавляет.
|
|
12
|
+
|
|
13
|
+
## Маршрут и ответ
|
|
14
|
+
|
|
15
|
+
Используется существующий `POST /workspaces/:workspaceId/telegram-personal-setup-sessions`
|
|
16
|
+
с телом `{channelId}`. Новый маршрут ядра и новая страница не создаются.
|
|
17
|
+
Ответ моста перечисляет только `setup_page_url`, `expires_at`, `operation_id: null`,
|
|
18
|
+
`import_status_available: false` и инструкцию следующего шага. Сырой `setup_ref`, состояние
|
|
19
|
+
мастера и неизвестные поля ответа ядра отбрасываются до общей границы сериализации MCP.
|
|
20
|
+
|
|
21
|
+
Безопасного идентификатора операции импорта этот init пока не возвращает. Пофайловый статус
|
|
22
|
+
`GET /channels/:channelId/telegram-personal/session-imports/:jobId` разрешён только вошедшему
|
|
23
|
+
человеку в браузере. Мост не ослабляет этот запрет и не выдаёт `setup_ref` за идентификатор.
|
|
24
|
+
Результат каждого файла проверяется на странице; `besales_platform_telegram_personal_accounts`
|
|
25
|
+
помогает сверить принятые номера, но не объясняет отклонение каждого файла.
|
|
26
|
+
|
|
27
|
+
## Список прокси
|
|
28
|
+
|
|
29
|
+
Уже доступны `besales_platform_telegram_personal_proxy_import_link` / `_status` для канала
|
|
30
|
+
и `besales_platform_telegram_personal_proxy_stock_import_link` / `_status` для общего запаса.
|
|
31
|
+
Они возвращают настоящий `session_id`, ссылку и срок. Статусы содержат только состояние и
|
|
32
|
+
счётчики; причины по строкам остаются на защищённой странице. Пароли и строки SOCKS5
|
|
33
|
+
нельзя присылать инструментам и нельзя копировать из страницы в чат.
|
|
34
|
+
|
|
35
|
+
## Продолжение
|
|
36
|
+
|
|
37
|
+
Встроенная справка `besales://workflows/platform-setup-from-scratch`, раздел
|
|
38
|
+
«Подготовить партию номеров Telegram», ведёт по импорту, профилю, фото, Premium и кампании.
|
|
39
|
+
Для фото и Premium отдельных инструментов в текущем пакете нет; эти шаги выполняются
|
|
40
|
+
в интерфейсе приложения. Запуск кампании остаётся отдельным намерением человека.
|
|
41
|
+
|
|
42
|
+
## Проверки и генерация
|
|
43
|
+
|
|
44
|
+
`scripts/sync-telegram-session-import.mjs` выводит опубликованную схему и дополнение описи
|
|
45
|
+
чтений из исполняемого определения. Затем `scripts/sync-tool-inventory.mjs` пересчитывает
|
|
46
|
+
инвентарь, а `scripts/sync-route-map.mjs` сверяет маршрут с закреплённым контрактом ядра.
|
|
47
|
+
Каждый поддерживает `--check`; для TypeScript-импортов используется существующий
|
|
48
|
+
`src/utils/setup-sessions-loader.mjs`.
|
|
49
|
+
|
|
50
|
+
`telegram-session-import.spec.ts` проверяет опубликованную схему, HTTP, запрет секретных
|
|
51
|
+
полей, проекцию ответа в обоих форматах MCP, аннотации и отсутствие автоматического повтора
|
|
52
|
+
после отказов. Общие проверки описи, аннотаций, размеров ответов и ссылок справки также
|
|
53
|
+
включают новый инструмент.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@besales/mcp",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "Model Context Protocol server for Besales",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/be-sales/mcp#readme",
|
|
@@ -57,15 +57,16 @@
|
|
|
57
57
|
"test": "vitest run",
|
|
58
58
|
"lint:fix": "eslint src bin scripts eslint.config.js --fix",
|
|
59
59
|
"lint:errors": "eslint src bin scripts eslint.config.js",
|
|
60
|
-
"types:gen:api": "node scripts/sync-api-contracts.mjs --types-only",
|
|
60
|
+
"types:gen:api": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-api-contracts.mjs --types-only",
|
|
61
61
|
"publish:mcp-registry": "node scripts/publish-mcp-registry.mjs",
|
|
62
|
-
"schemas:sync:agent-models": "node scripts/sync-agent-model-schemas.mjs",
|
|
63
|
-
"schemas:sync:trigger-arguments": "node scripts/sync-trigger-argument-schema.mjs",
|
|
64
|
-
"schemas:sync:wave-ids": "node scripts/sync-wave-id-schemas.mjs",
|
|
65
|
-
"schemas:sync:crm-stage-pool": "node scripts/sync-crm-stage-pool-schema.mjs",
|
|
62
|
+
"schemas:sync:agent-models": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-agent-model-schemas.mjs",
|
|
63
|
+
"schemas:sync:trigger-arguments": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-trigger-argument-schema.mjs",
|
|
64
|
+
"schemas:sync:wave-ids": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-wave-id-schemas.mjs",
|
|
65
|
+
"schemas:sync:crm-stage-pool": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-crm-stage-pool-schema.mjs",
|
|
66
66
|
"typecheck": "tsc --noEmit",
|
|
67
67
|
"lint": "eslint src bin scripts eslint.config.js",
|
|
68
|
-
"contracts:sync:api": "node scripts/sync-api-contracts.mjs"
|
|
68
|
+
"contracts:sync:api": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-api-contracts.mjs",
|
|
69
|
+
"contracts:check:api": "node --loader ./src/utils/setup-sessions-loader.mjs scripts/sync-api-contracts.mjs --check"
|
|
69
70
|
},
|
|
70
71
|
"engines": {
|
|
71
72
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
/** Читает тела операций снимка, включая inline-типы и ссылки на operations/components. */
|
|
4
|
+
export function bodyIdempotencyOperations(source) {
|
|
5
|
+
const file = ts.createSourceFile('api-contract.ts', source, ts.ScriptTarget.Latest, true);
|
|
6
|
+
if (file.parseDiagnostics.length) throw new Error('API_CONTRACT_PARSE_FAILED');
|
|
7
|
+
const declarations = new Map(
|
|
8
|
+
file.statements
|
|
9
|
+
.filter((node) => ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node))
|
|
10
|
+
.map((node) => [node.name.text, node]),
|
|
11
|
+
);
|
|
12
|
+
function members(node, seen = new Set()) {
|
|
13
|
+
if (!node) throw new Error('API_CONTRACT_TYPE_MISSING');
|
|
14
|
+
if (seen.has(node)) throw new Error('API_CONTRACT_TYPE_CYCLE');
|
|
15
|
+
const next = new Set([...seen, node]);
|
|
16
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
17
|
+
if (
|
|
18
|
+
node.typeName.getText(file) === 'Record' &&
|
|
19
|
+
node.typeArguments?.[0]?.kind === ts.SyntaxKind.StringKeyword &&
|
|
20
|
+
node.typeArguments?.[1]?.kind === ts.SyntaxKind.NeverKeyword
|
|
21
|
+
)
|
|
22
|
+
return [];
|
|
23
|
+
const declaration = declarations.get(node.typeName.getText(file));
|
|
24
|
+
if (!declaration) throw new Error('API_CONTRACT_REFERENCE_MISSING: ' + node.getText(file));
|
|
25
|
+
return members(declaration, next);
|
|
26
|
+
}
|
|
27
|
+
if (ts.isTypeAliasDeclaration(node) || ts.isParenthesizedTypeNode(node))
|
|
28
|
+
return members(node.type, next);
|
|
29
|
+
if (ts.isIndexedAccessTypeNode(node)) {
|
|
30
|
+
if (!ts.isLiteralTypeNode(node.indexType)) throw new Error('API_CONTRACT_INDEX_UNSUPPORTED');
|
|
31
|
+
const field = members(node.objectType, next).find(
|
|
32
|
+
(one) => one.name?.text === node.indexType.literal.text,
|
|
33
|
+
);
|
|
34
|
+
if (!field) throw new Error('API_CONTRACT_REFERENCE_MISSING: ' + node.getText(file));
|
|
35
|
+
return members(field.type, next);
|
|
36
|
+
}
|
|
37
|
+
if (ts.isUnionTypeNode(node) || ts.isIntersectionTypeNode(node))
|
|
38
|
+
return node.types.flatMap((one) => members(one, next));
|
|
39
|
+
if (ts.isTypeLiteralNode(node) || ts.isInterfaceDeclaration(node)) return [...node.members];
|
|
40
|
+
// Основание indexed access — имя интерфейса, а не ссылка TypeReference.
|
|
41
|
+
if (ts.isIdentifier(node)) return members(declarations.get(node.text), next);
|
|
42
|
+
if (
|
|
43
|
+
ts.isArrayTypeNode(node) ||
|
|
44
|
+
ts.isLiteralTypeNode(node) ||
|
|
45
|
+
(node.kind >= ts.SyntaxKind.FirstKeyword && node.kind <= ts.SyntaxKind.LastKeyword)
|
|
46
|
+
)
|
|
47
|
+
return [];
|
|
48
|
+
throw new Error('API_CONTRACT_TYPE_UNSUPPORTED: ' + node.getText(file));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const paths = declarations.get('paths');
|
|
52
|
+
if (!paths?.members?.length) throw new Error('API_CONTRACT_PATHS_MISSING');
|
|
53
|
+
const operations = [];
|
|
54
|
+
for (const route of paths.members) {
|
|
55
|
+
for (const method of members(route.type)) {
|
|
56
|
+
if (!/^(get|post|put|patch|delete|head|options|trace)$/u.test(method.name?.text ?? ''))
|
|
57
|
+
continue;
|
|
58
|
+
const body = members(method.type).find((field) => field.name?.text === 'requestBody');
|
|
59
|
+
if (!body || body.type.kind === ts.SyntaxKind.NeverKeyword) continue;
|
|
60
|
+
const content = members(body.type).find((field) => field.name?.text === 'content');
|
|
61
|
+
if (!content) throw new Error('API_CONTRACT_BODY_CONTENT_MISSING');
|
|
62
|
+
for (const media of members(content.type)) {
|
|
63
|
+
for (const field of members(media.type)) {
|
|
64
|
+
const key = field.name?.text;
|
|
65
|
+
const description = field.getFullText(file);
|
|
66
|
+
if (
|
|
67
|
+
!/^(requestKey|idempotencyKey)$/u.test(key ?? '') &&
|
|
68
|
+
!(
|
|
69
|
+
/(?:key|token|Id)$/iu.test(key ?? '') &&
|
|
70
|
+
/@description (?:Неизменный |Обязательный |Сохранённый )?(?:Ключ (?:идемпот|намерения|повтора|операции)|UUID идемпот)/iu.test(
|
|
71
|
+
description,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
continue;
|
|
76
|
+
operations.push({
|
|
77
|
+
method: method.name.text.toUpperCase(),
|
|
78
|
+
path: route.name.text,
|
|
79
|
+
key,
|
|
80
|
+
required: !body.questionToken && !field.questionToken,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return operations;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function contractRoute(entry) {
|
|
90
|
+
return `${entry.expected.method} /api/v2${entry.expected.pathTemplate.replace(/:([A-Za-z0-9_]+)/gu, '{$1}')}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function operationRoute(operation) {
|
|
94
|
+
return `${operation.method} ${operation.path}`;
|
|
95
|
+
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeGeneratedFile } from './generated-output.mjs';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { z } from 'zod';
|
|
3
|
-
import { reactivationConfigSchema } from '../
|
|
4
|
+
import { reactivationConfigSchema } from '../src/tools/definitions/follow-up-config.js';
|
|
4
5
|
|
|
5
6
|
// Только добавочные поля: остальные ограничения отгруженной поверхности остаются дословными.
|
|
6
7
|
const file = new URL('../src/schemas/mcp-tools.json', import.meta.url);
|
|
7
8
|
const registry = JSON.parse(readFileSync(file, 'utf8'));
|
|
8
|
-
const tool = registry.tools.find(item => item.name === 'besales_followup_sequence_upsert');
|
|
9
|
+
const tool = registry.tools.find((item) => item.name === 'besales_followup_sequence_upsert');
|
|
9
10
|
if (!tool?.inputSchema?.properties?.config?.properties) throw new Error('Нет конфигурации цепочки');
|
|
10
|
-
const derived = z.toJSONSchema(reactivationConfigSchema, {
|
|
11
|
+
const derived = z.toJSONSchema(reactivationConfigSchema, {
|
|
12
|
+
io: 'input',
|
|
13
|
+
unrepresentable: 'any',
|
|
14
|
+
});
|
|
11
15
|
for (const field of ['sessionTimeVariable', 'sessionDayOffsets', 'sessionDurationMinutes']) {
|
|
12
|
-
if (!derived.properties[field]) throw new Error('Не объявлено поле '+field);
|
|
16
|
+
if (!derived.properties[field]) throw new Error('Не объявлено поле ' + field);
|
|
13
17
|
tool.inputSchema.properties.config.properties[field] = derived.properties[field];
|
|
14
18
|
}
|
|
15
|
-
tool.description = tool.description.replace(
|
|
16
|
-
|
|
19
|
+
tool.description = tool.description.replace(
|
|
20
|
+
'непустые dealFields, серия эфиров и неисполняемые виды шагов',
|
|
21
|
+
'непустые dealFields и неисполняемые виды шагов',
|
|
22
|
+
);
|
|
23
|
+
writeGeneratedFile(file, JSON.stringify(registry, null, 2) + '\n');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
// Один режим проверки для узких генераторов: проверка никогда не переписывает вход.
|
|
4
|
+
export function writeGeneratedFile(file, output) {
|
|
5
|
+
if (process.argv.includes('--check')) {
|
|
6
|
+
if (readFileSync(file, 'utf8') !== output)
|
|
7
|
+
throw new Error('GENERATED_CONTRACT_DRIFT: ' + String(file));
|
|
8
|
+
} else writeFileSync(file, output);
|
|
9
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { writeGeneratedFile } from './generated-output.mjs';
|
|
1
2
|
// Выпуск схемы нового каталога и подсказок из исполняемых определений, без копии списка моделей.
|
|
2
|
-
import {
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { z } from 'zod';
|
|
4
|
-
import { agentModelTools } from '../
|
|
5
|
-
import { agentSettingsTools } from '../
|
|
5
|
+
import { agentModelTools } from '../src/tools/definitions/agent-models.js';
|
|
6
|
+
import { agentSettingsTools } from '../src/tools/definitions/agent-settings.js';
|
|
6
7
|
|
|
7
|
-
import { renderToolInventoryMarkdown, toolInventoryCounts } from '../
|
|
8
|
+
import { renderToolInventoryMarkdown, toolInventoryCounts } from '../src/tools/inventory.js';
|
|
8
9
|
|
|
9
10
|
const target = new URL('../src/schemas/mcp-tools.json', import.meta.url);
|
|
10
11
|
const registry = JSON.parse(readFileSync(target, 'utf8'));
|
|
@@ -36,7 +37,7 @@ registry.summary.total_tools = names.length;
|
|
|
36
37
|
registry.summary.tools_by_category = toolInventoryCounts(names);
|
|
37
38
|
const readmePath = new URL('../README.md', import.meta.url);
|
|
38
39
|
const readme = readFileSync(readmePath, 'utf8');
|
|
39
|
-
|
|
40
|
+
writeGeneratedFile(
|
|
40
41
|
readmePath,
|
|
41
42
|
readme.replace(
|
|
42
43
|
/(<!-- BEGIN GENERATED TOOL INVENTORY -->)[\s\S]*?(<!-- END GENERATED TOOL INVENTORY -->)/u,
|
|
@@ -56,17 +57,16 @@ const route = {
|
|
|
56
57
|
canonicalArgs: args,
|
|
57
58
|
expected: {
|
|
58
59
|
method: request.method,
|
|
59
|
-
pathTemplate: url.pathname.replace(args.workspace_id, ':workspaceId'),
|
|
60
60
|
path: url.pathname,
|
|
61
|
+
pathTemplate: url.pathname.replace(args.workspace_id, ':workspaceId'),
|
|
61
62
|
query: Object.fromEntries(url.searchParams),
|
|
62
63
|
},
|
|
63
64
|
};
|
|
64
65
|
const previousRoute = routeMap.findIndex((item) => item.tool === definition.name);
|
|
65
66
|
if (previousRoute === -1) routeMap.push(route);
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
// Прочие поля строки (backendTarget) ставит sync-api-contracts — не стирать их, иначе выпуски по очереди меняют файл.
|
|
68
|
+
else routeMap[previousRoute] = { ...routeMap[previousRoute], ...route };
|
|
69
|
+
writeGeneratedFile(routeMapPath, JSON.stringify(routeMap, null, 2) + '\n');
|
|
68
70
|
const serialized = JSON.stringify(registry, null, 2) + '\n';
|
|
69
|
-
|
|
70
|
-
mkdirSync(new URL('../dist/schemas/', import.meta.url), { recursive: true });
|
|
71
|
-
writeFileSync(new URL('../dist/schemas/mcp-tools.json', import.meta.url), serialized);
|
|
71
|
+
writeGeneratedFile(target, serialized);
|
|
72
72
|
console.log('Схемы каталога моделей агента и подсказки выпущены');
|