@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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export declare const getCourseWaveTools: (import("./shared.js").ToolDefinition<{
|
|
2
|
+
platform_id: string;
|
|
3
|
+
crm_id: string;
|
|
4
|
+
channels: string[];
|
|
5
|
+
contacts: {
|
|
6
|
+
contact_id: string;
|
|
7
|
+
deal_id?: string | undefined;
|
|
8
|
+
transport?: string | undefined;
|
|
9
|
+
dialog_id?: string | undefined;
|
|
10
|
+
email?: string | undefined;
|
|
11
|
+
phone?: string | undefined;
|
|
12
|
+
first_name?: string | undefined;
|
|
13
|
+
last_name?: string | undefined;
|
|
14
|
+
lead_last_active_at?: string | undefined;
|
|
15
|
+
segment_entered_at?: string | undefined;
|
|
16
|
+
}[];
|
|
17
|
+
workspace_id?: string | undefined;
|
|
18
|
+
launch_type?: "status" | "board" | "group" | undefined;
|
|
19
|
+
funnel?: string | undefined;
|
|
20
|
+
segment?: string | undefined;
|
|
21
|
+
segment_description?: string | undefined;
|
|
22
|
+
cohort_label?: string | undefined;
|
|
23
|
+
mailing_variant_id?: string | undefined;
|
|
24
|
+
message_variant?: string | undefined;
|
|
25
|
+
found_count?: number | undefined;
|
|
26
|
+
product_code?: string | undefined;
|
|
27
|
+
product_name?: string | undefined;
|
|
28
|
+
deal_fields?: Record<string, string | number | boolean | null> | undefined;
|
|
29
|
+
pipeline_id?: number | undefined;
|
|
30
|
+
status_id?: number | undefined;
|
|
31
|
+
group_id?: number | undefined;
|
|
32
|
+
confirm?: boolean | undefined;
|
|
33
|
+
check_messenger_presence?: boolean | undefined;
|
|
34
|
+
dedup_window_days?: number | undefined;
|
|
35
|
+
dedup_scope?: "any" | "same_funnel" | undefined;
|
|
36
|
+
preflight?: {
|
|
37
|
+
enabled?: boolean | undefined;
|
|
38
|
+
product_title?: string | undefined;
|
|
39
|
+
product_match_key?: string | undefined;
|
|
40
|
+
offer_id?: number | undefined;
|
|
41
|
+
other_product_active_deal_mode?: "warn" | "block" | "ignore" | undefined;
|
|
42
|
+
} | undefined;
|
|
43
|
+
}> | import("./shared.js").ToolDefinition<{
|
|
44
|
+
job_id: string;
|
|
45
|
+
workspace_id?: string | undefined;
|
|
46
|
+
}> | import("./shared.js").ToolDefinition<{
|
|
47
|
+
platform_id: string;
|
|
48
|
+
crm_id: string;
|
|
49
|
+
channels: string[];
|
|
50
|
+
contacts: {
|
|
51
|
+
contact_id: string;
|
|
52
|
+
deal_id?: string | undefined;
|
|
53
|
+
transport?: string | undefined;
|
|
54
|
+
dialog_id?: string | undefined;
|
|
55
|
+
email?: string | undefined;
|
|
56
|
+
phone?: string | undefined;
|
|
57
|
+
first_name?: string | undefined;
|
|
58
|
+
last_name?: string | undefined;
|
|
59
|
+
lead_last_active_at?: string | undefined;
|
|
60
|
+
segment_entered_at?: string | undefined;
|
|
61
|
+
}[];
|
|
62
|
+
workspace_id?: string | undefined;
|
|
63
|
+
launch_key?: string | undefined;
|
|
64
|
+
dry_run_hash?: string | undefined;
|
|
65
|
+
launch_type?: "status" | undefined;
|
|
66
|
+
funnel?: string | undefined;
|
|
67
|
+
segment?: string | undefined;
|
|
68
|
+
segment_description?: string | undefined;
|
|
69
|
+
cohort_label?: string | undefined;
|
|
70
|
+
mailing_variant_id?: string | undefined;
|
|
71
|
+
message_variant?: string | undefined;
|
|
72
|
+
found_count?: number | undefined;
|
|
73
|
+
product_code?: string | undefined;
|
|
74
|
+
product_name?: string | undefined;
|
|
75
|
+
deal_fields?: Record<string, string | number | boolean | null> | undefined;
|
|
76
|
+
confirm?: boolean | undefined;
|
|
77
|
+
check_messenger_presence?: boolean | undefined;
|
|
78
|
+
dedup_window_days?: number | undefined;
|
|
79
|
+
dedup_scope?: "any" | "same_funnel" | undefined;
|
|
80
|
+
preflight?: {
|
|
81
|
+
enabled?: boolean | undefined;
|
|
82
|
+
product_title?: string | undefined;
|
|
83
|
+
product_match_key?: string | undefined;
|
|
84
|
+
offer_id?: number | undefined;
|
|
85
|
+
other_product_active_deal_mode?: "warn" | "block" | "ignore" | undefined;
|
|
86
|
+
} | undefined;
|
|
87
|
+
chunk_size?: number | undefined;
|
|
88
|
+
explicit_confirmation?: true | undefined;
|
|
89
|
+
confirmation_reason?: string | undefined;
|
|
90
|
+
}> | import("./shared.js").ToolDefinition<{
|
|
91
|
+
run_id: string;
|
|
92
|
+
workspace_id?: string | undefined;
|
|
93
|
+
}> | import("./shared.js").ToolDefinition<{
|
|
94
|
+
run_id: string;
|
|
95
|
+
workspace_id?: string | undefined;
|
|
96
|
+
status?: "PENDING" | "CLAIMED" | "SENT" | "SKIPPED" | "FAILED" | "UNKNOWN" | undefined;
|
|
97
|
+
cursor?: string | undefined;
|
|
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
|
+
}>)[];
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { compactObject, defineTool, uuidSchema, withInstructions } from './shared.js';
|
|
3
|
+
const BROADCAST_SEGMENT_DESCRIPTION_MAX_LENGTH = 4000;
|
|
4
|
+
const UNCERTAIN_WAVE_OUTCOME_INSTRUCTION = 'UNKNOWN и FAILED с неопределённым внешним исходом жёстко блокируются между волнами: repeat-режим и dedup_window_days=0 их не обходят. Повтор допустим только для FAILED, который подтверждённо возник до внешней отправки; остальные случаи требуют отдельного ручного разбора исхода.';
|
|
5
|
+
const getCourseWaveContactSchema = z.object({
|
|
6
|
+
contact_id: z.string(),
|
|
7
|
+
deal_id: z.string().optional(),
|
|
8
|
+
transport: z.string().optional(),
|
|
9
|
+
dialog_id: z.string().optional(),
|
|
10
|
+
email: z.string().email().max(320).optional(),
|
|
11
|
+
phone: z.string().max(64).optional(),
|
|
12
|
+
first_name: z.string().max(200).optional(),
|
|
13
|
+
last_name: z.string().max(200).optional(),
|
|
14
|
+
lead_last_active_at: z.string().datetime({ offset: true }).optional(),
|
|
15
|
+
segment_entered_at: z.string().datetime({ offset: true }).optional(),
|
|
16
|
+
});
|
|
17
|
+
const mapGetCourseWaveContact = (contact) => compactObject({
|
|
18
|
+
contactId: contact.contact_id,
|
|
19
|
+
dealId: contact.deal_id,
|
|
20
|
+
transport: contact.transport,
|
|
21
|
+
dialogId: contact.dialog_id,
|
|
22
|
+
email: contact.email,
|
|
23
|
+
phone: contact.phone,
|
|
24
|
+
firstName: contact.first_name,
|
|
25
|
+
lastName: contact.last_name,
|
|
26
|
+
leadLastActiveAt: contact.lead_last_active_at,
|
|
27
|
+
segmentEnteredAt: contact.segment_entered_at,
|
|
28
|
+
});
|
|
29
|
+
export const getCourseWaveTools = [
|
|
30
|
+
defineTool({
|
|
31
|
+
name: 'besales_getcourse_wave_import',
|
|
32
|
+
requiredFields: ['platform_id', 'crm_id', 'channels', 'contacts'],
|
|
33
|
+
defaultWorkspaceId: true,
|
|
34
|
+
schema: z
|
|
35
|
+
.object({
|
|
36
|
+
workspace_id: uuidSchema.optional(),
|
|
37
|
+
platform_id: uuidSchema,
|
|
38
|
+
crm_id: uuidSchema,
|
|
39
|
+
launch_type: z.enum(['status', 'board', 'group']).optional(),
|
|
40
|
+
channels: z.array(z.string()).min(1),
|
|
41
|
+
funnel: z.string().max(200).optional(),
|
|
42
|
+
segment: z.string().max(200).optional(),
|
|
43
|
+
segment_description: z.string().max(BROADCAST_SEGMENT_DESCRIPTION_MAX_LENGTH).optional(),
|
|
44
|
+
cohort_label: z.string().max(200).optional(),
|
|
45
|
+
mailing_variant_id: uuidSchema.optional(),
|
|
46
|
+
message_variant: z.string().max(120).optional(),
|
|
47
|
+
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
48
|
+
product_code: z
|
|
49
|
+
.string()
|
|
50
|
+
.trim()
|
|
51
|
+
.max(100)
|
|
52
|
+
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
53
|
+
.optional(),
|
|
54
|
+
product_name: z.string().max(200).optional(),
|
|
55
|
+
deal_fields: z
|
|
56
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
57
|
+
.optional(),
|
|
58
|
+
pipeline_id: z.number().int().positive().optional(),
|
|
59
|
+
status_id: z.number().int().positive().optional(),
|
|
60
|
+
group_id: z.number().int().positive().optional(),
|
|
61
|
+
contacts: z.array(getCourseWaveContactSchema).min(1).max(500),
|
|
62
|
+
confirm: z.boolean().optional(),
|
|
63
|
+
check_messenger_presence: z.boolean().optional(),
|
|
64
|
+
dedup_window_days: z.number().int().min(0).max(365).optional(),
|
|
65
|
+
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
66
|
+
preflight: z
|
|
67
|
+
.object({
|
|
68
|
+
enabled: z.boolean().optional(),
|
|
69
|
+
product_title: z.string().max(500).optional(),
|
|
70
|
+
product_match_key: z.string().max(500).optional(),
|
|
71
|
+
offer_id: z.number().int().positive().optional(),
|
|
72
|
+
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
73
|
+
})
|
|
74
|
+
.optional(),
|
|
75
|
+
})
|
|
76
|
+
.refine((input) => input.segment_description === undefined || Boolean(input.segment?.trim()), {
|
|
77
|
+
message: 'segment_description требует непустой segment',
|
|
78
|
+
path: ['segment_description'],
|
|
79
|
+
}),
|
|
80
|
+
createRequest: (input) => ({
|
|
81
|
+
method: 'POST',
|
|
82
|
+
path: `/workspaces/${input.workspace_id}/getcourse-waves/import`,
|
|
83
|
+
body: compactObject({
|
|
84
|
+
platformId: input.platform_id,
|
|
85
|
+
crmId: input.crm_id,
|
|
86
|
+
launchType: input.launch_type,
|
|
87
|
+
channels: input.channels,
|
|
88
|
+
funnel: input.funnel,
|
|
89
|
+
segment: input.segment,
|
|
90
|
+
segmentDescription: input.segment_description,
|
|
91
|
+
cohortLabel: input.cohort_label,
|
|
92
|
+
mailingVariantId: input.mailing_variant_id,
|
|
93
|
+
messageVariant: input.message_variant,
|
|
94
|
+
foundCount: input.found_count,
|
|
95
|
+
productCode: input.product_code,
|
|
96
|
+
productName: input.product_name,
|
|
97
|
+
dealFields: input.deal_fields,
|
|
98
|
+
pipelineId: input.pipeline_id,
|
|
99
|
+
statusId: input.status_id,
|
|
100
|
+
groupId: input.group_id,
|
|
101
|
+
dedupWindowDays: input.dedup_window_days,
|
|
102
|
+
dedupScope: input.dedup_scope,
|
|
103
|
+
contacts: input.contacts?.map(mapGetCourseWaveContact),
|
|
104
|
+
confirm: input.confirm,
|
|
105
|
+
checkMessengerPresence: input.check_messenger_presence,
|
|
106
|
+
preflight: input.preflight
|
|
107
|
+
? compactObject({
|
|
108
|
+
enabled: input.preflight.enabled,
|
|
109
|
+
productTitle: input.preflight.product_title,
|
|
110
|
+
productMatchKey: input.preflight.product_match_key,
|
|
111
|
+
offerId: input.preflight.offer_id,
|
|
112
|
+
otherProductActiveDealMode: input.preflight.other_product_active_deal_mode,
|
|
113
|
+
})
|
|
114
|
+
: undefined,
|
|
115
|
+
}),
|
|
116
|
+
}),
|
|
117
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
118
|
+
'Запуск outbound-волны GetCourse: Шаг 1 (импорт контактов + пин сделок) → Шаг 2 (отправка). По умолчанию launch_type=status (простановка полей → «Процесс» GetCourse шлёт welcome).',
|
|
119
|
+
'БЕЗ confirm=true — DRY-RUN: вернётся summary (totalContacts/withDeal/channels/dealFields/sample), НИЧЕГО не отправляется. Покажи summary пользователю и запускай реально только с confirm=true.',
|
|
120
|
+
'channels — значения GetCourseTransport: "VK" | "Telegram" | "MAX" | "SMS" | "Email" | "Site" (для аналитики «Канал волны»).',
|
|
121
|
+
'funnel — канонический ключ/название БИЗНЕС-воронки. Не передавай сюда имя файла, batch/resend или дату запуска. cohort_label — человекочитаемое имя конкретного запуска волны.',
|
|
122
|
+
'mailing_variant_id — ID именованного «Варианта рассылки» из besales_broadcast_mailing_variant_list/create. Backend tenant-scoped проверяет активность и сохраняет связь для фильтра аналитики.',
|
|
123
|
+
'segment_description — явное бизнес-правило отбора segment. Передавай только вместе с segment: новый сегмент создастся с описанием, существующий явно обновит его. Если описание не передать, сохранённый текст не изменится.',
|
|
124
|
+
'contacts[].segment_entered_at — точный ISO-момент попадания контакта в сегмент. contacts[].lead_last_active_at — только legacy/последняя активность; не используй её как замену segment_entered_at.',
|
|
125
|
+
'launch_type=status требует deal_fields = { "<числовой fieldId GetCourse>": значение } (одинаковые на всю волну). board требует pipeline_id+status_id, group — group_id.',
|
|
126
|
+
'preflight (default enabled=true) — pre-send гейт перед каждой строкой Шага 2: блокирует контакт, если уже есть активная/оплаченная сделка по этому продукту или проверка GetCourse упала. Передавай product_title/product_match_key или funnel; если контекста продукта нет вообще, backend отклонит запуск до создания job.',
|
|
127
|
+
'В ответе confirm-запуска — waveJobId. Опрашивай его через besales_getcourse_wave_status до status=COMPLETED (Шаг 2 идёт медленно, с паузами).',
|
|
128
|
+
'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.',
|
|
129
|
+
'dedup_window_days (default 30, 0–365) и dedup_scope (default any | same_funnel): any учитывает любую прошлую волну, same_funnel — ту же каноническую бизнес-воронку (legacy batch/resend-суффиксы игнорируются). 0 = выключить дедуп полностью — только для осознанного повторного запуска.',
|
|
130
|
+
]),
|
|
131
|
+
}),
|
|
132
|
+
defineTool({
|
|
133
|
+
name: 'besales_getcourse_wave_status',
|
|
134
|
+
requiredFields: ['job_id'],
|
|
135
|
+
defaultWorkspaceId: true,
|
|
136
|
+
schema: z.object({
|
|
137
|
+
workspace_id: uuidSchema.optional(),
|
|
138
|
+
job_id: uuidSchema,
|
|
139
|
+
}),
|
|
140
|
+
createRequest: (input) => ({
|
|
141
|
+
method: 'GET',
|
|
142
|
+
path: `/workspaces/${input.workspace_id}/getcourse-waves/${input.job_id}`,
|
|
143
|
+
}),
|
|
144
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
145
|
+
'Статус волны (джоб Шага 2): status (PROCESSING|COMPLETED), processedRows/successCount/failedCount, errorReasons (первые построчные ошибки), preflight aggregates.',
|
|
146
|
+
'COMPLETED + failedCount>0 — часть контактов не прошла (см. errorReasons: нет сделки, поле вне «Предложения» заказа и т.п.).',
|
|
147
|
+
'preflight.blocked/errors — контакты, которым НЕ отправляли; BroadcastRecipient для них не создаётся, поэтому received в аналитике не раздувается.',
|
|
148
|
+
]),
|
|
149
|
+
}),
|
|
150
|
+
defineTool({
|
|
151
|
+
name: 'besales_getcourse_wave_run_start',
|
|
152
|
+
requiredFields: ['platform_id', 'crm_id', 'channels', 'contacts'],
|
|
153
|
+
defaultWorkspaceId: true,
|
|
154
|
+
schema: z
|
|
155
|
+
.object({
|
|
156
|
+
workspace_id: uuidSchema.optional(),
|
|
157
|
+
platform_id: uuidSchema,
|
|
158
|
+
crm_id: uuidSchema,
|
|
159
|
+
launch_key: uuidSchema.optional(),
|
|
160
|
+
dry_run_hash: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
161
|
+
launch_type: z.literal('status').optional(),
|
|
162
|
+
channels: z.array(z.string()).min(1),
|
|
163
|
+
funnel: z.string().max(200).optional(),
|
|
164
|
+
segment: z.string().max(200).optional(),
|
|
165
|
+
segment_description: z.string().max(BROADCAST_SEGMENT_DESCRIPTION_MAX_LENGTH).optional(),
|
|
166
|
+
cohort_label: z.string().max(200).optional(),
|
|
167
|
+
mailing_variant_id: uuidSchema.optional(),
|
|
168
|
+
message_variant: z.string().max(120).optional(),
|
|
169
|
+
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
170
|
+
product_code: z
|
|
171
|
+
.string()
|
|
172
|
+
.trim()
|
|
173
|
+
.max(100)
|
|
174
|
+
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
175
|
+
.optional(),
|
|
176
|
+
product_name: z.string().max(200).optional(),
|
|
177
|
+
deal_fields: z
|
|
178
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
|
|
179
|
+
.optional(),
|
|
180
|
+
contacts: z.array(getCourseWaveContactSchema).min(1).max(5000),
|
|
181
|
+
confirm: z.boolean().optional(),
|
|
182
|
+
check_messenger_presence: z.boolean().optional(),
|
|
183
|
+
dedup_window_days: z.number().int().min(0).max(365).optional(),
|
|
184
|
+
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
185
|
+
preflight: z
|
|
186
|
+
.object({
|
|
187
|
+
enabled: z.boolean().optional(),
|
|
188
|
+
product_title: z.string().max(500).optional(),
|
|
189
|
+
product_match_key: z.string().max(500).optional(),
|
|
190
|
+
offer_id: z.number().int().positive().optional(),
|
|
191
|
+
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
192
|
+
})
|
|
193
|
+
.optional(),
|
|
194
|
+
chunk_size: z.number().int().min(1).max(500).optional(),
|
|
195
|
+
explicit_confirmation: z.literal(true).optional(),
|
|
196
|
+
confirmation_reason: z.string().optional(),
|
|
197
|
+
})
|
|
198
|
+
.refine((input) => input.segment_description === undefined || Boolean(input.segment?.trim()), {
|
|
199
|
+
message: 'segment_description требует непустой segment',
|
|
200
|
+
path: ['segment_description'],
|
|
201
|
+
})
|
|
202
|
+
.refine((input) => !input.confirm || input.explicit_confirmation === true, {
|
|
203
|
+
message: 'Реальный запуск требует explicit_confirmation=true: отправка волны необратима',
|
|
204
|
+
path: ['explicit_confirmation'],
|
|
205
|
+
})
|
|
206
|
+
.refine((input) => !input.confirm || Boolean(input.launch_key), {
|
|
207
|
+
message: 'confirm=true требует стабильный launch_key UUID',
|
|
208
|
+
path: ['launch_key'],
|
|
209
|
+
})
|
|
210
|
+
.refine((input) => !input.confirm || Boolean(input.dry_run_hash), {
|
|
211
|
+
message: 'confirm=true требует dry_run_hash из предыдущего dry-run',
|
|
212
|
+
path: ['dry_run_hash'],
|
|
213
|
+
}),
|
|
214
|
+
createRequest: (input) => ({
|
|
215
|
+
method: 'POST',
|
|
216
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs`,
|
|
217
|
+
body: compactObject({
|
|
218
|
+
platformId: input.platform_id,
|
|
219
|
+
crmId: input.crm_id,
|
|
220
|
+
launchKey: input.launch_key,
|
|
221
|
+
dryRunHash: input.dry_run_hash,
|
|
222
|
+
launchType: input.launch_type,
|
|
223
|
+
channels: input.channels,
|
|
224
|
+
funnel: input.funnel,
|
|
225
|
+
segment: input.segment,
|
|
226
|
+
segmentDescription: input.segment_description,
|
|
227
|
+
cohortLabel: input.cohort_label,
|
|
228
|
+
mailingVariantId: input.mailing_variant_id,
|
|
229
|
+
messageVariant: input.message_variant,
|
|
230
|
+
foundCount: input.found_count,
|
|
231
|
+
productCode: input.product_code,
|
|
232
|
+
productName: input.product_name,
|
|
233
|
+
dealFields: input.deal_fields,
|
|
234
|
+
contacts: input.contacts?.map(mapGetCourseWaveContact),
|
|
235
|
+
confirm: input.confirm,
|
|
236
|
+
checkMessengerPresence: input.check_messenger_presence,
|
|
237
|
+
dedupWindowDays: input.dedup_window_days,
|
|
238
|
+
dedupScope: input.dedup_scope,
|
|
239
|
+
preflight: input.preflight
|
|
240
|
+
? compactObject({
|
|
241
|
+
enabled: input.preflight.enabled,
|
|
242
|
+
productTitle: input.preflight.product_title,
|
|
243
|
+
productMatchKey: input.preflight.product_match_key,
|
|
244
|
+
offerId: input.preflight.offer_id,
|
|
245
|
+
otherProductActiveDealMode: input.preflight.other_product_active_deal_mode,
|
|
246
|
+
})
|
|
247
|
+
: undefined,
|
|
248
|
+
chunkSize: input.chunk_size,
|
|
249
|
+
}),
|
|
250
|
+
}),
|
|
251
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
252
|
+
'Durable-прогон сам режет когорту на куски и отправляет их последовательно, сам ждёт освобождения лока импорта и переживает рестарт сервиса. Опрашивай ОДИН прогон через besales_getcourse_wave_run_status, не собирай волну вручную.',
|
|
253
|
+
'mailing_variant_id — ID именованного «Варианта рассылки» из besales_broadcast_mailing_variant_list/create. Backend tenant-scoped проверяет активность и сохраняет связь для фильтра аналитики.',
|
|
254
|
+
'БЕЗ confirm=true — DRY-RUN: возвращается проверка, но прогон НЕ создаётся и ничего не отправляется.',
|
|
255
|
+
'Реальный запуск требует confirm=true, explicit_confirmation=true, launch_key и dry_run_hash из последнего dry-run. Сгенерируй launch_key один раз на намерение и повторяй ТОТ ЖЕ ключ при сетевом ретрае.',
|
|
256
|
+
'Поддерживается ТОЛЬКО launch_type=status. Для board/group используй старый besales_getcourse_wave_import.',
|
|
257
|
+
'HTTP 409 означает, что на платформе уже идёт активный прогон: в ответе есть его runId — не создавай новую волну, опрашивай этот runId.',
|
|
258
|
+
'Статус прогона отдаёт только агрегаты. Полный построчный разбор получай через besales_getcourse_wave_run_contacts с курсорной пагинацией.',
|
|
259
|
+
'Исходы контактов: SENT — отправлено; SKIPPED — дедуп, нет мессенджера или preflight заблокировал; FAILED — построчная ошибка; UNKNOWN — исход НЕИЗВЕСТЕН: кусок был прерван, но отправка могла состояться.',
|
|
260
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
261
|
+
]),
|
|
262
|
+
}),
|
|
263
|
+
defineTool({
|
|
264
|
+
name: 'besales_getcourse_wave_run_status',
|
|
265
|
+
requiredFields: ['run_id'],
|
|
266
|
+
defaultWorkspaceId: true,
|
|
267
|
+
schema: z.object({
|
|
268
|
+
workspace_id: uuidSchema.optional(),
|
|
269
|
+
run_id: uuidSchema,
|
|
270
|
+
}),
|
|
271
|
+
createRequest: (input) => ({
|
|
272
|
+
method: 'GET',
|
|
273
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}`,
|
|
274
|
+
}),
|
|
275
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
276
|
+
'Это единый агрегированный статус durable-прогона: он сам последовательно обрабатывает куски, ждёт лок импорта и продолжает после рестарта сервиса. Не собирай и не запускай куски вручную.',
|
|
277
|
+
'Статус содержит только агрегаты. Для полного построчного разбора используй besales_getcourse_wave_run_contacts с курсорной пагинацией.',
|
|
278
|
+
'SENT — отправлено; SKIPPED — дедуп, нет мессенджера или блокировка preflight; FAILED — построчная ошибка; UNKNOWN — исход НЕИЗВЕСТЕН после прерванного куска, отправка могла состояться.',
|
|
279
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
280
|
+
]),
|
|
281
|
+
}),
|
|
282
|
+
defineTool({
|
|
283
|
+
name: 'besales_getcourse_wave_run_contacts',
|
|
284
|
+
requiredFields: ['run_id'],
|
|
285
|
+
defaultWorkspaceId: true,
|
|
286
|
+
schema: z.object({
|
|
287
|
+
workspace_id: uuidSchema.optional(),
|
|
288
|
+
run_id: uuidSchema,
|
|
289
|
+
status: z
|
|
290
|
+
.enum(['PENDING', 'CLAIMED', 'SENT', 'SKIPPED', 'FAILED', 'UNKNOWN'])
|
|
291
|
+
.optional(),
|
|
292
|
+
cursor: uuidSchema.optional(),
|
|
293
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
294
|
+
}),
|
|
295
|
+
createRequest: (input) => {
|
|
296
|
+
const params = new URLSearchParams();
|
|
297
|
+
if (input.status)
|
|
298
|
+
params.set('status', input.status);
|
|
299
|
+
if (input.cursor)
|
|
300
|
+
params.set('cursor', input.cursor);
|
|
301
|
+
if (input.limit !== undefined)
|
|
302
|
+
params.set('limit', String(input.limit));
|
|
303
|
+
const query = params.toString();
|
|
304
|
+
return {
|
|
305
|
+
method: 'GET',
|
|
306
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}/contacts${query ? `?${query}` : ''}`,
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
310
|
+
'Полный построчный разбор durable-прогона. Продолжай курсорную пагинацию по nextCursor до конца; агрегированный besales_getcourse_wave_run_status строк контактов не возвращает.',
|
|
311
|
+
'Фильтр status: PENDING | CLAIMED | SENT | SKIPPED | FAILED | UNKNOWN. SKIPPED означает дедуп, отсутствие мессенджера или блокировку preflight; FAILED — построчную ошибку.',
|
|
312
|
+
'UNKNOWN означает, что исход НЕИЗВЕСТЕН: кусок был прерван, но отправка могла состояться.',
|
|
313
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
314
|
+
]),
|
|
315
|
+
}),
|
|
316
|
+
defineTool({
|
|
317
|
+
name: 'besales_getcourse_wave_run_cancel',
|
|
318
|
+
requiredFields: ['run_id'],
|
|
319
|
+
defaultWorkspaceId: true,
|
|
320
|
+
schema: z.object({
|
|
321
|
+
workspace_id: uuidSchema.optional(),
|
|
322
|
+
run_id: uuidSchema,
|
|
323
|
+
}),
|
|
324
|
+
createRequest: (input) => ({
|
|
325
|
+
method: 'POST',
|
|
326
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/${input.run_id}/cancel`,
|
|
327
|
+
}),
|
|
328
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
329
|
+
'Отмена запрещает прогону заявлять новые куски. Уже заявленный кусок может завершиться; после отмены опроси besales_getcourse_wave_run_status.',
|
|
330
|
+
'Для полного построчного результата используй besales_getcourse_wave_run_contacts с курсорной пагинацией; статус прогона отдаёт только агрегаты.',
|
|
331
|
+
'UNKNOWN — исход НЕИЗВЕСТЕН после прерванного куска, отправка могла состояться.',
|
|
332
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
333
|
+
]),
|
|
334
|
+
}),
|
|
335
|
+
defineTool({
|
|
336
|
+
name: 'besales_getcourse_wave_run_batch_start',
|
|
337
|
+
requiredFields: ['batch_key', 'platform_id', 'crm_id', 'segments'],
|
|
338
|
+
defaultWorkspaceId: true,
|
|
339
|
+
schema: z
|
|
340
|
+
.object({
|
|
341
|
+
workspace_id: uuidSchema.optional(),
|
|
342
|
+
batch_key: z.string(),
|
|
343
|
+
platform_id: uuidSchema,
|
|
344
|
+
crm_id: uuidSchema,
|
|
345
|
+
segments: z
|
|
346
|
+
.array(z
|
|
347
|
+
.object({
|
|
348
|
+
// Имя поля обязано совпадать с WaveLaunchParamsDto.launchType: у бэкенда
|
|
349
|
+
// включён forbidNonWhitelisted, поэтому лишний ключ роняет весь батч в 400.
|
|
350
|
+
launch_type: z.literal('status').optional(),
|
|
351
|
+
contacts: z.array(getCourseWaveContactSchema).min(1).max(2000),
|
|
352
|
+
channels: z.array(z.string()).min(1),
|
|
353
|
+
deal_fields: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
|
354
|
+
funnel: z.string().max(200).optional(),
|
|
355
|
+
segment: z.string().max(200).optional(),
|
|
356
|
+
segment_description: z
|
|
357
|
+
.string()
|
|
358
|
+
.max(BROADCAST_SEGMENT_DESCRIPTION_MAX_LENGTH)
|
|
359
|
+
.optional(),
|
|
360
|
+
cohort_label: z.string().max(200).optional(),
|
|
361
|
+
mailing_variant_id: uuidSchema.optional(),
|
|
362
|
+
message_variant: z.string().max(120).optional(),
|
|
363
|
+
found_count: z.number().int().min(0).max(10_000_000).optional(),
|
|
364
|
+
product_code: z
|
|
365
|
+
.string()
|
|
366
|
+
.trim()
|
|
367
|
+
.max(100)
|
|
368
|
+
.regex(/^[^\s/]+$/, 'product_code: без пробелов и «/»')
|
|
369
|
+
.optional(),
|
|
370
|
+
product_name: z.string().max(200).optional(),
|
|
371
|
+
preflight: z
|
|
372
|
+
.object({
|
|
373
|
+
enabled: z.boolean().optional(),
|
|
374
|
+
product_title: z.string().max(500).optional(),
|
|
375
|
+
product_match_key: z.string().max(500).optional(),
|
|
376
|
+
offer_id: z.number().int().positive().optional(),
|
|
377
|
+
other_product_active_deal_mode: z.enum(['warn', 'block', 'ignore']).optional(),
|
|
378
|
+
})
|
|
379
|
+
.optional(),
|
|
380
|
+
check_messenger_presence: z.boolean().optional(),
|
|
381
|
+
dedup_window_days: z.number().int().min(1).max(365).optional(),
|
|
382
|
+
dedup_scope: z.enum(['any', 'same_funnel']).optional(),
|
|
383
|
+
chunk_size: z.number().int().min(1).max(500).optional(),
|
|
384
|
+
})
|
|
385
|
+
.strict()
|
|
386
|
+
.refine((segment) => segment.segment_description === undefined || Boolean(segment.segment?.trim()), {
|
|
387
|
+
message: 'segment_description требует непустой segment',
|
|
388
|
+
path: ['segment_description'],
|
|
389
|
+
}))
|
|
390
|
+
.min(1)
|
|
391
|
+
.max(10)
|
|
392
|
+
.refine((segments) => segments.reduce((total, segment) => total + segment.contacts.length, 0) <= 5000, {
|
|
393
|
+
message: 'В батче может быть не более 5000 контактов суммарно',
|
|
394
|
+
}),
|
|
395
|
+
confirm: z.boolean().optional(),
|
|
396
|
+
explicit_confirmation: z.literal(true).optional(),
|
|
397
|
+
confirmation_reason: z.string().optional(),
|
|
398
|
+
})
|
|
399
|
+
.refine((input) => !input.confirm || input.explicit_confirmation === true, {
|
|
400
|
+
message: 'Реальный запуск требует explicit_confirmation=true: отправка волны необратима',
|
|
401
|
+
path: ['explicit_confirmation'],
|
|
402
|
+
}),
|
|
403
|
+
createRequest: (input) => ({
|
|
404
|
+
method: 'POST',
|
|
405
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batch`,
|
|
406
|
+
body: compactObject({
|
|
407
|
+
batchKey: input.batch_key,
|
|
408
|
+
platformId: input.platform_id,
|
|
409
|
+
crmId: input.crm_id,
|
|
410
|
+
segments: input.segments.map((segment) => compactObject({
|
|
411
|
+
launchType: segment.launch_type,
|
|
412
|
+
contacts: segment.contacts.map(mapGetCourseWaveContact),
|
|
413
|
+
channels: segment.channels,
|
|
414
|
+
dealFields: segment.deal_fields,
|
|
415
|
+
funnel: segment.funnel,
|
|
416
|
+
segment: segment.segment,
|
|
417
|
+
segmentDescription: segment.segment_description,
|
|
418
|
+
cohortLabel: segment.cohort_label,
|
|
419
|
+
mailingVariantId: segment.mailing_variant_id,
|
|
420
|
+
messageVariant: segment.message_variant,
|
|
421
|
+
foundCount: segment.found_count,
|
|
422
|
+
productCode: segment.product_code,
|
|
423
|
+
productName: segment.product_name,
|
|
424
|
+
preflight: segment.preflight
|
|
425
|
+
? compactObject({
|
|
426
|
+
enabled: segment.preflight.enabled,
|
|
427
|
+
productTitle: segment.preflight.product_title,
|
|
428
|
+
productMatchKey: segment.preflight.product_match_key,
|
|
429
|
+
offerId: segment.preflight.offer_id,
|
|
430
|
+
otherProductActiveDealMode: segment.preflight.other_product_active_deal_mode,
|
|
431
|
+
})
|
|
432
|
+
: undefined,
|
|
433
|
+
checkMessengerPresence: segment.check_messenger_presence,
|
|
434
|
+
dedupWindowDays: segment.dedup_window_days,
|
|
435
|
+
dedupScope: segment.dedup_scope,
|
|
436
|
+
chunkSize: segment.chunk_size,
|
|
437
|
+
})),
|
|
438
|
+
confirm: input.confirm,
|
|
439
|
+
}),
|
|
440
|
+
}),
|
|
441
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
442
|
+
'Очередь исполняет сегменты ПОСЛЕДОВАТЕЛЬНО: в один момент на платформе идёт только один прогон. Опрашивай ОДИН besales_getcourse_wave_run_batch_status, не собирай сегменты и их статусы вручную.',
|
|
443
|
+
'Для каждого сегмента mailing_variant_id — ID именованного «Варианта рассылки» из besales_broadcast_mailing_variant_list/create. Перед запуском backend повторно tenant-scoped проверяет активность всех вариантов под транзакционным lock.',
|
|
444
|
+
'batch_key сгенерируй ОДИН раз на попытку и ПЕРЕИСПОЛЬЗУЙ при ретрае: это ключ идемпотентности. Повтор с тем же ключом вернёт существующий батч; новый ключ создаст ВТОРУЮ копию кампании, которую дедуп не остановит.',
|
|
445
|
+
'Если повторить тот же batch_key с ИЗМЕНЁННЫМ составом сегментов, сервер вернёт HTTP 409 «ключ занят другим составом». Не меняй состав при ретрае — сначала прочитай статус существующего батча.',
|
|
446
|
+
'HTTP 400 отклоняет батч до запуска, если контакт есть в двух сегментах, пересекается с незавершённым прогоном платформы или ему уже отправляли за последние 30 дней. В ответе будут пары contactId + сегменты либо конфликтующий runId; исправь состав, а не обходи проверку новым ключом.',
|
|
447
|
+
'Два подряд отказавших сегмента ставят батч на паузу. Продолжай только через besales_getcourse_wave_run_batch_resume после проверки причин отказов.',
|
|
448
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
449
|
+
'БЕЗ confirm=true — DRY-RUN: батч не создаётся и ничего не отправляется. Реальный запуск дополнительно требует MCP-only explicit_confirmation=true; это поле в HTTP-запрос не передаётся.',
|
|
450
|
+
]),
|
|
451
|
+
}),
|
|
452
|
+
defineTool({
|
|
453
|
+
name: 'besales_getcourse_wave_run_batch_status',
|
|
454
|
+
requiredFields: ['batch_id'],
|
|
455
|
+
defaultWorkspaceId: true,
|
|
456
|
+
schema: z.object({
|
|
457
|
+
workspace_id: uuidSchema.optional(),
|
|
458
|
+
batch_id: uuidSchema,
|
|
459
|
+
}),
|
|
460
|
+
createRequest: (input) => ({
|
|
461
|
+
method: 'GET',
|
|
462
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}`,
|
|
463
|
+
}),
|
|
464
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
465
|
+
'Это ЕДИНСТВЕННЫЙ агрегированный статус очереди: она запускает сегменты последовательно и держит только один прогон на платформу. Опрашивай этот batch-status, а не статусы сегментов вручную.',
|
|
466
|
+
'Если батч на паузе после двух подряд отказавших сегментов, сначала проверь причины, затем снимай паузу только besales_getcourse_wave_run_batch_resume.',
|
|
467
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
468
|
+
]),
|
|
469
|
+
}),
|
|
470
|
+
defineTool({
|
|
471
|
+
name: 'besales_getcourse_wave_run_batch_cancel',
|
|
472
|
+
requiredFields: ['batch_id'],
|
|
473
|
+
defaultWorkspaceId: true,
|
|
474
|
+
schema: z.object({
|
|
475
|
+
workspace_id: uuidSchema.optional(),
|
|
476
|
+
batch_id: uuidSchema,
|
|
477
|
+
}),
|
|
478
|
+
createRequest: (input) => ({
|
|
479
|
+
method: 'POST',
|
|
480
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}/cancel`,
|
|
481
|
+
}),
|
|
482
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
483
|
+
'Отмена останавливает очередь батча; после неё опроси ОДИН besales_getcourse_wave_run_batch_status, не собирая сегменты вручную.',
|
|
484
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
485
|
+
]),
|
|
486
|
+
}),
|
|
487
|
+
defineTool({
|
|
488
|
+
name: 'besales_getcourse_wave_run_batch_resume',
|
|
489
|
+
requiredFields: ['batch_id'],
|
|
490
|
+
defaultWorkspaceId: true,
|
|
491
|
+
schema: z.object({
|
|
492
|
+
workspace_id: uuidSchema.optional(),
|
|
493
|
+
batch_id: uuidSchema,
|
|
494
|
+
}),
|
|
495
|
+
createRequest: (input) => ({
|
|
496
|
+
method: 'POST',
|
|
497
|
+
path: `/workspaces/${input.workspace_id}/getcourse-wave-runs/batches/${input.batch_id}/resume`,
|
|
498
|
+
}),
|
|
499
|
+
enhanceResponse: (response) => withInstructions(response, [
|
|
500
|
+
'Снимает паузу батча, которую вызывают два подряд отказавших сегмента. После resume опрашивай ОДИН besales_getcourse_wave_run_batch_status: очередь продолжит сегменты последовательно, один прогон на платформу.',
|
|
501
|
+
UNCERTAIN_WAVE_OUTCOME_INSTRUCTION,
|
|
502
|
+
]),
|
|
503
|
+
}),
|
|
504
|
+
];
|
|
505
|
+
//# sourceMappingURL=getcourse-wave.js.map
|