@besales/mcp 0.33.0 → 0.37.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 +45 -12
- package/dist/http/api-client.d.ts +2 -0
- package/dist/http/api-client.js +55 -0
- package/dist/http/api-client.js.map +1 -1
- package/dist/resources/docs/agent-behavior.md +7 -5
- package/dist/resources/docs/prompt-anatomy.md +4 -4
- package/dist/resources/docs/prompt-principles.md +22 -3
- package/dist/schemas/mcp-tools.json +1369 -98
- package/dist/skills/besales-dialogue-audit/SKILL.md +6 -0
- package/dist/tools/definitions/broadcast-analytics.d.ts +28 -7
- package/dist/tools/definitions/broadcast-analytics.js +399 -33
- package/dist/tools/definitions/broadcast-analytics.js.map +1 -1
- package/dist/tools/definitions/broadcast-mailing-variants.d.ts +19 -0
- package/dist/tools/definitions/broadcast-mailing-variants.js +107 -0
- package/dist/tools/definitions/broadcast-mailing-variants.js.map +1 -0
- package/dist/tools/definitions/dialogue-audit.d.ts +23 -0
- package/dist/tools/definitions/dialogue-audit.js +155 -0
- package/dist/tools/definitions/dialogue-audit.js.map +1 -0
- package/dist/tools/definitions/follow-up-setup.d.ts +27 -0
- package/dist/tools/definitions/follow-up-setup.js +76 -0
- package/dist/tools/definitions/follow-up-setup.js.map +1 -0
- package/dist/tools/definitions/follow-up.d.ts +19 -0
- package/dist/tools/definitions/follow-up.js +81 -3
- package/dist/tools/definitions/follow-up.js.map +1 -1
- package/dist/tools/definitions/getcourse-wave.d.ts +147 -0
- package/dist/tools/definitions/getcourse-wave.js +505 -0
- package/dist/tools/definitions/getcourse-wave.js.map +1 -0
- package/dist/tools/definitions/platform-setup.d.ts +92 -20
- package/dist/tools/definitions/platform-setup.js +132 -537
- package/dist/tools/definitions/platform-setup.js.map +1 -1
- package/dist/tools/definitions/scheduled-tasks.d.ts +9 -0
- package/dist/tools/definitions/scheduled-tasks.js +49 -2
- package/dist/tools/definitions/scheduled-tasks.js.map +1 -1
- package/dist/tools/definitions/session.js +6 -4
- package/dist/tools/definitions/session.js.map +1 -1
- package/dist/tools/definitions/shared.d.ts +75 -13
- package/dist/tools/definitions/shared.js +120 -7
- package/dist/tools/definitions/shared.js.map +1 -1
- package/dist/tools/definitions.d.ts +171 -26
- package/dist/tools/definitions.js +4 -0
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/registry-mapping-cases-core.d.ts +38 -0
- package/dist/tools/registry-mapping-cases-core.js +700 -0
- package/dist/tools/registry-mapping-cases-core.js.map +1 -0
- package/dist/tools/registry-mapping-cases-platform.d.ts +2 -0
- package/dist/tools/registry-mapping-cases-platform.js +954 -0
- package/dist/tools/registry-mapping-cases-platform.js.map +1 -0
- package/dist/tools/registry.js +12 -6
- package/dist/tools/registry.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { followUpSetupTools } from './follow-up-setup.js';
|
|
3
|
+
import { getCourseWaveTools } from './getcourse-wave.js';
|
|
4
|
+
import { agentOutputConfigSchema, agentRuntimeConfigSchema, agentScopeSchema, agentToolPatchSchema, anyObjectSchema, behaviorErrorHandlingSchema, behaviorGreetingSchema, behaviorMediaHandlingSchema, behaviorMessageFilterSchema, behaviorMessageSplittingSchema, behaviorOperatorScheduleSchema, behaviorPrivacySchema, behaviorTypingBehaviorSchema, behaviorUrlHandlingSchema, buildListQuery, compactObject, defineTool, feedbackSheetRowSchema, mapBehaviorErrorHandlingInput, mapBehaviorGreetingInput, mapBehaviorMediaHandlingInput, mapBehaviorOperatorScheduleInput, mapBehaviorTypingInput, mapBehaviorUrlHandlingInput, mapOutputConfigInput, mapPipelineAutoFinishInput, mapPipelineCrmBehaviorInput, mapPlatformAccessControlInput, mapPlatformBitrix24BotSettingsInput, mapPlatformCrmBehaviorInput, mapPlatformGeneralSettingsInput, mapPlatformGetcourseSettingsInput, mapPlatformInstagramCommentInput, mapPlatformSalebotSettingsInput, mapPlatformSmsgorodSettingsInput, mapPlatformVariablesConfigInput, mapPlatformWidgetMarketingInput, mapRuntimeConfigInput, modelLaneSchema, pipelineAutoFinishSchema, pipelineCrmBehaviorSchema, platformAccessControlSchema, platformBitrix24BotSettingsSchema, platformCrmBehaviorSchema, platformGeneralSettingsSchema, platformGetcourseSettingsSchema, platformInstagramCommentSchema, platformSalebotSettingsSchema, platformSetupConfirmationSchema, platformSmsgorodSettingsSchema, platformVariablesConfigSchema, mapPromptPatchOperationInput, platformWidgetMarketingSchema, PROMPT_BATCH_IDEMPOTENCY_KEY_MAX_LENGTH, PROMPT_BATCH_MAX_AGENTS, PROMPT_DIFF_MAX_LINES_LIMIT, promptPatchOperationSchema, routingRuleConditionSchema, routingRuleLeadStatusSchema, routingStrategySchema, setupConfirmation, triggerScopeSchema, uuidSchema, withInstructions, } from './shared.js';
|
|
4
5
|
export const platformSetupTools = [
|
|
5
6
|
defineTool({
|
|
6
7
|
name: 'besales_workspace_overview',
|
|
@@ -79,484 +80,7 @@ export const platformSetupTools = [
|
|
|
79
80
|
'Используй department.id как target_department_id в besales_platform_settings_update (getcourse_settings) для department-routing.',
|
|
80
81
|
]),
|
|
81
82
|
}),
|
|
82
|
-
|
|
83
|
-
name: 'besales_getcourse_wave_import',
|
|
84
|
-
requiredFields: ['platform_id', 'crm_id', 'channels', 'contacts'],
|
|
85
|
-
defaultWorkspaceId: true,
|
|
86
|
-
schema: z.object({
|
|
87
|
-
workspace_id: uuidSchema.optional(),
|
|
88
|
-
platform_id: uuidSchema,
|
|
89
|
-
crm_id: uuidSchema,
|
|
90
|
-
launch_type: z.enum(['status', 'board', 'group']).optional(),
|
|
91
|
-
channels: z.array(z.string()).min(1),
|
|
92
|
-
funnel: z.string().max(200).optional(),
|
|
93
|
-
segment: z.string().max(200).optional(),
|
|
94
|
-
cohort_label: z.string().max(200).optional(),
|
|
95
|
-
message_variant: z.string().max(120).optional(),
|
|
96
|
-
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
97
|
-
product_code: z
|
|
98
|
-
.string()
|
|
99
|
-
.trim()
|
|
100
|
-
.max(100)
|
|
101
|
-
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
102
|
-
.optional(),
|
|
103
|
-
product_name: z.string().max(200).optional(),
|
|
104
|
-
deal_fields: z
|
|
105
|
-
.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
106
|
-
.optional(),
|
|
107
|
-
pipeline_id: z.number().int().positive().optional(),
|
|
108
|
-
status_id: z.number().int().positive().optional(),
|
|
109
|
-
group_id: z.number().int().positive().optional(),
|
|
110
|
-
contacts: z
|
|
111
|
-
.array(z.object({
|
|
112
|
-
contact_id: z.string(),
|
|
113
|
-
deal_id: z.string().optional(),
|
|
114
|
-
transport: z.string().optional(),
|
|
115
|
-
dialog_id: z.string().optional(),
|
|
116
|
-
lead_last_active_at: z.string().datetime({ offset: true }).optional(),
|
|
117
|
-
segment_entered_at: z.string().datetime({ offset: true }).optional(),
|
|
118
|
-
}))
|
|
119
|
-
.min(1)
|
|
120
|
-
.max(500),
|
|
121
|
-
confirm: z.boolean().optional(),
|
|
122
|
-
check_messenger_presence: z.boolean().optional(),
|
|
123
|
-
dedup_window_days: z.number().int().min(0).max(365).optional(),
|
|
124
|
-
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
125
|
-
preflight: z
|
|
126
|
-
.object({
|
|
127
|
-
enabled: z.boolean().optional(),
|
|
128
|
-
product_title: z.string().max(500).optional(),
|
|
129
|
-
product_match_key: z.string().max(500).optional(),
|
|
130
|
-
offer_id: z.number().int().positive().optional(),
|
|
131
|
-
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
132
|
-
})
|
|
133
|
-
.optional(),
|
|
134
|
-
}),
|
|
135
|
-
createRequest: (input) => ({
|
|
136
|
-
method: 'POST',
|
|
137
|
-
path: `/workspaces/${input.workspace_id}/getcourse-waves/import`,
|
|
138
|
-
body: compactObject({
|
|
139
|
-
platformId: input.platform_id,
|
|
140
|
-
crmId: input.crm_id,
|
|
141
|
-
launchType: input.launch_type,
|
|
142
|
-
channels: input.channels,
|
|
143
|
-
funnel: input.funnel,
|
|
144
|
-
segment: input.segment,
|
|
145
|
-
cohortLabel: input.cohort_label,
|
|
146
|
-
messageVariant: input.message_variant,
|
|
147
|
-
foundCount: input.found_count,
|
|
148
|
-
productCode: input.product_code,
|
|
149
|
-
productName: input.product_name,
|
|
150
|
-
dealFields: input.deal_fields,
|
|
151
|
-
pipelineId: input.pipeline_id,
|
|
152
|
-
statusId: input.status_id,
|
|
153
|
-
groupId: input.group_id,
|
|
154
|
-
dedupWindowDays: input.dedup_window_days,
|
|
155
|
-
dedupScope: input.dedup_scope,
|
|
156
|
-
contacts: input.contacts?.map((c) => compactObject({
|
|
157
|
-
contactId: c.contact_id,
|
|
158
|
-
dealId: c.deal_id,
|
|
159
|
-
transport: c.transport,
|
|
160
|
-
dialogId: c.dialog_id,
|
|
161
|
-
leadLastActiveAt: c.lead_last_active_at,
|
|
162
|
-
segmentEnteredAt: c.segment_entered_at,
|
|
163
|
-
})),
|
|
164
|
-
confirm: input.confirm,
|
|
165
|
-
checkMessengerPresence: input.check_messenger_presence,
|
|
166
|
-
preflight: input.preflight
|
|
167
|
-
? compactObject({
|
|
168
|
-
enabled: input.preflight.enabled,
|
|
169
|
-
productTitle: input.preflight.product_title,
|
|
170
|
-
productMatchKey: input.preflight.product_match_key,
|
|
171
|
-
offerId: input.preflight.offer_id,
|
|
172
|
-
otherProductActiveDealMode: input.preflight.other_product_active_deal_mode,
|
|
173
|
-
})
|
|
174
|
-
: undefined,
|
|
175
|
-
}),
|
|
176
|
-
}),
|
|
177
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
178
|
-
'Запуск outbound-волны GetCourse: Шаг 1 (импорт контактов + пин сделок) → Шаг 2 (отправка). По умолчанию launch_type=status (простановка полей → «Процесс» GetCourse шлёт welcome).',
|
|
179
|
-
'БЕЗ confirm=true — DRY-RUN: вернётся summary (totalContacts/withDeal/channels/dealFields/sample), НИЧЕГО не отправляется. Покажи summary пользователю и запускай реально только с confirm=true.',
|
|
180
|
-
'channels — значения GetCourseTransport: "VK" | "Telegram" | "MAX" | "SMS" | "Email" | "Site" (для аналитики «Канал волны»).',
|
|
181
|
-
'funnel — канонический ключ/название БИЗНЕС-воронки. Не передавай сюда имя файла, batch/resend или дату запуска. cohort_label — человекочитаемое имя конкретного запуска волны.',
|
|
182
|
-
'contacts[].segment_entered_at — точный ISO-момент попадания контакта в сегмент. contacts[].lead_last_active_at — только legacy/последняя активность; не используй её как замену segment_entered_at.',
|
|
183
|
-
'launch_type=status требует deal_fields = { "<числовой fieldId GetCourse>": значение } (одинаковые на всю волну). board требует pipeline_id+status_id, group — group_id.',
|
|
184
|
-
'preflight (default enabled=true) — pre-send гейт перед каждой строкой Шага 2: блокирует контакт, если уже есть активная/оплаченная сделка по этому продукту или проверка GetCourse упала. Передавай product_title/product_match_key или funnel; если контекста продукта нет вообще, backend отклонит запуск до создания job.',
|
|
185
|
-
'В ответе confirm-запуска — waveJobId. Опрашивай его через besales_getcourse_wave_status до status=COMPLETED (Шаг 2 идёт медленно, с паузами).',
|
|
186
|
-
'check_messenger_presence (default TRUE): на Шаге 1 отсеивает контакты без мессенджера (Telegram/Max/VK) — они не импортируются и не уходят в волну (число — в contactsSkippedNoMessenger). Проверка = API-вызов на КАЖДЫЙ контакт, запуск синхронно ждёт Шаг 1 → для БОЛЬШИХ когорт (сотни контактов) возможен таймаут: тогда ставь check_messenger_presence=false и сначала прогони backfill POST /api/v2/.../chat-users/import/getcourse-refresh-messengers.',
|
|
187
|
-
'dedup_window_days (default 30, 0–365) и dedup_scope (default any | same_funnel): any учитывает любую прошлую волну, same_funnel — ту же каноническую бизнес-воронку (legacy batch/resend-суффиксы игнорируются). 0 = выключить дедуп полностью — только для осознанного повторного запуска.',
|
|
188
|
-
]),
|
|
189
|
-
}),
|
|
190
|
-
defineTool({
|
|
191
|
-
name: 'besales_getcourse_wave_status',
|
|
192
|
-
requiredFields: ['job_id'],
|
|
193
|
-
defaultWorkspaceId: true,
|
|
194
|
-
schema: z.object({
|
|
195
|
-
workspace_id: uuidSchema.optional(),
|
|
196
|
-
job_id: uuidSchema,
|
|
197
|
-
}),
|
|
198
|
-
createRequest: (input) => ({
|
|
199
|
-
method: 'GET',
|
|
200
|
-
path: `/workspaces/${input.workspace_id}/getcourse-waves/${input.job_id}`,
|
|
201
|
-
}),
|
|
202
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
203
|
-
'Статус волны (джоб Шага 2): status (PROCESSING|COMPLETED), processedRows/successCount/failedCount, errorReasons (первые построчные ошибки), preflight aggregates.',
|
|
204
|
-
'COMPLETED + failedCount>0 — часть контактов не прошла (см. errorReasons: нет сделки, поле вне «Предложения» заказа и т.п.).',
|
|
205
|
-
'preflight.blocked/errors — контакты, которым НЕ отправляли; BroadcastRecipient для них не создаётся, поэтому received в аналитике не раздувается.',
|
|
206
|
-
]),
|
|
207
|
-
}),
|
|
208
|
-
defineTool({
|
|
209
|
-
name: 'besales_getcourse_wave_run_start',
|
|
210
|
-
requiredFields: ['platform_id', 'crm_id', 'channels', 'contacts'],
|
|
211
|
-
defaultWorkspaceId: true,
|
|
212
|
-
schema: z
|
|
213
|
-
.object({
|
|
214
|
-
workspace_id: uuidSchema.optional(),
|
|
215
|
-
platform_id: uuidSchema,
|
|
216
|
-
crm_id: uuidSchema,
|
|
217
|
-
launch_type: z.literal('status').optional(),
|
|
218
|
-
channels: z.array(z.string()).min(1),
|
|
219
|
-
funnel: z.string().max(200).optional(),
|
|
220
|
-
segment: z.string().max(200).optional(),
|
|
221
|
-
cohort_label: z.string().max(200).optional(),
|
|
222
|
-
message_variant: z.string().max(120).optional(),
|
|
223
|
-
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
224
|
-
product_code: z
|
|
225
|
-
.string()
|
|
226
|
-
.trim()
|
|
227
|
-
.max(100)
|
|
228
|
-
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
229
|
-
.optional(),
|
|
230
|
-
product_name: z.string().max(200).optional(),
|
|
231
|
-
deal_fields: z
|
|
232
|
-
.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
233
|
-
.optional(),
|
|
234
|
-
contacts: z
|
|
235
|
-
.array(z.object({
|
|
236
|
-
contact_id: z.string(),
|
|
237
|
-
deal_id: z.string().optional(),
|
|
238
|
-
transport: z.string().optional(),
|
|
239
|
-
dialog_id: z.string().optional(),
|
|
240
|
-
lead_last_active_at: z.string().datetime({ offset: true }).optional(),
|
|
241
|
-
segment_entered_at: z.string().datetime({ offset: true }).optional(),
|
|
242
|
-
}))
|
|
243
|
-
.min(1)
|
|
244
|
-
.max(5000),
|
|
245
|
-
confirm: z.boolean().optional(),
|
|
246
|
-
check_messenger_presence: z.boolean().optional(),
|
|
247
|
-
dedup_window_days: z.number().int().min(0).max(365).optional(),
|
|
248
|
-
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
249
|
-
preflight: z
|
|
250
|
-
.object({
|
|
251
|
-
enabled: z.boolean().optional(),
|
|
252
|
-
product_title: z.string().max(500).optional(),
|
|
253
|
-
product_match_key: z.string().max(500).optional(),
|
|
254
|
-
offer_id: z.number().int().positive().optional(),
|
|
255
|
-
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
256
|
-
})
|
|
257
|
-
.optional(),
|
|
258
|
-
chunk_size: z.number().int().min(1).max(500).optional(),
|
|
259
|
-
explicit_confirmation: z.literal(true).optional(),
|
|
260
|
-
confirmation_reason: z.string().optional(),
|
|
261
|
-
})
|
|
262
|
-
.refine((input) => !input.confirm || input.explicit_confirmation === true, {
|
|
263
|
-
message: 'Реальный запуск требует explicit_confirmation=true: отправка волны необратима',
|
|
264
|
-
path: ['explicit_confirmation'],
|
|
265
|
-
}),
|
|
266
|
-
createRequest: (input) => ({
|
|
267
|
-
method: 'POST',
|
|
268
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs`,
|
|
269
|
-
body: compactObject({
|
|
270
|
-
platformId: input.platform_id,
|
|
271
|
-
crmId: input.crm_id,
|
|
272
|
-
launchType: input.launch_type,
|
|
273
|
-
channels: input.channels,
|
|
274
|
-
funnel: input.funnel,
|
|
275
|
-
segment: input.segment,
|
|
276
|
-
cohortLabel: input.cohort_label,
|
|
277
|
-
messageVariant: input.message_variant,
|
|
278
|
-
foundCount: input.found_count,
|
|
279
|
-
productCode: input.product_code,
|
|
280
|
-
productName: input.product_name,
|
|
281
|
-
dealFields: input.deal_fields,
|
|
282
|
-
contacts: input.contacts?.map((contact) => compactObject({
|
|
283
|
-
contactId: contact.contact_id,
|
|
284
|
-
dealId: contact.deal_id,
|
|
285
|
-
transport: contact.transport,
|
|
286
|
-
dialogId: contact.dialog_id,
|
|
287
|
-
leadLastActiveAt: contact.lead_last_active_at,
|
|
288
|
-
segmentEnteredAt: contact.segment_entered_at,
|
|
289
|
-
})),
|
|
290
|
-
confirm: input.confirm,
|
|
291
|
-
checkMessengerPresence: input.check_messenger_presence,
|
|
292
|
-
dedupWindowDays: input.dedup_window_days,
|
|
293
|
-
dedupScope: input.dedup_scope,
|
|
294
|
-
preflight: input.preflight
|
|
295
|
-
? compactObject({
|
|
296
|
-
enabled: input.preflight.enabled,
|
|
297
|
-
productTitle: input.preflight.product_title,
|
|
298
|
-
productMatchKey: input.preflight.product_match_key,
|
|
299
|
-
offerId: input.preflight.offer_id,
|
|
300
|
-
otherProductActiveDealMode: input.preflight.other_product_active_deal_mode,
|
|
301
|
-
})
|
|
302
|
-
: undefined,
|
|
303
|
-
chunkSize: input.chunk_size,
|
|
304
|
-
}),
|
|
305
|
-
}),
|
|
306
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
307
|
-
'Durable-прогон сам режет когорту на куски и отправляет их последовательно, сам ждёт освобождения лока импорта и переживает рестарт сервиса. Опрашивай ОДИН прогон через besales_getcourse_wave_run_status, не собирай волну вручную.',
|
|
308
|
-
'БЕЗ confirm=true — DRY-RUN: возвращается проверка, но прогон НЕ создаётся и ничего не отправляется.',
|
|
309
|
-
'Реальный запуск требует ОДНОВРЕМЕННО confirm=true и explicit_confirmation=true. explicit_confirmation — MCP-гейт явного согласия оператора и в HTTP-запрос не передаётся.',
|
|
310
|
-
'Поддерживается ТОЛЬКО launch_type=status. Для board/group используй старый besales_getcourse_wave_import.',
|
|
311
|
-
'HTTP 409 означает, что на платформе уже идёт активный прогон: в ответе есть его runId — не создавай новую волну, опрашивай этот runId.',
|
|
312
|
-
'Статус прогона отдаёт только агрегаты. Полный построчный разбор получай через besales_getcourse_wave_run_contacts с курсорной пагинацией.',
|
|
313
|
-
'Исходы контактов: SENT — отправлено; SKIPPED — дедуп, нет мессенджера или preflight заблокировал; FAILED — построчная ошибка; UNKNOWN — исход НЕИЗВЕСТЕН: кусок был прерван, но отправка могла состояться.',
|
|
314
|
-
'UNKNOWN и FAILED НИКОГДА не переотправляются автоматически. Повторная отправка таких контактов — только осознанное решение оператора отдельной волной: отсутствие строки получателя не доказывает отсутствие отправки.',
|
|
315
|
-
]),
|
|
316
|
-
}),
|
|
317
|
-
defineTool({
|
|
318
|
-
name: 'besales_getcourse_wave_run_status',
|
|
319
|
-
requiredFields: ['run_id'],
|
|
320
|
-
defaultWorkspaceId: true,
|
|
321
|
-
schema: z.object({
|
|
322
|
-
workspace_id: uuidSchema.optional(),
|
|
323
|
-
run_id: uuidSchema,
|
|
324
|
-
}),
|
|
325
|
-
createRequest: (input) => ({
|
|
326
|
-
method: 'GET',
|
|
327
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}`,
|
|
328
|
-
}),
|
|
329
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
330
|
-
'Это единый агрегированный статус durable-прогона: он сам последовательно обрабатывает куски, ждёт лок импорта и продолжает после рестарта сервиса. Не собирай и не запускай куски вручную.',
|
|
331
|
-
'Статус содержит только агрегаты. Для полного построчного разбора используй besales_getcourse_wave_run_contacts с курсорной пагинацией.',
|
|
332
|
-
'SENT — отправлено; SKIPPED — дедуп, нет мессенджера или блокировка preflight; FAILED — построчная ошибка; UNKNOWN — исход НЕИЗВЕСТЕН после прерванного куска, отправка могла состояться.',
|
|
333
|
-
'UNKNOWN и FAILED НИКОГДА не переотправляются автоматически. Решение о повторе принимает оператор и запускает отдельную волну, потому что отсутствие строки получателя не доказывает отсутствие отправки.',
|
|
334
|
-
]),
|
|
335
|
-
}),
|
|
336
|
-
defineTool({
|
|
337
|
-
name: 'besales_getcourse_wave_run_contacts',
|
|
338
|
-
requiredFields: ['run_id'],
|
|
339
|
-
defaultWorkspaceId: true,
|
|
340
|
-
schema: z.object({
|
|
341
|
-
workspace_id: uuidSchema.optional(),
|
|
342
|
-
run_id: uuidSchema,
|
|
343
|
-
status: z
|
|
344
|
-
.enum(['PENDING', 'CLAIMED', 'SENT', 'SKIPPED', 'FAILED', 'UNKNOWN'])
|
|
345
|
-
.optional(),
|
|
346
|
-
cursor: uuidSchema.optional(),
|
|
347
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
348
|
-
}),
|
|
349
|
-
createRequest: (input) => {
|
|
350
|
-
const params = new URLSearchParams();
|
|
351
|
-
if (input.status)
|
|
352
|
-
params.set('status', input.status);
|
|
353
|
-
if (input.cursor)
|
|
354
|
-
params.set('cursor', input.cursor);
|
|
355
|
-
if (input.limit !== undefined)
|
|
356
|
-
params.set('limit', String(input.limit));
|
|
357
|
-
const query = params.toString();
|
|
358
|
-
return {
|
|
359
|
-
method: 'GET',
|
|
360
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}/contacts${query ? `?${query}` : ''}`,
|
|
361
|
-
};
|
|
362
|
-
},
|
|
363
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
364
|
-
'Полный построчный разбор durable-прогона. Продолжай курсорную пагинацию по nextCursor до конца; агрегированный besales_getcourse_wave_run_status строк контактов не возвращает.',
|
|
365
|
-
'Фильтр status: PENDING | CLAIMED | SENT | SKIPPED | FAILED | UNKNOWN. SKIPPED означает дедуп, отсутствие мессенджера или блокировку preflight; FAILED — построчную ошибку.',
|
|
366
|
-
'UNKNOWN означает, что исход НЕИЗВЕСТЕН: кусок был прерван, но отправка могла состояться. UNKNOWN и FAILED НИКОГДА не переотправляются автоматически.',
|
|
367
|
-
'Повтор UNKNOWN/FAILED допустим только осознанным решением оператора отдельной волной: отсутствие строки получателя не доказывает отсутствие отправки.',
|
|
368
|
-
]),
|
|
369
|
-
}),
|
|
370
|
-
defineTool({
|
|
371
|
-
name: 'besales_getcourse_wave_run_cancel',
|
|
372
|
-
requiredFields: ['run_id'],
|
|
373
|
-
defaultWorkspaceId: true,
|
|
374
|
-
schema: z.object({
|
|
375
|
-
workspace_id: uuidSchema.optional(),
|
|
376
|
-
run_id: uuidSchema,
|
|
377
|
-
}),
|
|
378
|
-
createRequest: (input) => ({
|
|
379
|
-
method: 'POST',
|
|
380
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}/cancel`,
|
|
381
|
-
}),
|
|
382
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
383
|
-
'Отмена запрещает прогону заявлять новые куски. Уже заявленный кусок может завершиться; после отмены опроси besales_getcourse_wave_run_status.',
|
|
384
|
-
'Для полного построчного результата используй besales_getcourse_wave_run_contacts с курсорной пагинацией; статус прогона отдаёт только агрегаты.',
|
|
385
|
-
'UNKNOWN — исход НЕИЗВЕСТЕН после прерванного куска, отправка могла состояться. UNKNOWN и FAILED автоматически не переотправляются; повтор возможен только отдельной волной по осознанному решению оператора.',
|
|
386
|
-
]),
|
|
387
|
-
}),
|
|
388
|
-
defineTool({
|
|
389
|
-
name: 'besales_getcourse_wave_run_batch_start',
|
|
390
|
-
requiredFields: ['batch_key', 'platform_id', 'crm_id', 'segments'],
|
|
391
|
-
defaultWorkspaceId: true,
|
|
392
|
-
schema: z
|
|
393
|
-
.object({
|
|
394
|
-
workspace_id: uuidSchema.optional(),
|
|
395
|
-
batch_key: z.string(),
|
|
396
|
-
platform_id: uuidSchema,
|
|
397
|
-
crm_id: uuidSchema,
|
|
398
|
-
segments: z
|
|
399
|
-
.array(z
|
|
400
|
-
.object({
|
|
401
|
-
mode: z.literal('status'),
|
|
402
|
-
contacts: z
|
|
403
|
-
.array(z.object({
|
|
404
|
-
contact_id: z.string(),
|
|
405
|
-
deal_id: z.string().optional(),
|
|
406
|
-
transport: z.string().optional(),
|
|
407
|
-
dialog_id: z.string().optional(),
|
|
408
|
-
lead_last_active_at: z.string().datetime({ offset: true }).optional(),
|
|
409
|
-
segment_entered_at: z.string().datetime({ offset: true }).optional(),
|
|
410
|
-
}))
|
|
411
|
-
.min(1)
|
|
412
|
-
.max(2000),
|
|
413
|
-
channels: z.array(z.string()).min(1),
|
|
414
|
-
deal_fields: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
|
415
|
-
funnel: z.string().max(200).optional(),
|
|
416
|
-
segment: z.string().max(200).optional(),
|
|
417
|
-
cohort_label: z.string().max(200).optional(),
|
|
418
|
-
message_variant: z.string().max(120).optional(),
|
|
419
|
-
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
420
|
-
product_code: z
|
|
421
|
-
.string()
|
|
422
|
-
.trim()
|
|
423
|
-
.max(100)
|
|
424
|
-
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
425
|
-
.optional(),
|
|
426
|
-
product_name: z.string().max(200).optional(),
|
|
427
|
-
preflight: z
|
|
428
|
-
.object({
|
|
429
|
-
enabled: z.boolean().optional(),
|
|
430
|
-
product_title: z.string().max(500).optional(),
|
|
431
|
-
product_match_key: z.string().max(500).optional(),
|
|
432
|
-
offer_id: z.number().int().positive().optional(),
|
|
433
|
-
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
434
|
-
})
|
|
435
|
-
.optional(),
|
|
436
|
-
check_messenger_presence: z.boolean().optional(),
|
|
437
|
-
dedup_window_days: z.number().int().min(1).max(365).optional(),
|
|
438
|
-
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
439
|
-
chunk_size: z.number().int().min(1).max(500).optional(),
|
|
440
|
-
})
|
|
441
|
-
.strict())
|
|
442
|
-
.min(1)
|
|
443
|
-
.max(10)
|
|
444
|
-
.refine((segments) => segments.reduce((total, segment) => total + segment.contacts.length, 0) <= 5000, {
|
|
445
|
-
message: 'В батче может быть не более 5000 контактов суммарно',
|
|
446
|
-
}),
|
|
447
|
-
confirm: z.boolean().optional(),
|
|
448
|
-
explicit_confirmation: z.literal(true).optional(),
|
|
449
|
-
confirmation_reason: z.string().optional(),
|
|
450
|
-
})
|
|
451
|
-
.refine((input) => !input.confirm || input.explicit_confirmation === true, {
|
|
452
|
-
message: 'Реальный запуск требует explicit_confirmation=true: отправка волны необратима',
|
|
453
|
-
path: ['explicit_confirmation'],
|
|
454
|
-
}),
|
|
455
|
-
createRequest: (input) => ({
|
|
456
|
-
method: 'POST',
|
|
457
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batch`,
|
|
458
|
-
body: compactObject({
|
|
459
|
-
batchKey: input.batch_key,
|
|
460
|
-
platformId: input.platform_id,
|
|
461
|
-
crmId: input.crm_id,
|
|
462
|
-
segments: input.segments.map((segment) => compactObject({
|
|
463
|
-
mode: segment.mode,
|
|
464
|
-
contacts: segment.contacts.map((contact) => compactObject({
|
|
465
|
-
contactId: contact.contact_id,
|
|
466
|
-
dealId: contact.deal_id,
|
|
467
|
-
transport: contact.transport,
|
|
468
|
-
dialogId: contact.dialog_id,
|
|
469
|
-
leadLastActiveAt: contact.lead_last_active_at,
|
|
470
|
-
segmentEnteredAt: contact.segment_entered_at,
|
|
471
|
-
})),
|
|
472
|
-
channels: segment.channels,
|
|
473
|
-
dealFields: segment.deal_fields,
|
|
474
|
-
funnel: segment.funnel,
|
|
475
|
-
segment: segment.segment,
|
|
476
|
-
cohortLabel: segment.cohort_label,
|
|
477
|
-
messageVariant: segment.message_variant,
|
|
478
|
-
foundCount: segment.found_count,
|
|
479
|
-
productCode: segment.product_code,
|
|
480
|
-
productName: segment.product_name,
|
|
481
|
-
preflight: segment.preflight
|
|
482
|
-
? compactObject({
|
|
483
|
-
enabled: segment.preflight.enabled,
|
|
484
|
-
productTitle: segment.preflight.product_title,
|
|
485
|
-
productMatchKey: segment.preflight.product_match_key,
|
|
486
|
-
offerId: segment.preflight.offer_id,
|
|
487
|
-
otherProductActiveDealMode: segment.preflight.other_product_active_deal_mode,
|
|
488
|
-
})
|
|
489
|
-
: undefined,
|
|
490
|
-
checkMessengerPresence: segment.check_messenger_presence,
|
|
491
|
-
dedupWindowDays: segment.dedup_window_days,
|
|
492
|
-
dedupScope: segment.dedup_scope,
|
|
493
|
-
chunkSize: segment.chunk_size,
|
|
494
|
-
})),
|
|
495
|
-
confirm: input.confirm,
|
|
496
|
-
}),
|
|
497
|
-
}),
|
|
498
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
499
|
-
'Очередь исполняет сегменты ПОСЛЕДОВАТЕЛЬНО: в один момент на платформе идёт только один прогон. Опрашивай ОДИН besales_getcourse_wave_run_batch_status, не собирай сегменты и их статусы вручную.',
|
|
500
|
-
'batch_key сгенерируй ОДИН раз на попытку и ПЕРЕИСПОЛЬЗУЙ при ретрае: это ключ идемпотентности. Повтор с тем же ключом вернёт существующий батч; новый ключ создаст ВТОРУЮ копию кампании, которую дедуп не остановит.',
|
|
501
|
-
'Если повторить тот же batch_key с ИЗМЕНЁННЫМ составом сегментов, сервер вернёт HTTP 409 «ключ занят другим составом». Не меняй состав при ретрае — сначала прочитай статус существующего батча.',
|
|
502
|
-
'HTTP 400 отклоняет батч до запуска, если контакт есть в двух сегментах, пересекается с незавершённым прогоном платформы или ему уже отправляли за последние 30 дней. В ответе будут пары contactId + сегменты либо конфликтующий runId; исправь состав, а не обходи проверку новым ключом.',
|
|
503
|
-
'Два подряд отказавших сегмента ставят батч на паузу. Продолжай только через besales_getcourse_wave_run_batch_resume после проверки причин отказов.',
|
|
504
|
-
'UNKNOWN и FAILED НИКОГДА не переотправляются автоматически. Повтор возможен только отдельным осознанным запуском оператора.',
|
|
505
|
-
'БЕЗ confirm=true — DRY-RUN: батч не создаётся и ничего не отправляется. Реальный запуск дополнительно требует MCP-only explicit_confirmation=true; это поле в HTTP-запрос не передаётся.',
|
|
506
|
-
]),
|
|
507
|
-
}),
|
|
508
|
-
defineTool({
|
|
509
|
-
name: 'besales_getcourse_wave_run_batch_status',
|
|
510
|
-
requiredFields: ['batch_id'],
|
|
511
|
-
defaultWorkspaceId: true,
|
|
512
|
-
schema: z.object({
|
|
513
|
-
workspace_id: uuidSchema.optional(),
|
|
514
|
-
batch_id: uuidSchema,
|
|
515
|
-
}),
|
|
516
|
-
createRequest: (input) => ({
|
|
517
|
-
method: 'GET',
|
|
518
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}`,
|
|
519
|
-
}),
|
|
520
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
521
|
-
'Это ЕДИНСТВЕННЫЙ агрегированный статус очереди: она запускает сегменты последовательно и держит только один прогон на платформу. Опрашивай этот batch-status, а не статусы сегментов вручную.',
|
|
522
|
-
'Если батч на паузе после двух подряд отказавших сегментов, сначала проверь причины, затем снимай паузу только besales_getcourse_wave_run_batch_resume.',
|
|
523
|
-
'UNKNOWN и FAILED никогда не переотправляются автоматически; отсутствие подтверждённого исхода не означает, что отправки не было.',
|
|
524
|
-
]),
|
|
525
|
-
}),
|
|
526
|
-
defineTool({
|
|
527
|
-
name: 'besales_getcourse_wave_run_batch_cancel',
|
|
528
|
-
requiredFields: ['batch_id'],
|
|
529
|
-
defaultWorkspaceId: true,
|
|
530
|
-
schema: z.object({
|
|
531
|
-
workspace_id: uuidSchema.optional(),
|
|
532
|
-
batch_id: uuidSchema,
|
|
533
|
-
}),
|
|
534
|
-
createRequest: (input) => ({
|
|
535
|
-
method: 'POST',
|
|
536
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}/cancel`,
|
|
537
|
-
}),
|
|
538
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
539
|
-
'Отмена останавливает очередь батча; после неё опроси ОДИН besales_getcourse_wave_run_batch_status, не собирая сегменты вручную.',
|
|
540
|
-
'UNKNOWN и FAILED автоматически не переотправляются. Любой повтор — только отдельное осознанное решение оператора.',
|
|
541
|
-
]),
|
|
542
|
-
}),
|
|
543
|
-
defineTool({
|
|
544
|
-
name: 'besales_getcourse_wave_run_batch_resume',
|
|
545
|
-
requiredFields: ['batch_id'],
|
|
546
|
-
defaultWorkspaceId: true,
|
|
547
|
-
schema: z.object({
|
|
548
|
-
workspace_id: uuidSchema.optional(),
|
|
549
|
-
batch_id: uuidSchema,
|
|
550
|
-
}),
|
|
551
|
-
createRequest: (input) => ({
|
|
552
|
-
method: 'POST',
|
|
553
|
-
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}/resume`,
|
|
554
|
-
}),
|
|
555
|
-
enhanceResponse: (response) => withInstructions(response, [
|
|
556
|
-
'Снимает паузу батча, которую вызывают два подряд отказавших сегмента. После resume опрашивай ОДИН besales_getcourse_wave_run_batch_status: очередь продолжит сегменты последовательно, один прогон на платформу.',
|
|
557
|
-
'UNKNOWN и FAILED никогда не переотправляются автоматически; повтор таких контактов — только отдельная осознанная волна.',
|
|
558
|
-
]),
|
|
559
|
-
}),
|
|
83
|
+
...getCourseWaveTools,
|
|
560
84
|
defineTool({
|
|
561
85
|
name: 'besales_wazzup_templates_list',
|
|
562
86
|
requiredFields: ['platform_id'],
|
|
@@ -884,7 +408,16 @@ export const platformSetupTools = [
|
|
|
884
408
|
conditionLogic: input.condition_logic,
|
|
885
409
|
conditions: input.conditions?.map((condition) => ({
|
|
886
410
|
customVariableId: condition.custom_variable_id,
|
|
887
|
-
|
|
411
|
+
// Набор — источник истины; legacy-скаляр пробрасываем как есть (API примет обе формы).
|
|
412
|
+
// Без этого маппинга variable_values терялся бы молча: до правки сюда уходил только скаляр.
|
|
413
|
+
// Незаданную форму не отправляем вовсе: явный undefined в теле выглядел бы как
|
|
414
|
+
// «поле передано пустым» и мешал бы взаимоисключающей валидации на стороне API.
|
|
415
|
+
...(condition.variable_values !== undefined
|
|
416
|
+
? { variableValues: condition.variable_values }
|
|
417
|
+
: {}),
|
|
418
|
+
...(condition.variable_value !== undefined
|
|
419
|
+
? { variableValue: condition.variable_value }
|
|
420
|
+
: {}),
|
|
888
421
|
})),
|
|
889
422
|
customRouterPrompt: input.custom_router_prompt,
|
|
890
423
|
isEnabled: input.is_enabled,
|
|
@@ -913,6 +446,14 @@ export const platformSetupTools = [
|
|
|
913
446
|
is_enabled: z.boolean().optional(),
|
|
914
447
|
priority: z.number().int().min(1).max(100).optional(),
|
|
915
448
|
stop_propagation: z.boolean().optional(),
|
|
449
|
+
/** Повтор ВНУТРИ одного ответа модели. Горизонт — этот ответ, дальше состояние теряется. */
|
|
450
|
+
allow_repeated_calls: z.boolean().optional(),
|
|
451
|
+
/**
|
|
452
|
+
* Одно срабатывание на контакт НАВСЕГДА (право в `agent_trigger_contact_claims`).
|
|
453
|
+
* Ось другая, чем у `allow_repeated_calls`: тот живёт один ответ, этот переживает
|
|
454
|
+
* все следующие сообщения. Для действий, пишущих в чужие системы (заметка в CRM).
|
|
455
|
+
*/
|
|
456
|
+
run_once_per_contact: z.boolean().optional(),
|
|
916
457
|
scope: triggerScopeSchema,
|
|
917
458
|
metadata: anyObjectSchema.optional(),
|
|
918
459
|
actions: z.array(anyObjectSchema).optional(),
|
|
@@ -930,6 +471,8 @@ export const platformSetupTools = [
|
|
|
930
471
|
isEnabled: input.is_enabled,
|
|
931
472
|
priority: input.priority,
|
|
932
473
|
stopPropagation: input.stop_propagation,
|
|
474
|
+
allowRepeatedCalls: input.allow_repeated_calls,
|
|
475
|
+
runOncePerContact: input.run_once_per_contact,
|
|
933
476
|
scope: input.scope,
|
|
934
477
|
metadata: input.metadata,
|
|
935
478
|
actions: input.actions,
|
|
@@ -942,6 +485,10 @@ export const platformSetupTools = [
|
|
|
942
485
|
'сперва прочитай текущие действия (besales_platform_context_get) и передай ПОЛНЫЙ желаемый набор, ' +
|
|
943
486
|
'а не дельту, иначе потеряешь не переданные действия.',
|
|
944
487
|
'Без replace_actions передача actions на существующем триггере отклоняется (защита от случайной потери).',
|
|
488
|
+
'allow_repeated_calls и run_once_per_contact — РАЗНЫЕ оси, не синонимы: первый про повтор ' +
|
|
489
|
+
'внутри одного ответа модели, второй запрещает срабатывание навсегда после первого ' +
|
|
490
|
+
'успешного действия у этого контакта. Для действий, пишущих в чужие системы (заметка в CRM, ' +
|
|
491
|
+
'создание сделки), нужен именно run_once_per_contact.',
|
|
945
492
|
]),
|
|
946
493
|
}),
|
|
947
494
|
defineTool({
|
|
@@ -1028,37 +575,7 @@ export const platformSetupTools = [
|
|
|
1028
575
|
}),
|
|
1029
576
|
}),
|
|
1030
577
|
}),
|
|
1031
|
-
|
|
1032
|
-
name: 'besales_followup_sequence_upsert',
|
|
1033
|
-
requiredFields: ['platform_id', 'name', 'type', 'trigger', 'config'],
|
|
1034
|
-
schema: z.object({
|
|
1035
|
-
platform_id: uuidSchema,
|
|
1036
|
-
sequence_id: uuidSchema.optional(),
|
|
1037
|
-
name: z.string(),
|
|
1038
|
-
description: z.string().nullable().optional(),
|
|
1039
|
-
type: followUpSequenceTypeSchema,
|
|
1040
|
-
enabled: z.boolean().optional(),
|
|
1041
|
-
trigger: followUpTriggerSchema,
|
|
1042
|
-
config: anyObjectSchema,
|
|
1043
|
-
...platformSetupConfirmationSchema,
|
|
1044
|
-
})
|
|
1045
|
-
.superRefine(refineFollowUpConfig),
|
|
1046
|
-
createRequest: (input) => ({
|
|
1047
|
-
method: input.sequence_id ? 'PATCH' : 'POST',
|
|
1048
|
-
path: input.sequence_id
|
|
1049
|
-
? `/platforms/${input.platform_id}/follow-up/sequences/${input.sequence_id}`
|
|
1050
|
-
: `/platforms/${input.platform_id}/follow-up/sequences`,
|
|
1051
|
-
body: compactObject({
|
|
1052
|
-
name: input.name,
|
|
1053
|
-
description: input.description,
|
|
1054
|
-
type: input.type,
|
|
1055
|
-
enabled: input.enabled,
|
|
1056
|
-
trigger: input.trigger,
|
|
1057
|
-
config: input.config,
|
|
1058
|
-
...setupConfirmation(input),
|
|
1059
|
-
}),
|
|
1060
|
-
}),
|
|
1061
|
-
}),
|
|
578
|
+
...followUpSetupTools,
|
|
1062
579
|
defineTool({
|
|
1063
580
|
name: 'besales_pipeline_settings_update',
|
|
1064
581
|
requiredFields: ['platform_id'],
|
|
@@ -1353,42 +870,66 @@ export const platformSetupTools = [
|
|
|
1353
870
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
|
|
1354
871
|
}),
|
|
1355
872
|
}),
|
|
873
|
+
// Остаток до лимитов линтера ДО сборки черновика. Порядок здесь обязан совпадать с
|
|
874
|
+
// mcp-tools.json (registry.spec проверяет parity), поэтому вставка строго после _patch_get.
|
|
875
|
+
defineTool({
|
|
876
|
+
name: 'besales_prompt_headroom',
|
|
877
|
+
requiredFields: ['agent_id'],
|
|
878
|
+
// .strict() — иначе опечатка `draftId` вместо `draft_id` молча отбрасывается zod'ом,
|
|
879
|
+
// и тулза измеряет закоммиченный промпт вместо черновика.
|
|
880
|
+
schema: z
|
|
881
|
+
.object({
|
|
882
|
+
agent_id: uuidSchema,
|
|
883
|
+
draft_id: uuidSchema.optional(),
|
|
884
|
+
})
|
|
885
|
+
.strict(),
|
|
886
|
+
createRequest: (input) => ({
|
|
887
|
+
method: 'GET',
|
|
888
|
+
// `query` в ApiToolRequest нет — параметр был бы молча выброшен, поэтому клеим в path.
|
|
889
|
+
path: `/agents/${input.agent_id}/prompt/headroom${buildListQuery({ draftId: input.draft_id })}`,
|
|
890
|
+
}),
|
|
891
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
892
|
+
'`hardHeadroom` — сколько символов ещё можно добавить. Признак перебора — `exceedsHardLimit: true`; ровно на лимите (`hardHeadroom: 0`) commit ещё проходит.',
|
|
893
|
+
'Если запас меньше объёма планируемой правки — сначала сокращай промпт, а не собирай черновик.',
|
|
894
|
+
'`sections` отсортированы по весу DESC — это кандидаты на сокращение.',
|
|
895
|
+
'`exceedsSoftLimit` — промпт выше рекомендованных 40000: факты стоит вынести в Базу знаний.',
|
|
896
|
+
]),
|
|
897
|
+
}),
|
|
898
|
+
defineTool({
|
|
899
|
+
name: 'besales_prompt_diff',
|
|
900
|
+
requiredFields: ['agent_id', 'compare_agent_id'],
|
|
901
|
+
schema: z
|
|
902
|
+
.object({
|
|
903
|
+
agent_id: uuidSchema,
|
|
904
|
+
compare_agent_id: uuidSchema,
|
|
905
|
+
max_lines: z.number().int().min(1).max(PROMPT_DIFF_MAX_LINES_LIMIT).optional(),
|
|
906
|
+
})
|
|
907
|
+
.strict(),
|
|
908
|
+
createRequest: (input) => ({
|
|
909
|
+
method: 'POST',
|
|
910
|
+
path: `/agents/${input.agent_id}/prompt/diff`,
|
|
911
|
+
body: { compareAgentId: input.compare_agent_id, maxLines: input.max_lines },
|
|
912
|
+
}),
|
|
913
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
914
|
+
'`identicalBytes: true` — текущие тексты совпадают побайтово, значит один и тот же набор patch-операций найдёт те же якоря. Это НЕ гарантия одинакового исхода commit: lint резолвит канал каждого агента отдельно (одна и та же markdown-правка проходит в веб-виджете и падает в Telegram), а свой черновик каждый агент коммитит независимо.',
|
|
915
|
+
'`identicalNormalized: true` при `identicalBytes: false` — расходятся только пробелы/переводы строк, правила те же.',
|
|
916
|
+
'`onlyInBase` / `onlyInCompare` — что есть на одной стороне и нет на другой; `count` — превышение кратности.',
|
|
917
|
+
'`truncated: true` — список обрезан по `max_lines`. Обрезка ТЕКСТА строки — отдельный признак `textTruncated`; у таких строк сравнивай `characters`, а не `text`.',
|
|
918
|
+
]),
|
|
919
|
+
}),
|
|
1356
920
|
defineTool({
|
|
1357
921
|
name: 'besales_prompt_patch_apply',
|
|
1358
922
|
requiredFields: ['agent_id', 'draft_id', 'operations'],
|
|
1359
923
|
schema: z.object({
|
|
1360
924
|
agent_id: uuidSchema,
|
|
1361
925
|
draft_id: uuidSchema,
|
|
1362
|
-
operations: z
|
|
1363
|
-
.array(z.object({
|
|
1364
|
-
id: z.string().optional(),
|
|
1365
|
-
type: z.enum([
|
|
1366
|
-
'replace_text',
|
|
1367
|
-
'replace_all_text',
|
|
1368
|
-
'insert_after_text',
|
|
1369
|
-
'insert_before_text',
|
|
1370
|
-
'remove_text',
|
|
1371
|
-
'move_text_after_text',
|
|
1372
|
-
]),
|
|
1373
|
-
target_text: z.string().optional(),
|
|
1374
|
-
anchor_text: z.string().optional(),
|
|
1375
|
-
replacement_text: z.string().optional(),
|
|
1376
|
-
match_variants: z.array(z.string()).optional(),
|
|
1377
|
-
}))
|
|
1378
|
-
.min(1),
|
|
926
|
+
operations: z.array(promptPatchOperationSchema).min(1),
|
|
1379
927
|
}),
|
|
1380
928
|
createRequest: (input) => ({
|
|
1381
929
|
method: 'PATCH',
|
|
1382
930
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}/operations`,
|
|
1383
931
|
body: {
|
|
1384
|
-
operations: input.operations.map(
|
|
1385
|
-
id: op.id,
|
|
1386
|
-
type: op.type,
|
|
1387
|
-
targetText: op.target_text,
|
|
1388
|
-
anchorText: op.anchor_text,
|
|
1389
|
-
replacementText: op.replacement_text,
|
|
1390
|
-
matchVariants: op.match_variants,
|
|
1391
|
-
})),
|
|
932
|
+
operations: input.operations.map(mapPromptPatchOperationInput),
|
|
1392
933
|
},
|
|
1393
934
|
}),
|
|
1394
935
|
enhanceResponse: (response) => withInstructions(response, [
|
|
@@ -1431,6 +972,60 @@ export const platformSetupTools = [
|
|
|
1431
972
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
|
|
1432
973
|
}),
|
|
1433
974
|
}),
|
|
975
|
+
// Пакетная раскатка одной правки по парку агентов. Порядок обязан совпадать с
|
|
976
|
+
// mcp-tools.json — registry.spec сверяет имена позиционно.
|
|
977
|
+
defineTool({
|
|
978
|
+
name: 'besales_prompt_batch_start',
|
|
979
|
+
requiredFields: ['agent_ids', 'operations'],
|
|
980
|
+
schema: z
|
|
981
|
+
.object({
|
|
982
|
+
agent_ids: z.array(uuidSchema).min(1).max(PROMPT_BATCH_MAX_AGENTS),
|
|
983
|
+
operations: z.array(promptPatchOperationSchema).min(1),
|
|
984
|
+
all_or_nothing: z.boolean().optional(),
|
|
985
|
+
idempotency_key: z.string().max(PROMPT_BATCH_IDEMPOTENCY_KEY_MAX_LENGTH).optional(),
|
|
986
|
+
platform_id: uuidSchema.optional(),
|
|
987
|
+
})
|
|
988
|
+
.strict(),
|
|
989
|
+
createRequest: (input) => ({
|
|
990
|
+
method: 'POST',
|
|
991
|
+
path: '/prompt-batch/runs',
|
|
992
|
+
body: compactObject({
|
|
993
|
+
agentIds: input.agent_ids,
|
|
994
|
+
operations: input.operations.map(mapPromptPatchOperationInput),
|
|
995
|
+
allOrNothing: input.all_or_nothing,
|
|
996
|
+
idempotencyKey: input.idempotency_key,
|
|
997
|
+
platformId: input.platform_id,
|
|
998
|
+
}),
|
|
999
|
+
}),
|
|
1000
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
1001
|
+
'Прогон фоновый: опрашивай `besales_prompt_batch_status` по `runId`, пока `counts.PENDING` и `counts.PREPARED` не станут нулями.',
|
|
1002
|
+
'`SKIPPED` — не ошибка: смотри `skipReason`, чаще всего это NO_OPERATIONS_APPLIED (у агента другой промпт, якоря не нашлись).',
|
|
1003
|
+
'Упавших агентов докатывай `besales_prompt_batch_resume` — повторный `_start` создаст ВТОРОЙ прогон и применит операции ещё раз.',
|
|
1004
|
+
'Перед раскаткой на парк проверь запас длины у самого тяжёлого агента через `besales_prompt_headroom`.',
|
|
1005
|
+
]),
|
|
1006
|
+
}),
|
|
1007
|
+
defineTool({
|
|
1008
|
+
name: 'besales_prompt_batch_status',
|
|
1009
|
+
requiredFields: ['run_id'],
|
|
1010
|
+
schema: z.object({ run_id: uuidSchema }).strict(),
|
|
1011
|
+
createRequest: (input) => ({
|
|
1012
|
+
method: 'GET',
|
|
1013
|
+
path: `/prompt-batch/runs/${input.run_id}`,
|
|
1014
|
+
}),
|
|
1015
|
+
}),
|
|
1016
|
+
defineTool({
|
|
1017
|
+
name: 'besales_prompt_batch_resume',
|
|
1018
|
+
requiredFields: ['run_id'],
|
|
1019
|
+
schema: z.object({ run_id: uuidSchema }).strict(),
|
|
1020
|
+
createRequest: (input) => ({
|
|
1021
|
+
method: 'POST',
|
|
1022
|
+
path: `/prompt-batch/runs/${input.run_id}/resume`,
|
|
1023
|
+
}),
|
|
1024
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
1025
|
+
'Докатываются только FAILED-агенты. Уже закоммиченные не трогаются — повторное применение операций удвоило бы вставки.',
|
|
1026
|
+
'400 «Run is still in progress» означает, что прогон жив: дождись, а не повторяй resume.',
|
|
1027
|
+
]),
|
|
1028
|
+
}),
|
|
1434
1029
|
// S2.6 extra — besales_prompt_lint (после patches, чтобы matched catalog order)
|
|
1435
1030
|
defineTool({
|
|
1436
1031
|
name: 'besales_prompt_lint',
|