@besales/mcp 0.32.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.
Files changed (37) hide show
  1. package/README.md +48 -11
  2. package/dist/http/api-client.d.ts +2 -0
  3. package/dist/http/api-client.js +55 -0
  4. package/dist/http/api-client.js.map +1 -1
  5. package/dist/schemas/mcp-tools.json +1286 -50
  6. package/dist/tools/definitions/broadcast-analytics.d.ts +28 -7
  7. package/dist/tools/definitions/broadcast-analytics.js +399 -33
  8. package/dist/tools/definitions/broadcast-analytics.js.map +1 -1
  9. package/dist/tools/definitions/broadcast-mailing-variants.d.ts +19 -0
  10. package/dist/tools/definitions/broadcast-mailing-variants.js +107 -0
  11. package/dist/tools/definitions/broadcast-mailing-variants.js.map +1 -0
  12. package/dist/tools/definitions/getcourse-wave.d.ts +147 -0
  13. package/dist/tools/definitions/getcourse-wave.js +505 -0
  14. package/dist/tools/definitions/getcourse-wave.js.map +1 -0
  15. package/dist/tools/definitions/platform-setup.d.ts +114 -7
  16. package/dist/tools/definitions/platform-setup.js +106 -332
  17. package/dist/tools/definitions/platform-setup.js.map +1 -1
  18. package/dist/tools/definitions/scheduled-tasks.d.ts +4 -0
  19. package/dist/tools/definitions/scheduled-tasks.js +14 -2
  20. package/dist/tools/definitions/scheduled-tasks.js.map +1 -1
  21. package/dist/tools/definitions/session.js +6 -4
  22. package/dist/tools/definitions/session.js.map +1 -1
  23. package/dist/tools/definitions/shared.d.ts +53 -0
  24. package/dist/tools/definitions/shared.js +71 -0
  25. package/dist/tools/definitions/shared.js.map +1 -1
  26. package/dist/tools/definitions.d.ts +162 -14
  27. package/dist/tools/definitions.js +2 -0
  28. package/dist/tools/definitions.js.map +1 -1
  29. package/dist/tools/registry-mapping-cases-core.d.ts +34 -0
  30. package/dist/tools/registry-mapping-cases-core.js +700 -0
  31. package/dist/tools/registry-mapping-cases-core.js.map +1 -0
  32. package/dist/tools/registry-mapping-cases-platform.d.ts +2 -0
  33. package/dist/tools/registry-mapping-cases-platform.js +910 -0
  34. package/dist/tools/registry-mapping-cases-platform.js.map +1 -0
  35. package/dist/tools/registry.js +12 -6
  36. package/dist/tools/registry.js.map +1 -1
  37. package/package.json +1 -1
@@ -1,11 +1,4 @@
1
1
  export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
2
- workspace_id?: string | undefined;
3
- }> | import("./shared.js").ToolDefinition<{
4
- platform_id: string;
5
- }> | import("./shared.js").ToolDefinition<{
6
- platform_id: string;
7
- agent_id: string;
8
- }> | import("./shared.js").ToolDefinition<{
9
2
  platform_id: string;
10
3
  crm_id: string;
11
4
  channels: string[];
@@ -14,6 +7,10 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
14
7
  deal_id?: string | undefined;
15
8
  transport?: string | undefined;
16
9
  dialog_id?: string | undefined;
10
+ email?: string | undefined;
11
+ phone?: string | undefined;
12
+ first_name?: string | undefined;
13
+ last_name?: string | undefined;
17
14
  lead_last_active_at?: string | undefined;
18
15
  segment_entered_at?: string | undefined;
19
16
  }[];
@@ -21,7 +18,9 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
21
18
  launch_type?: "status" | "board" | "group" | undefined;
22
19
  funnel?: string | undefined;
23
20
  segment?: string | undefined;
21
+ segment_description?: string | undefined;
24
22
  cohort_label?: string | undefined;
23
+ mailing_variant_id?: string | undefined;
25
24
  message_variant?: string | undefined;
26
25
  found_count?: number | undefined;
27
26
  product_code?: string | undefined;
@@ -53,14 +52,22 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
53
52
  deal_id?: string | undefined;
54
53
  transport?: string | undefined;
55
54
  dialog_id?: string | undefined;
55
+ email?: string | undefined;
56
+ phone?: string | undefined;
57
+ first_name?: string | undefined;
58
+ last_name?: string | undefined;
56
59
  lead_last_active_at?: string | undefined;
57
60
  segment_entered_at?: string | undefined;
58
61
  }[];
59
62
  workspace_id?: string | undefined;
