@besales/mcp 0.33.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/schemas/mcp-tools.json +965 -30
- 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/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 +73 -8
- package/dist/tools/definitions/platform-setup.js +106 -504
- package/dist/tools/definitions/platform-setup.js.map +1 -1
- package/dist/tools/definitions/scheduled-tasks.d.ts +4 -0
- package/dist/tools/definitions/scheduled-tasks.js +14 -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 +53 -0
- package/dist/tools/definitions/shared.js +71 -0
- package/dist/tools/definitions/shared.js.map +1 -1
- package/dist/tools/definitions.d.ts +121 -15
- package/dist/tools/definitions.js +2 -0
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/registry-mapping-cases-core.d.ts +34 -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 +910 -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 +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { refineFollowUpConfig } from './follow-up.js';
|
|
3
|
-
import {
|
|
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, followUpSequenceTypeSchema, followUpTriggerSchema, 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'],
|
|
@@ -1353,42 +877,66 @@ export const platformSetupTools = [
|
|
|
1353
877
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
|
|
1354
878
|
}),
|
|
1355
879
|
}),
|
|
880
|
+
// Остаток до лимитов линтера ДО сборки черновика. Порядок здесь обязан совпадать с
|
|
881
|
+
// mcp-tools.json (registry.spec проверяет parity), поэтому вставка строго после _patch_get.
|
|
882
|
+
defineTool({
|
|
883
|
+
name: 'besales_prompt_headroom',
|
|
884
|
+
requiredFields: ['agent_id'],
|
|
885
|
+
// .strict() — иначе опечатка `draftId` вместо `draft_id` молча отбрасывается zod'ом,
|
|
886
|
+
// и тулза измеряет закоммиченный промпт вместо черновика.
|
|
887
|
+
schema: z
|
|
888
|
+
.object({
|
|
889
|
+
agent_id: uuidSchema,
|
|
890
|
+
draft_id: uuidSchema.optional(),
|
|
891
|
+
})
|
|
892
|
+
.strict(),
|
|
893
|
+
createRequest: (input) => ({
|
|
894
|
+
method: 'GET',
|
|
895
|
+
// `query` в ApiToolRequest нет — параметр был бы молча выброшен, поэтому клеим в path.
|
|
896
|
+
path: `/agents/${input.agent_id}/prompt/headroom${buildListQuery({ draftId: input.draft_id })}`,
|
|
897
|
+
}),
|
|
898
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
899
|
+
'`hardHeadroom` — сколько символов ещё можно добавить. Признак перебора — `exceedsHardLimit: true`; ровно на лимите (`hardHeadroom: 0`) commit ещё проходит.',
|
|
900
|
+
'Если запас меньше объёма планируемой правки — сначала сокращай промпт, а не собирай черновик.',
|
|
901
|
+
'`sections` отсортированы по весу DESC — это кандидаты на сокращение.',
|
|
902
|
+
'`exceedsSoftLimit` — промпт выше рекомендованных 40000: факты стоит вынести в Базу знаний.',
|
|
903
|
+
]),
|
|
904
|
+
}),
|
|
905
|
+
defineTool({
|
|
906
|
+
name: 'besales_prompt_diff',
|
|
907
|
+
requiredFields: ['agent_id', 'compare_agent_id'],
|
|
908
|
+
schema: z
|
|
909
|
+
.object({
|
|
910
|
+
agent_id: uuidSchema,
|
|
911
|
+
compare_agent_id: uuidSchema,
|
|
912
|
+
max_lines: z.number().int().min(1).max(PROMPT_DIFF_MAX_LINES_LIMIT).optional(),
|
|
913
|
+
})
|
|
914
|
+
.strict(),
|
|
915
|
+
createRequest: (input) => ({
|
|
916
|
+
method: 'POST',
|
|
917
|
+
path: `/agents/${input.agent_id}/prompt/diff`,
|
|
918
|
+
body: { compareAgentId: input.compare_agent_id, maxLines: input.max_lines },
|
|
919
|
+
}),
|
|
920
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
921
|
+
'`identicalBytes: true` — текущие тексты совпадают побайтово, значит один и тот же набор patch-операций найдёт те же якоря. Это НЕ гарантия одинакового исхода commit: lint резолвит канал каждого агента отдельно (одна и та же markdown-правка проходит в веб-виджете и падает в Telegram), а свой черновик каждый агент коммитит независимо.',
|
|
922
|
+
'`identicalNormalized: true` при `identicalBytes: false` — расходятся только пробелы/переводы строк, правила те же.',
|
|
923
|
+
'`onlyInBase` / `onlyInCompare` — что есть на одной стороне и нет на другой; `count` — превышение кратности.',
|
|
924
|
+
'`truncated: true` — список обрезан по `max_lines`. Обрезка ТЕКСТА строки — отдельный признак `textTruncated`; у таких строк сравнивай `characters`, а не `text`.',
|
|
925
|
+
]),
|
|
926
|
+
}),
|
|
1356
927
|
defineTool({
|
|
1357
928
|
name: 'besales_prompt_patch_apply',
|
|
1358
929
|
requiredFields: ['agent_id', 'draft_id', 'operations'],
|
|
1359
930
|
schema: z.object({
|
|
1360
931
|
agent_id: uuidSchema,
|
|
1361
932
|
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),
|
|
933
|
+
operations: z.array(promptPatchOperationSchema).min(1),
|
|
1379
934
|
}),
|
|
1380
935
|
createRequest: (input) => ({
|
|
1381
936
|
method: 'PATCH',
|
|
1382
937
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}/operations`,
|
|
1383
938
|
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
|
-
})),
|
|
939
|
+
operations: input.operations.map(mapPromptPatchOperationInput),
|
|
1392
940
|
},
|
|
1393
941
|
}),
|
|
1394
942
|
enhanceResponse: (response) => withInstructions(response, [
|
|
@@ -1431,6 +979,60 @@ export const platformSetupTools = [
|
|
|
1431
979
|
path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
|
|
1432
980
|
}),
|
|
1433
981
|
}),
|
|
982
|
+
// Пакетная раскатка одной правки по парку агентов. Порядок обязан совпадать с
|
|
983
|
+
// mcp-tools.json — registry.spec сверяет имена позиционно.
|
|
984
|
+
defineTool({
|
|
985
|
+
name: 'besales_prompt_batch_start',
|
|
986
|
+
requiredFields: ['agent_ids', 'operations'],
|
|
987
|
+
schema: z
|
|
988
|
+
.object({
|
|
989
|
+
agent_ids: z.array(uuidSchema).min(1).max(PROMPT_BATCH_MAX_AGENTS),
|
|
990
|
+
operations: z.array(promptPatchOperationSchema).min(1),
|
|
991
|
+
all_or_nothing: z.boolean().optional(),
|
|
992
|
+
idempotency_key: z.string().max(PROMPT_BATCH_IDEMPOTENCY_KEY_MAX_LENGTH).optional(),
|
|
993
|
+
platform_id: uuidSchema.optional(),
|
|
994
|
+
})
|
|
995
|
+
.strict(),
|
|
996
|
+
createRequest: (input) => ({
|
|
997
|
+
method: 'POST',
|
|
998
|
+
path: '/prompt-batch/runs',
|
|
999
|
+
body: compactObject({
|
|
1000
|
+
agentIds: input.agent_ids,
|
|
1001
|
+
operations: input.operations.map(mapPromptPatchOperationInput),
|
|
1002
|
+
allOrNothing: input.all_or_nothing,
|
|
1003
|
+
idempotencyKey: input.idempotency_key,
|
|
1004
|
+
platformId: input.platform_id,
|
|
1005
|
+
}),
|
|
1006
|
+
}),
|
|
1007
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
1008
|
+
'Прогон фоновый: опрашивай `besales_prompt_batch_status` по `runId`, пока `counts.PENDING` и `counts.PREPARED` не станут нулями.',
|
|
1009
|
+
'`SKIPPED` — не ошибка: смотри `skipReason`, чаще всего это NO_OPERATIONS_APPLIED (у агента другой промпт, якоря не нашлись).',
|
|
1010
|
+
'Упавших агентов докатывай `besales_prompt_batch_resume` — повторный `_start` создаст ВТОРОЙ прогон и применит операции ещё раз.',
|
|
1011
|
+
'Перед раскаткой на парк проверь запас длины у самого тяжёлого агента через `besales_prompt_headroom`.',
|
|
1012
|
+
]),
|
|
1013
|
+
}),
|
|
1014
|
+
defineTool({
|
|
1015
|
+
name: 'besales_prompt_batch_status',
|
|
1016
|
+
requiredFields: ['run_id'],
|
|
1017
|
+
schema: z.object({ run_id: uuidSchema }).strict(),
|
|
1018
|
+
createRequest: (input) => ({
|
|
1019
|
+
method: 'GET',
|
|
1020
|
+
path: `/prompt-batch/runs/${input.run_id}`,
|
|
1021
|
+
}),
|
|
1022
|
+
}),
|
|
1023
|
+
defineTool({
|
|
1024
|
+
name: 'besales_prompt_batch_resume',
|
|
1025
|
+
requiredFields: ['run_id'],
|
|
1026
|
+
schema: z.object({ run_id: uuidSchema }).strict(),
|
|
1027
|
+
createRequest: (input) => ({
|
|
1028
|
+
method: 'POST',
|
|
1029
|
+
path: `/prompt-batch/runs/${input.run_id}/resume`,
|
|
1030
|
+
}),
|
|
1031
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
1032
|
+
'Докатываются только FAILED-агенты. Уже закоммиченные не трогаются — повторное применение операций удвоило бы вставки.',
|
|
1033
|
+
'400 «Run is still in progress» означает, что прогон жив: дождись, а не повторяй resume.',
|
|
1034
|
+
]),
|
|
1035
|
+
}),
|
|
1434
1036
|
// S2.6 extra — besales_prompt_lint (после patches, чтобы matched catalog order)
|
|
1435
1037
|
defineTool({
|
|
1436
1038
|
name: 'besales_prompt_lint',
|