63
+ launch_key?: string | undefined;
64
+ dry_run_hash?: string | undefined;
60
65
  launch_type?: "status" | undefined;
61
66
  funnel?: string | undefined;
62
67
  segment?: string | undefined;
68
+ segment_description?: string | undefined;
63
69
  cohort_label?: string | undefined;
70
+ mailing_variant_id?: string | undefined;
64
71
  message_variant?: string | undefined;
65
72
  found_count?: number | undefined;
66
73
  product_code?: string | undefined;
@@ -89,6 +96,61 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
89
96
  status?: "PENDING" | "CLAIMED" | "SENT" | "SKIPPED" | "FAILED" | "UNKNOWN" | undefined;
90
97
  cursor?: string | undefined;
91
98
  limit?: number | undefined;
99
+ }> | import("./shared.js").ToolDefinition<{
100
+ batch_key: string;
101
+ platform_id: string;
102
+ crm_id: string;
103
+ segments: {
104
+ contacts: {
105
+ contact_id: string;
106
+ deal_id?: string | undefined;
107
+ transport?: string | undefined;
108
+ dialog_id?: string | undefined;
109
+ email?: string | undefined;
110
+ phone?: string | undefined;
111
+ first_name?: string | undefined;
112
+ last_name?: string | undefined;
113
+ lead_last_active_at?: string | undefined;
114
+ segment_entered_at?: string | undefined;
115
+ }[];
116
+ channels: string[];
117
+ deal_fields: Record<string, string | number | boolean | null>;
118
+ launch_type?: "status" | undefined;
119
+ funnel?: string | undefined;
120
+ segment?: string | undefined;
121
+ segment_description?: string | undefined;
122
+ cohort_label?: string | undefined;
123
+ mailing_variant_id?: string | undefined;
124
+ message_variant?: string | undefined;
125
+ found_count?: number | undefined;
126
+ product_code?: string | undefined;
127
+ product_name?: string | undefined;
128
+ preflight?: {
129
+ enabled?: boolean | undefined;
130
+ product_title?: string | undefined;
131
+ product_match_key?: string | undefined;
132
+ offer_id?: number | undefined;
133
+ other_product_active_deal_mode?: "warn" | "block" | "ignore" | undefined;
134
+ } | undefined;
135
+ check_messenger_presence?: boolean | undefined;
136
+ dedup_window_days?: number | undefined;
137
+ dedup_scope?: "any" | "same_funnel" | undefined;
138
+ chunk_size?: number | undefined;
139
+ }[];
140
+ workspace_id?: string | undefined;
141
+ confirm?: boolean | undefined;
142
+ explicit_confirmation?: true | undefined;
143
+ confirmation_reason?: string | undefined;
144
+ }> | import("./shared.js").ToolDefinition<{
145
+ batch_id: string;
146
+ workspace_id?: string | undefined;
147
+ }> | import("./shared.js").ToolDefinition<{
148
+ workspace_id?: string | undefined;
149
+ }> | import("./shared.js").ToolDefinition<{
150
+ platform_id: string;
151
+ }> | import("./shared.js").ToolDefinition<{
152
+ platform_id: string;
153
+ agent_id: string;
92
154
  }> | import("./shared.js").ToolDefinition<{
93
155
  platform_id: string;
94
156
  workspace_id?: string | undefined;
@@ -506,6 +568,29 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
506
568
  }[] | null | undefined;
507
569
  template_user_id?: number | null | undefined;
508
570
  template_deal_id?: number | null | undefined;
571
+ wave_field_bindings?: {
572
+ product_value_source: "CODE" | "NAME";
573
+ launch_type?: {
574
+ field_id: number;
575
+ field_label: string;
576
+ } | undefined;
577
+ product?: {
578
+ field_id: number;
579
+ field_label: string;
580
+ } | undefined;
581
+ segment?: {
582
+ field_id: number;
583
+ field_label: string;
584
+ } | undefined;
585
+ funnel?: {
586
+ field_id: number;
587
+ field_label: string;
588
+ } | undefined;
589
+ channels?: {
590
+ field_id: number;
591
+ field_label: string;
592
+ } | undefined;
593
+ } | null | undefined;
509
594
  field_variable_map?: {
510
595
  getcourse_field_name: string;
511
596
  variable_name: string;
@@ -556,6 +641,13 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
556
641
  }> | import("./shared.js").ToolDefinition<{
557
642
  agent_id: string;
558
643
  draft_id: string;
644
+ }> | import("./shared.js").ToolDefinition<{
645
+ agent_id: string;
646
+ draft_id?: string | undefined;
647
+ }> | import("./shared.js").ToolDefinition<{
648
+ agent_id: string;
649
+ compare_agent_id: string;
650
+ max_lines?: number | undefined;
559
651
  }> | import("./shared.js").ToolDefinition<{
560
652
  agent_id: string;
561
653
  draft_id: string;
@@ -567,6 +659,21 @@ export declare const platformSetupTools: (import("./shared.js").ToolDefinition<{
567
659
  replacement_text?: string | undefined;
568
660
  match_variants?: string[] | undefined;
569
661
  }[];
662
+ }> | import("./shared.js").ToolDefinition<{
663
+ agent_ids: string[];
664
+ operations: {
665
+ type: "replace_text" | "replace_all_text" | "insert_after_text" | "insert_before_text" | "remove_text" | "move_text_after_text";
666
+ id?: string | undefined;
667
+ target_text?: string | undefined;
668
+ anchor_text?: string | undefined;
669
+ replacement_text?: string | undefined;
670
+ match_variants?: string[] | undefined;
671
+ }[];
672
+ all_or_nothing?: boolean | undefined;
673
+ idempotency_key?: string | undefined;
674
+ platform_id?: string | undefined;
675
+ }> | import("./shared.js").ToolDefinition<{
676
+ run_id: string;
570
677
  }> | import("./shared.js").ToolDefinition<{
571
678
  prompt_text: string;
572
679
  workspace_id?: string | undefined;
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { refineFollowUpConfig } from './follow-up.js';
3
- import { agentOutputConfigSchema, agentRuntimeConfigSchema, agentScopeSchema, agentToolPatchSchema, anyObjectSchema, behaviorErrorHandlingSchema, behaviorGreetingSchema, behaviorMediaHandlingSchema, behaviorMessageFilterSchema, behaviorMessageSplittingSchema, behaviorOperatorScheduleSchema, behaviorPrivacySchema, behaviorTypingBehaviorSchema, behaviorUrlHandlingSchema, 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, platformWidgetMarketingSchema, routingRuleConditionSchema, routingRuleLeadStatusSchema, routingStrategySchema, setupConfirmation, triggerScopeSchema, uuidSchema, withInstructions, } from './shared.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, 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,312 +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
- defineTool({
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
- }),
83
+ ...getCourseWaveTools,
388
84
  defineTool({
389
85
  name: 'besales_wazzup_templates_list',
390
86
  requiredFields: ['platform_id'],
@@ -1181,42 +877,66 @@ export const platformSetupTools = [
1181
877
  path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
1182
878
  }),
1183
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
+ }),
1184
927
  defineTool({
1185
928
  name: 'besales_prompt_patch_apply',
1186
929
  requiredFields: ['agent_id', 'draft_id', 'operations'],
1187
930
  schema: z.object({
1188
931
  agent_id: uuidSchema,
1189
932
  draft_id: uuidSchema,
1190
- operations: z
1191
- .array(z.object({
1192
- id: z.string().optional(),
1193
- type: z.enum([
1194
- 'replace_text',
1195
- 'replace_all_text',
1196
- 'insert_after_text',
1197
- 'insert_before_text',
1198
- 'remove_text',
1199
- 'move_text_after_text',
1200
- ]),
1201
- target_text: z.string().optional(),
1202
- anchor_text: z.string().optional(),
1203
- replacement_text: z.string().optional(),
1204
- match_variants: z.array(z.string()).optional(),
1205
- }))
1206
- .min(1),
933
+ operations: z.array(promptPatchOperationSchema).min(1),
1207
934
  }),
1208
935
  createRequest: (input) => ({
1209
936
  method: 'PATCH',
1210
937
  path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}/operations`,
1211
938
  body: {
1212
- operations: input.operations.map((op) => compactObject({
1213
- id: op.id,
1214
- type: op.type,
1215
- targetText: op.target_text,
1216
- anchorText: op.anchor_text,
1217
- replacementText: op.replacement_text,
1218
- matchVariants: op.match_variants,
1219
- })),
939
+ operations: input.operations.map(mapPromptPatchOperationInput),
1220
940
  },
1221
941
  }),
1222
942
  enhanceResponse: (response) => withInstructions(response, [
@@ -1259,6 +979,60 @@ export const platformSetupTools = [
1259
979
  path: `/agents/${input.agent_id}/prompt/drafts/${input.draft_id}`,
1260
980
  }),
1261
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
+ }),
1262
1036
  // S2.6 extra — besales_prompt_lint (после patches, чтобы matched catalog order)
1263
1037
  defineTool({
1264
1038
  name: 'besales_prompt_lint',