@besales/mcp 0.1.0 → 0.11.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.
Files changed (94) hide show
  1. package/README.md +272 -17
  2. package/dist/auth/connection-store.d.ts +58 -0
  3. package/dist/auth/connection-store.js +208 -0
  4. package/dist/auth/connection-store.js.map +1 -0
  5. package/dist/auth/oauth-client.d.ts +27 -2
  6. package/dist/auth/oauth-client.js +62 -11
  7. package/dist/auth/oauth-client.js.map +1 -1
  8. package/dist/auth/session-workspace.d.ts +2 -0
  9. package/dist/auth/session-workspace.js +20 -0
  10. package/dist/auth/session-workspace.js.map +1 -0
  11. package/dist/auth/token-storage.d.ts +19 -5
  12. package/dist/auth/token-storage.js +11 -6
  13. package/dist/auth/token-storage.js.map +1 -1
  14. package/dist/cli.d.ts +2 -7
  15. package/dist/cli.js +111 -33
  16. package/dist/cli.js.map +1 -1
  17. package/dist/http/api-client.d.ts +4 -13
  18. package/dist/http/api-client.js +18 -18
  19. package/dist/http/api-client.js.map +1 -1
  20. package/dist/index.d.ts +8 -6
  21. package/dist/index.js +3 -2
  22. package/dist/index.js.map +1 -1
  23. package/dist/instructions/server-instructions.d.ts +15 -0
  24. package/dist/instructions/server-instructions.js +243 -0
  25. package/dist/instructions/server-instructions.js.map +1 -0
  26. package/dist/package-metadata.js +7 -1
  27. package/dist/package-metadata.js.map +1 -1
  28. package/dist/resources/concepts/feedback-sheets.md +77 -0
  29. package/dist/resources/concepts/sandbox.md +13 -0
  30. package/dist/resources/concepts/workbook-classification.md +241 -0
  31. package/dist/resources/docs/agent-behavior.md +393 -0
  32. package/dist/resources/docs/crm-integration.md +535 -0
  33. package/dist/resources/docs/files-and-uploads.md +295 -0
  34. package/dist/resources/docs/knowledge-base.md +521 -0
  35. package/dist/resources/docs/pipeline-builder.md +221 -0
  36. package/dist/resources/docs/pipeline-settings-deep.md +221 -0
  37. package/dist/resources/docs/platforms.md +513 -0
  38. package/dist/resources/docs/prompt-anatomy.md +298 -0
  39. package/dist/resources/docs/prompt-principles.md +289 -0
  40. package/dist/resources/registry.js +34 -12
  41. package/dist/resources/registry.js.map +1 -1
  42. package/dist/resources/workflows/compare-models.md +46 -0
  43. package/dist/resources/workflows/connect-crm-from-scratch.md +89 -0
  44. package/dist/resources/workflows/connect-datasource-from-scratch.md +92 -0
  45. package/dist/resources/workflows/extract-from-document.md +36 -0
  46. package/dist/resources/workflows/iterate-with-sandbox.md +31 -0
  47. package/dist/resources/workflows/platform-setup-from-scratch.md +113 -0
  48. package/dist/resources/workflows/production-readiness-check.md +41 -0
  49. package/dist/schemas/mcp-tools.json +2636 -182
  50. package/dist/server.js +2 -0
  51. package/dist/server.js.map +1 -1
  52. package/dist/tools/definitions/agent-design.d.ts +215 -0
  53. package/dist/tools/definitions/agent-design.js +643 -0
  54. package/dist/tools/definitions/agent-design.js.map +1 -0
  55. package/dist/tools/definitions/crm-platform.d.ts +211 -0
  56. package/dist/tools/definitions/crm-platform.js +1070 -0
  57. package/dist/tools/definitions/crm-platform.js.map +1 -0
  58. package/dist/tools/definitions/datasource.d.ts +40 -0
  59. package/dist/tools/definitions/datasource.js +196 -0
  60. package/dist/tools/definitions/datasource.js.map +1 -0
  61. package/dist/tools/definitions/knowledge.d.ts +215 -0
  62. package/dist/tools/definitions/knowledge.js +782 -0
  63. package/dist/tools/definitions/knowledge.js.map +1 -0
  64. package/dist/tools/definitions/model-comparison.d.ts +25 -0
  65. package/dist/tools/definitions/model-comparison.js +101 -0
  66. package/dist/tools/definitions/model-comparison.js.map +1 -0
  67. package/dist/tools/definitions/platform-setup.d.ts +412 -0
  68. package/dist/tools/definitions/platform-setup.js +738 -0
  69. package/dist/tools/definitions/platform-setup.js.map +1 -0
  70. package/dist/tools/definitions/session.d.ts +11 -0
  71. package/dist/tools/definitions/session.js +86 -0
  72. package/dist/tools/definitions/session.js.map +1 -0
  73. package/dist/tools/definitions/shared.d.ts +742 -0
  74. package/dist/tools/definitions/shared.js +773 -0
  75. package/dist/tools/definitions/shared.js.map +1 -0
  76. package/dist/tools/definitions.d.ts +873 -88
  77. package/dist/tools/definitions.js +14 -856
  78. package/dist/tools/definitions.js.map +1 -1
  79. package/dist/tools/registry.d.ts +3 -1
  80. package/dist/tools/registry.js +90 -11
  81. package/dist/tools/registry.js.map +1 -1
  82. package/dist/tools/result.d.ts +1 -1
  83. package/dist/tools/result.js +12 -4
  84. package/dist/tools/result.js.map +1 -1
  85. package/dist/utils/logger.js +2 -1
  86. package/dist/utils/logger.js.map +1 -1
  87. package/docs/host-setup.md +34 -15
  88. package/package.json +2 -2
  89. package/scripts/install-claude-desktop.js +89 -11
  90. package/scripts/mock-api-server.js +1 -1
  91. package/scripts/mock-credentials.js +49 -6
  92. package/dist/types/api-contract.gen.d.ts +0 -6975
  93. package/dist/types/api-contract.gen.js +0 -6
  94. package/dist/types/api-contract.gen.js.map +0 -1
@@ -0,0 +1,1070 @@
1
+ import { z } from 'zod';
2
+ import { MAX_FILE_UPLOAD_BYTES, apiKeyPlatformTypeSchema, buildFileListQuery, buildVariableListQuery, buildVariableScopeQuery, compactObject, crmFieldMappingItemSchema, defineTool, mcpFileSourceSchema, oauthPlatformTypeSchema, optionalAnyObjectSchema, provisionableCrmTypeSchema, storedFileTypeSchema, uuidSchema, withInstructions, } from './shared.js';
3
+ export const crmPlatformTools = [
4
+ // ============================================================
5
+ // v1.5 Increment 4a — Unified Variables API (5 tools, v2-shaped scope discriminator)
6
+ // ============================================================
7
+ // Backend wraps two v1 tables (WorkspaceVariable + CustomVariable) под единый MCP shape.
8
+ // При v2 cutover (spec 23) обе таблицы → один `variables` с тем же `scope` discriminator —
9
+ // MCP shape работает без изменений.
10
+ defineTool({
11
+ name: 'besales_variable_list',
12
+ requiredFields: ['scope'],
13
+ defaultWorkspaceId: true,
14
+ schema: z
15
+ .object({
16
+ workspace_id: uuidSchema.optional(),
17
+ scope: z.enum(['WORKSPACE', 'CHANNEL']),
18
+ platform_id: uuidSchema.optional(),
19
+ filter: z.enum(['all', 'variables', 'constants']).optional(),
20
+ })
21
+ .strict()
22
+ .superRefine((input, ctx) => {
23
+ if (input.scope === 'CHANNEL' && !input.platform_id) {
24
+ ctx.addIssue({
25
+ code: z.ZodIssueCode.custom,
26
+ message: 'platform_id is required when scope=CHANNEL',
27
+ path: ['platform_id'],
28
+ });
29
+ }
30
+ if (input.scope === 'WORKSPACE' && input.platform_id) {
31
+ ctx.addIssue({
32
+ code: z.ZodIssueCode.custom,
33
+ message: 'platform_id is forbidden when scope=WORKSPACE',
34
+ path: ['platform_id'],
35
+ });
36
+ }
37
+ }),
38
+ createRequest: (input) => ({
39
+ method: 'GET',
40
+ path: `/workspaces/${input.workspace_id}/variables${buildVariableListQuery(input.scope, input.platform_id, input.filter)}`,
41
+ }),
42
+ enhanceResponse: (response) => withInstructions(response, [
43
+ 'WORKSPACE scope возвращает глобальные переменные workspace (доступны всем платформам).',
44
+ 'CHANNEL scope требует platform_id; filter=variables (исключить константы), filter=constants (только константы), filter=all (по умолчанию).',
45
+ ]),
46
+ }),
47
+ defineTool({
48
+ name: 'besales_variable_create',
49
+ requiredFields: ['scope', 'name', 'type'],
50
+ defaultWorkspaceId: true,
51
+ schema: z
52
+ .object({
53
+ workspace_id: uuidSchema.optional(),
54
+ scope: z.enum(['WORKSPACE', 'CHANNEL']),
55
+ name: z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, {
56
+ message: 'name must be ASCII letters/digits/underscores; start with letter or _',
57
+ }),
58
+ description: z.string().optional(),
59
+ type: z.enum(['TEXT', 'NUMBER', 'FLOAT', 'BOOLEAN']),
60
+ // WORKSPACE-only
61
+ value: z.string().optional(),
62
+ // CHANNEL-only
63
+ platform_id: uuidSchema.optional(),
64
+ is_constant: z.boolean().optional(),
65
+ constant_value: z.string().optional(),
66
+ })
67
+ .strict()
68
+ .superRefine((input, ctx) => {
69
+ if (input.scope === 'WORKSPACE') {
70
+ if (input.platform_id) {
71
+ ctx.addIssue({
72
+ code: z.ZodIssueCode.custom,
73
+ message: 'platform_id is forbidden when scope=WORKSPACE',
74
+ path: ['platform_id'],
75
+ });
76
+ }
77
+ if (input.is_constant !== undefined || input.constant_value !== undefined) {
78
+ ctx.addIssue({
79
+ code: z.ZodIssueCode.custom,
80
+ message: 'is_constant and constant_value are forbidden when scope=WORKSPACE',
81
+ path: ['is_constant'],
82
+ });
83
+ }
84
+ }
85
+ else {
86
+ // CHANNEL
87
+ if (!input.platform_id) {
88
+ ctx.addIssue({
89
+ code: z.ZodIssueCode.custom,
90
+ message: 'platform_id is required when scope=CHANNEL',
91
+ path: ['platform_id'],
92
+ });
93
+ }
94
+ if (input.value !== undefined) {
95
+ ctx.addIssue({
96
+ code: z.ZodIssueCode.custom,
97
+ message: 'value is forbidden when scope=CHANNEL — use constant_value if isConstant=true',
98
+ path: ['value'],
99
+ });
100
+ }
101
+ if (input.is_constant === true && !input.constant_value) {
102
+ ctx.addIssue({
103
+ code: z.ZodIssueCode.custom,
104
+ message: 'constant_value is required when scope=CHANNEL and is_constant=true',
105
+ path: ['constant_value'],
106
+ });
107
+ }
108
+ }
109
+ }),
110
+ createRequest: (input) => ({
111
+ method: 'POST',
112
+ path: `/workspaces/${input.workspace_id}/variables`,
113
+ body: compactObject({
114
+ scope: input.scope,
115
+ name: input.name,
116
+ description: input.description,
117
+ type: input.type,
118
+ value: input.value,
119
+ platformId: input.platform_id,
120
+ isConstant: input.is_constant,
121
+ constantValue: input.constant_value,
122
+ }),
123
+ }),
124
+ enhanceResponse: (response) => withInstructions(response, [
125
+ 'name, scope, is_constant — immutable post-create (используй delete + create для смены).',
126
+ 'WORKSPACE variables референсятся в шаблонах как {{{name}}} (triple braces), CHANNEL constants — {{name}} (double).',
127
+ ]),
128
+ }),
129
+ defineTool({
130
+ name: 'besales_variable_get',
131
+ requiredFields: ['variable_id', 'scope'],
132
+ defaultWorkspaceId: true,
133
+ schema: z
134
+ .object({
135
+ workspace_id: uuidSchema.optional(),
136
+ variable_id: uuidSchema,
137
+ scope: z.enum(['WORKSPACE', 'CHANNEL']),
138
+ platform_id: uuidSchema.optional(),
139
+ })
140
+ .strict()
141
+ .superRefine((input, ctx) => {
142
+ if (input.scope === 'CHANNEL' && !input.platform_id) {
143
+ ctx.addIssue({
144
+ code: z.ZodIssueCode.custom,
145
+ message: 'platform_id is required when scope=CHANNEL',
146
+ path: ['platform_id'],
147
+ });
148
+ }
149
+ }),
150
+ createRequest: (input) => ({
151
+ method: 'GET',
152
+ path: `/workspaces/${input.workspace_id}/variables/${input.variable_id}${buildVariableScopeQuery(input.scope, input.platform_id)}`,
153
+ }),
154
+ }),
155
+ defineTool({
156
+ name: 'besales_variable_update',
157
+ requiredFields: ['variable_id', 'scope'],
158
+ defaultWorkspaceId: true,
159
+ schema: z
160
+ .object({
161
+ workspace_id: uuidSchema.optional(),
162
+ variable_id: uuidSchema,
163
+ scope: z.enum(['WORKSPACE', 'CHANNEL']),
164
+ platform_id: uuidSchema.optional(),
165
+ description: z.string().optional(),
166
+ type: z.enum(['TEXT', 'NUMBER', 'FLOAT', 'BOOLEAN']).optional(),
167
+ // WORKSPACE-only (nullable для clear)
168
+ value: z.string().nullable().optional(),
169
+ // CHANNEL-only (nullable для clear)
170
+ constant_value: z.string().nullable().optional(),
171
+ })
172
+ .strict()
173
+ .superRefine((input, ctx) => {
174
+ if (input.scope === 'CHANNEL' && !input.platform_id) {
175
+ ctx.addIssue({
176
+ code: z.ZodIssueCode.custom,
177
+ message: 'platform_id is required when scope=CHANNEL',
178
+ path: ['platform_id'],
179
+ });
180
+ }
181
+ if (input.scope === 'WORKSPACE' && input.constant_value !== undefined) {
182
+ ctx.addIssue({
183
+ code: z.ZodIssueCode.custom,
184
+ message: 'constant_value is forbidden when scope=WORKSPACE',
185
+ path: ['constant_value'],
186
+ });
187
+ }
188
+ if (input.scope === 'CHANNEL' && input.value !== undefined) {
189
+ ctx.addIssue({
190
+ code: z.ZodIssueCode.custom,
191
+ message: 'value is forbidden when scope=CHANNEL',
192
+ path: ['value'],
193
+ });
194
+ }
195
+ }),
196
+ createRequest: (input) => ({
197
+ method: 'PATCH',
198
+ path: `/workspaces/${input.workspace_id}/variables/${input.variable_id}`,
199
+ body: compactObject({
200
+ scope: input.scope,
201
+ platformId: input.platform_id,
202
+ description: input.description,
203
+ type: input.type,
204
+ value: input.value,
205
+ constantValue: input.constant_value,
206
+ }),
207
+ }),
208
+ }),
209
+ defineTool({
210
+ name: 'besales_variable_delete',
211
+ requiredFields: ['variable_id', 'scope', 'explicit_confirmation'],
212
+ defaultWorkspaceId: true,
213
+ schema: z
214
+ .object({
215
+ workspace_id: uuidSchema.optional(),
216
+ variable_id: uuidSchema,
217
+ scope: z.enum(['WORKSPACE', 'CHANNEL']),
218
+ platform_id: uuidSchema.optional(),
219
+ explicit_confirmation: z.literal(true),
220
+ })
221
+ .strict()
222
+ .superRefine((input, ctx) => {
223
+ if (input.scope === 'CHANNEL' && !input.platform_id) {
224
+ ctx.addIssue({
225
+ code: z.ZodIssueCode.custom,
226
+ message: 'platform_id is required when scope=CHANNEL',
227
+ path: ['platform_id'],
228
+ });
229
+ }
230
+ }),
231
+ createRequest: (input) => ({
232
+ method: 'DELETE',
233
+ path: `/workspaces/${input.workspace_id}/variables/${input.variable_id}`,
234
+ body: compactObject({
235
+ scope: input.scope,
236
+ platformId: input.platform_id,
237
+ explicitConfirmation: input.explicit_confirmation,
238
+ }),
239
+ }),
240
+ }),
241
+ // ============================================================
242
+ // v1.5 Increment 4b — CRM Pipelines (11 tools)
243
+ // ============================================================
244
+ // CRUD CRM pipelines + statuses + platform-pipeline M:N junction.
245
+ // Pipeline/status create/update/delete HIT external CRM API (AmoCRM/Bitrix24);
246
+ // GetCourse manages locally с manual funnel_id / funnel_stage_id.
247
+ //
248
+ // Защита от accidental external CRM mutation: `acknowledge_external_side_effect: true`
249
+ // literal на всех mutation tools + `explicit_confirmation: true` на destructive.
250
+ //
251
+ // platform-pipeline link/unlink — local-only (M:N junction), audit reversible.
252
+ defineTool({
253
+ name: 'besales_crm_pipeline_list',
254
+ defaultWorkspaceId: true,
255
+ requiredFields: ['crm_id'],
256
+ schema: z
257
+ .object({
258
+ workspace_id: uuidSchema.optional(),
259
+ crm_id: uuidSchema,
260
+ include_deleted: z.boolean().optional(),
261
+ })
262
+ .strict(),
263
+ createRequest: (input) => ({
264
+ method: 'GET',
265
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines${input.include_deleted !== undefined ? `?includeDeleted=${input.include_deleted}` : ''}`,
266
+ }),
267
+ enhanceResponse: (response) => withInstructions(response, [
268
+ 'Триггерит sync с external CRM (AmoCRM/Bitrix24) при каждом вызове — best-effort, не блокирует list при failure.',
269
+ ]),
270
+ }),
271
+ defineTool({
272
+ name: 'besales_crm_pipeline_get',
273
+ defaultWorkspaceId: true,
274
+ requiredFields: ['crm_id', 'pipeline_id'],
275
+ schema: z
276
+ .object({
277
+ workspace_id: uuidSchema.optional(),
278
+ crm_id: uuidSchema,
279
+ pipeline_id: z.number().int(),
280
+ })
281
+ .strict(),
282
+ createRequest: (input) => ({
283
+ method: 'GET',
284
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}`,
285
+ }),
286
+ }),
287
+ defineTool({
288
+ name: 'besales_crm_pipeline_create',
289
+ defaultWorkspaceId: true,
290
+ requiredFields: ['crm_id', 'name', 'acknowledge_external_side_effect'],
291
+ schema: z
292
+ .object({
293
+ workspace_id: uuidSchema.optional(),
294
+ crm_id: uuidSchema,
295
+ name: z.string(),
296
+ sort: z.number().int().optional(),
297
+ is_main: z.boolean().optional(),
298
+ is_unsorted_on: z.boolean().optional(),
299
+ is_active: z.boolean().optional(),
300
+ pipeline_id: z.number().int().optional(),
301
+ acknowledge_external_side_effect: z.literal(true),
302
+ })
303
+ .strict(),
304
+ createRequest: (input) => ({
305
+ method: 'POST',
306
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines`,
307
+ body: compactObject({
308
+ name: input.name,
309
+ sort: input.sort,
310
+ isMain: input.is_main,
311
+ isUnsortedOn: input.is_unsorted_on,
312
+ isActive: input.is_active,
313
+ pipelineId: input.pipeline_id,
314
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
315
+ }),
316
+ }),
317
+ enhanceResponse: (response) => withInstructions(response, [
318
+ '⚠️ Создаёт воронку в EXTERNAL CRM (AmoCRM/Bitrix24). Для GetCourse требуется pipeline_id (funnel_id).',
319
+ 'External CRM ops NOT undoable через MCP. Используй с осторожностью.',
320
+ ]),
321
+ }),
322
+ defineTool({
323
+ name: 'besales_crm_pipeline_update',
324
+ defaultWorkspaceId: true,
325
+ requiredFields: [
326
+ 'crm_id',
327
+ 'pipeline_id',
328
+ 'acknowledge_external_side_effect',
329
+ ],
330
+ schema: z
331
+ .object({
332
+ workspace_id: uuidSchema.optional(),
333
+ crm_id: uuidSchema,
334
+ pipeline_id: z.number().int(),
335
+ name: z.string().optional(),
336
+ sort: z.number().int().optional(),
337
+ is_main: z.boolean().optional(),
338
+ is_unsorted_on: z.boolean().optional(),
339
+ is_active: z.boolean().optional(),
340
+ acknowledge_external_side_effect: z.literal(true),
341
+ })
342
+ .strict(),
343
+ createRequest: (input) => ({
344
+ method: 'PATCH',
345
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}`,
346
+ body: compactObject({
347
+ name: input.name,
348
+ sort: input.sort,
349
+ isMain: input.is_main,
350
+ isUnsortedOn: input.is_unsorted_on,
351
+ isActive: input.is_active,
352
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
353
+ }),
354
+ }),
355
+ enhanceResponse: (response) => withInstructions(response, [
356
+ 'Sync changes в external CRM (если поддерживается updatePipeline). Settings типа aiResponseMoveToStatus живут в PipelineSettings — обновляй через besales_pipeline_settings_update.',
357
+ ]),
358
+ }),
359
+ defineTool({
360
+ name: 'besales_crm_pipeline_delete',
361
+ defaultWorkspaceId: true,
362
+ requiredFields: [
363
+ 'crm_id',
364
+ 'pipeline_id',
365
+ 'explicit_confirmation',
366
+ 'acknowledge_external_side_effect',
367
+ ],
368
+ schema: z
369
+ .object({
370
+ workspace_id: uuidSchema.optional(),
371
+ crm_id: uuidSchema,
372
+ pipeline_id: z.number().int(),
373
+ explicit_confirmation: z.literal(true),
374
+ acknowledge_external_side_effect: z.literal(true),
375
+ })
376
+ .strict(),
377
+ createRequest: (input) => ({
378
+ method: 'DELETE',
379
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}`,
380
+ body: {
381
+ explicitConfirmation: input.explicit_confirmation,
382
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
383
+ },
384
+ }),
385
+ enhanceResponse: (response) => withInstructions(response, [
386
+ '⚠️ Soft delete локально (`deletedAt`) + удаление в external CRM. Refuses если isMain=true.',
387
+ 'External CRM ops NOT undoable через MCP.',
388
+ ]),
389
+ }),
390
+ defineTool({
391
+ name: 'besales_crm_pipeline_status_list',
392
+ defaultWorkspaceId: true,
393
+ requiredFields: ['crm_id', 'pipeline_id'],
394
+ schema: z
395
+ .object({
396
+ workspace_id: uuidSchema.optional(),
397
+ crm_id: uuidSchema,
398
+ pipeline_id: z.number().int(),
399
+ })
400
+ .strict(),
401
+ createRequest: (input) => ({
402
+ method: 'GET',
403
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}/statuses`,
404
+ }),
405
+ }),
406
+ defineTool({
407
+ name: 'besales_crm_pipeline_status_create',
408
+ defaultWorkspaceId: true,
409
+ requiredFields: [
410
+ 'crm_id',
411
+ 'pipeline_id',
412
+ 'name',
413
+ 'sort',
414
+ 'acknowledge_external_side_effect',
415
+ ],
416
+ schema: z
417
+ .object({
418
+ workspace_id: uuidSchema.optional(),
419
+ crm_id: uuidSchema,
420
+ pipeline_id: z.number().int(),
421
+ name: z.string(),
422
+ sort: z.number().int(),
423
+ color: z.string().optional(),
424
+ status_types: z.array(z.string()).optional(),
425
+ is_editable: z.boolean().optional(),
426
+ is_bot_active: z.boolean().optional(),
427
+ is_operator_available: z.boolean().optional(),
428
+ on_message_received_move_to_status: z.number().int().nullable().optional(),
429
+ status_id: z.number().int().optional(),
430
+ acknowledge_external_side_effect: z.literal(true),
431
+ })
432
+ .strict(),
433
+ createRequest: (input) => ({
434
+ method: 'POST',
435
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}/statuses`,
436
+ body: compactObject({
437
+ name: input.name,
438
+ sort: input.sort,
439
+ color: input.color,
440
+ statusTypes: input.status_types,
441
+ isEditable: input.is_editable,
442
+ isBotActive: input.is_bot_active,
443
+ isOperatorAvailable: input.is_operator_available,
444
+ onMessageReceivedMoveToStatus: input.on_message_received_move_to_status,
445
+ statusId: input.status_id,
446
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
447
+ }),
448
+ }),
449
+ enhanceResponse: (response) => withInstructions(response, [
450
+ '⚠️ Создаёт статус в EXTERNAL CRM. GetCourse требует status_id (manual funnel_stage_id).',
451
+ ]),
452
+ }),
453
+ defineTool({
454
+ name: 'besales_crm_pipeline_status_update',
455
+ defaultWorkspaceId: true,
456
+ requiredFields: [
457
+ 'crm_id',
458
+ 'pipeline_id',
459
+ 'status_id',
460
+ 'acknowledge_external_side_effect',
461
+ ],
462
+ schema: z
463
+ .object({
464
+ workspace_id: uuidSchema.optional(),
465
+ crm_id: uuidSchema,
466
+ pipeline_id: z.number().int(),
467
+ status_id: z.number().int(),
468
+ name: z.string().optional(),
469
+ sort: z.number().int().optional(),
470
+ color: z.string().optional(),
471
+ status_types: z.array(z.string()).optional(),
472
+ is_editable: z.boolean().optional(),
473
+ is_bot_active: z.boolean().optional(),
474
+ is_operator_available: z.boolean().optional(),
475
+ on_message_received_move_to_status: z.number().int().nullable().optional(),
476
+ acknowledge_external_side_effect: z.literal(true),
477
+ })
478
+ .strict(),
479
+ createRequest: (input) => ({
480
+ method: 'PATCH',
481
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}/statuses/${input.status_id}`,
482
+ body: compactObject({
483
+ name: input.name,
484
+ sort: input.sort,
485
+ color: input.color,
486
+ statusTypes: input.status_types,
487
+ isEditable: input.is_editable,
488
+ isBotActive: input.is_bot_active,
489
+ isOperatorAvailable: input.is_operator_available,
490
+ onMessageReceivedMoveToStatus: input.on_message_received_move_to_status,
491
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
492
+ }),
493
+ }),
494
+ enhanceResponse: (response) => withInstructions(response, [
495
+ 'Если status.isEditable=false — skip external CRM update (только local DB).',
496
+ ]),
497
+ }),
498
+ defineTool({
499
+ name: 'besales_crm_pipeline_status_delete',
500
+ defaultWorkspaceId: true,
501
+ requiredFields: [
502
+ 'crm_id',
503
+ 'pipeline_id',
504
+ 'status_id',
505
+ 'explicit_confirmation',
506
+ 'acknowledge_external_side_effect',
507
+ ],
508
+ schema: z
509
+ .object({
510
+ workspace_id: uuidSchema.optional(),
511
+ crm_id: uuidSchema,
512
+ pipeline_id: z.number().int(),
513
+ status_id: z.number().int(),
514
+ explicit_confirmation: z.literal(true),
515
+ acknowledge_external_side_effect: z.literal(true),
516
+ })
517
+ .strict(),
518
+ createRequest: (input) => ({
519
+ method: 'DELETE',
520
+ path: `/workspaces/${input.workspace_id}/crm/${input.crm_id}/pipelines/${input.pipeline_id}/statuses/${input.status_id}`,
521
+ body: {
522
+ explicitConfirmation: input.explicit_confirmation,
523
+ acknowledgeExternalSideEffect: input.acknowledge_external_side_effect,
524
+ },
525
+ }),
526
+ }),
527
+ defineTool({
528
+ name: 'besales_platform_pipeline_link',
529
+ defaultWorkspaceId: true,
530
+ requiredFields: ['platform_id', 'pipeline_id', 'pipeline_crm_id'],
531
+ schema: z
532
+ .object({
533
+ workspace_id: uuidSchema.optional(),
534
+ platform_id: uuidSchema,
535
+ pipeline_id: z.number().int(),
536
+ pipeline_crm_id: uuidSchema,
537
+ sort: z.number().int().min(0).optional(),
538
+ })
539
+ .strict(),
540
+ createRequest: (input) => ({
541
+ method: 'POST',
542
+ path: `/workspaces/${input.workspace_id}/platforms/${input.platform_id}/active-pipelines`,
543
+ body: compactObject({
544
+ pipelineId: input.pipeline_id,
545
+ pipelineCrmId: input.pipeline_crm_id,
546
+ sort: input.sort,
547
+ }),
548
+ }),
549
+ enhanceResponse: (response) => withInstructions(response, [
550
+ 'Local-only M:N junction. Reversible audit — unlink восстановится через besales_audit_revert.',
551
+ ]),
552
+ }),
553
+ defineTool({
554
+ name: 'besales_platform_pipeline_unlink',
555
+ defaultWorkspaceId: true,
556
+ requiredFields: ['platform_id', 'platform_pipeline_id', 'explicit_confirmation'],
557
+ schema: z
558
+ .object({
559
+ workspace_id: uuidSchema.optional(),
560
+ platform_id: uuidSchema,
561
+ platform_pipeline_id: uuidSchema,
562
+ explicit_confirmation: z.literal(true),
563
+ })
564
+ .strict(),
565
+ createRequest: (input) => ({
566
+ method: 'DELETE',
567
+ path: `/workspaces/${input.workspace_id}/platforms/${input.platform_id}/active-pipelines/${input.platform_pipeline_id}`,
568
+ body: { explicitConfirmation: input.explicit_confirmation },
569
+ }),
570
+ }),
571
+ // ============================================================
572
+ // v1.5 Increment 4d — cross-platform clone (3 tools)
573
+ // ============================================================
574
+ // Two-phase clone: preview → execute. Preview returns token (15-min TTL Redis-cached)
575
+ // с summary/conflicts/warnings; execute replays plan в Prisma transaction с
576
+ // pg_advisory_xact_lock на target_platform_id.
577
+ //
578
+ // Strict mode (C-P1a): blocks unsafe fallback в PlatformCopyService (где если source
579
+ // пусто — сервис берёт ANY other platform). Plan-builder pre-check'ает что source
580
+ // has data per included group → throws 400 если нет.
581
+ //
582
+ // Hard cap: 1000 entities per clone. >1000 → 400 с suggestion to split groups.
583
+ // Async via BullMQ — Phase 2 of 4d (out of MVP scope).
584
+ defineTool({
585
+ name: 'besales_platform_clone_preview',
586
+ defaultWorkspaceId: true,
587
+ requiredFields: ['source_platform_id', 'target_platform_id', 'include'],
588
+ schema: z
589
+ .object({
590
+ workspace_id: uuidSchema.optional(),
591
+ source_platform_id: uuidSchema,
592
+ target_platform_id: uuidSchema,
593
+ include: z
594
+ .object({
595
+ agents: z.boolean().optional(),
596
+ agent_tools: z.boolean().optional(),
597
+ triggers: z.boolean().optional(),
598
+ behavior: z.boolean().optional(),
599
+ platform_settings: z.boolean().optional(),
600
+ pipeline_settings: z.boolean().optional(),
601
+ knowledge_space_links: z.boolean().optional(),
602
+ follow_up_sequences: z.boolean().optional(),
603
+ custom_variables: z.boolean().optional(),
604
+ })
605
+ .strict()
606
+ .refine((flags) => Object.values(flags).some((v) => v === true), {
607
+ message: 'include должен содержать минимум один true flag',
608
+ }),
609
+ conflict_mode: z.enum(['reject', 'merge', 'overwrite']).optional(),
610
+ name_collision_strategy: z.enum(['suffix', 'keep']).optional(),
611
+ })
612
+ .strict()
613
+ .refine((input) => input.source_platform_id !== input.target_platform_id, {
614
+ message: 'source_platform_id и target_platform_id должны различаться',
615
+ }),
616
+ createRequest: (input) => ({
617
+ method: 'POST',
618
+ path: `/workspaces/${input.workspace_id}/platforms/clone/preview`,
619
+ body: compactObject({
620
+ sourcePlatformId: input.source_platform_id,
621
+ targetPlatformId: input.target_platform_id,
622
+ include: compactObject({
623
+ agents: input.include.agents,
624
+ agentTools: input.include.agent_tools,
625
+ triggers: input.include.triggers,
626
+ behavior: input.include.behavior,
627
+ platformSettings: input.include.platform_settings,
628
+ pipelineSettings: input.include.pipeline_settings,
629
+ knowledgeSpaceLinks: input.include.knowledge_space_links,
630
+ followUpSequences: input.include.follow_up_sequences,
631
+ customVariables: input.include.custom_variables,
632
+ }),
633
+ conflictMode: input.conflict_mode,
634
+ nameCollisionStrategy: input.name_collision_strategy,
635
+ }),
636
+ }),
637
+ enhanceResponse: (response) => withInstructions(response, [
638
+ 'Inspect `summary.totalEntities` + `conflicts[]` + `warnings[]` ДО execute.',
639
+ 'Pass `preview_token` в besales_platform_clone_execute (TTL 15 min).',
640
+ 'Если `conflicts.length > 0` и `conflict_mode=reject` — execute fails 409. Re-run preview с merge или overwrite.',
641
+ 'Если `requires_destructive_ack: true` — execute требует second literal `acknowledge_destructive_overwrite: true` (overwrite mode заменит existing target entities). Изучите `destructive_summary` ДО подтверждения.',
642
+ ]),
643
+ }),
644
+ // -----------------------------------------------------------------------------
645
+ // v1.5 Increment 4d safety policy: NO `besales_platform_delete` tool by design.
646
+ // Платформа удаляется ТОЛЬКО через UI (DELETE /platforms/:id с Permission.PLATFORM_DELETE)
647
+ // — НЕ через MCP. Cascade effect (chats, knowledge links, follow-ups) слишком
648
+ // destructive для agent-driven flow. Если будущий PR попытается добавить
649
+ // _delete tool — оставьте policy comment здесь как guard для review.
650
+ //
651
+ // Reciprocal safety: `besales_platform_clone_execute` ниже требует ВТОРОЙ ack literal
652
+ // `acknowledge_destructive_overwrite: true` для overwrite mode с реальными collisions
653
+ // (предотвращает silent agent/follow-up/behavior replace).
654
+ // -----------------------------------------------------------------------------
655
+ defineTool({
656
+ name: 'besales_platform_clone_execute',
657
+ defaultWorkspaceId: true,
658
+ requiredFields: [
659
+ 'preview_token',
660
+ 'source_platform_id',
661
+ 'target_platform_id',
662
+ 'explicit_confirmation',
663
+ ],
664
+ schema: z
665
+ .object({
666
+ workspace_id: uuidSchema.optional(),
667
+ preview_token: uuidSchema,
668
+ source_platform_id: uuidSchema,
669
+ target_platform_id: uuidSchema,
670
+ explicit_confirmation: z.literal(true),
671
+ /**
672
+ * v1.5 Increment 4d safety — second confirmation literal.
673
+ *
674
+ * REQUIRED когда preview ответил `requires_destructive_ack: true`
675
+ * (conflict_mode=overwrite + ≥1 entity to overwrite). Backend throws 400
676
+ * DESTRUCTIVE_ACK_REQUIRED если plan destructive но ack отсутствует.
677
+ *
678
+ * Для reject/merge mode или overwrite без collisions — поле игнорируется.
679
+ */
680
+ acknowledge_destructive_overwrite: z.literal(true).optional(),
681
+ idempotency_key: z.string().min(1).max(255).optional(),
682
+ actor_note: z.string().max(2000).optional(),
683
+ })
684
+ .strict(),
685
+ createRequest: (input) => ({
686
+ method: 'POST',
687
+ path: `/workspaces/${input.workspace_id}/platforms/clone/execute`,
688
+ body: compactObject({
689
+ previewToken: input.preview_token,
690
+ sourcePlatformId: input.source_platform_id,
691
+ targetPlatformId: input.target_platform_id,
692
+ explicitConfirmation: input.explicit_confirmation,
693
+ acknowledgeDestructiveOverwrite: input.acknowledge_destructive_overwrite,
694
+ idempotencyKey: input.idempotency_key,
695
+ actorNote: input.actor_note,
696
+ }),
697
+ }),
698
+ enhanceResponse: (response) => withInstructions(response, [
699
+ 'Result reversible через besales_audit_revert (kind=revertClone) — пометит CloneRun.status=REVERTED.',
700
+ 'idempotency_key reuse → returns cached result БЕЗ повторного выполнения.',
701
+ 'preview_token expired (15 min) → 410 Gone, нужно повторить preview.',
702
+ 'Если preview вернул `requires_destructive_ack: true` — обязательно передайте `acknowledge_destructive_overwrite: true` (overwrite mode фактически удалит target агентов / follow-ups / behavior).',
703
+ ]),
704
+ }),
705
+ defineTool({
706
+ name: 'besales_platform_clone_status',
707
+ defaultWorkspaceId: true,
708
+ requiredFields: ['clone_id'],
709
+ schema: z
710
+ .object({
711
+ workspace_id: uuidSchema.optional(),
712
+ clone_id: uuidSchema,
713
+ })
714
+ .strict(),
715
+ createRequest: (input) => ({
716
+ method: 'GET',
717
+ path: `/workspaces/${input.workspace_id}/platforms/clone/${input.clone_id}`,
718
+ }),
719
+ }),
720
+ // ============================================================
721
+ // v1.5 Increment 5a — Generic File Upload (3 tools, Setup URL pattern)
722
+ // ============================================================
723
+ // Файл НЕ передаётся через MCP/LLM: AI создаёт upload-сессию → пользователь
724
+ // открывает upload_page_url в браузере → drag&drop файла → backend стримит в S3.
725
+ // AI поллит статус по upload_ref. Upload нереверсивный (удаление — только через UI).
726
+ defineTool({
727
+ name: 'besales_file_upload_request',
728
+ requiredFields: ['name'],
729
+ defaultWorkspaceId: true,
730
+ schema: z
731
+ .object({
732
+ workspace_id: uuidSchema.optional(),
733
+ name: z.string().min(1).max(255),
734
+ file_type: storedFileTypeSchema.optional(),
735
+ section_id: uuidSchema.optional(),
736
+ source: mcpFileSourceSchema.optional(),
737
+ mime_type: z.string().max(255).optional(),
738
+ size_bytes: z.number().int().positive().max(MAX_FILE_UPLOAD_BYTES).optional(),
739
+ })
740
+ .strict(),
741
+ createRequest: (input) => ({
742
+ method: 'POST',
743
+ path: `/workspaces/${input.workspace_id}/file-upload-sessions`,
744
+ body: compactObject({
745
+ name: input.name,
746
+ file_type: input.file_type,
747
+ section_id: input.section_id,
748
+ source: input.source,
749
+ mime_type: input.mime_type,
750
+ size_bytes: input.size_bytes,
751
+ }),
752
+ }),
753
+ enhanceResponse: (response) => withInstructions(response, [
754
+ 'Отдайте пользователю `upload_page_url` — он откроет его в браузере (уже залогинен) и загрузит файл. Секрет/файл НЕ проходит через LLM.',
755
+ 'Затем поллите `besales_file_upload_status({ upload_ref })`: status=READY → `stored_file_id` (используйте в knowledge_document_upload / behavior greeting).',
756
+ 'TTL сессии 15 минут. Имя файла (`name`) должно быть уникальным в workspace.',
757
+ ]),
758
+ }),
759
+ defineTool({
760
+ name: 'besales_file_upload_status',
761
+ requiredFields: ['upload_ref'],
762
+ defaultWorkspaceId: true,
763
+ schema: z
764
+ .object({
765
+ workspace_id: uuidSchema.optional(),
766
+ upload_ref: z.string().min(1),
767
+ })
768
+ .strict(),
769
+ createRequest: (input) => ({
770
+ method: 'GET',
771
+ path: `/workspaces/${input.workspace_id}/file-upload-sessions/${input.upload_ref}`,
772
+ }),
773
+ enhanceResponse: (response) => withInstructions(response, [
774
+ 'status: AWAITING_USER (ждём загрузку) → VALIDATING → READY (`stored_file_id` готов) | VALIDATION_FAILED (`error_message`, пользователь может повторить) | EXPIRED (создайте новую сессию).',
775
+ ]),
776
+ }),
777
+ defineTool({
778
+ name: 'besales_file_list',
779
+ requiredFields: [],
780
+ defaultWorkspaceId: true,
781
+ schema: z
782
+ .object({
783
+ workspace_id: uuidSchema.optional(),
784
+ file_type: storedFileTypeSchema.optional(),
785
+ source: mcpFileSourceSchema.optional(),
786
+ section_id: z.string().optional(),
787
+ limit: z.number().int().min(1).max(200).optional(),
788
+ offset: z.number().int().min(0).optional(),
789
+ })
790
+ .strict(),
791
+ createRequest: (input) => ({
792
+ method: 'GET',
793
+ path: `/workspaces/${input.workspace_id}/files${buildFileListQuery(input)}`,
794
+ }),
795
+ }),
796
+ // ============================================================
797
+ // v1.5 Increment 5b — Platform Provisioning (API-key + OAuth каналы, 3 tools, Setup URL pattern)
798
+ // ============================================================
799
+ // Секреты (botToken / wazzupCrmKey / ...) НЕ передаются через MCP: AI создаёт сессию →
800
+ // пользователь открывает setup_page_url и вводит секрет в браузере → backend создаёт платформу.
801
+ // OAuth-каналы (Instagram, Avito) — besales_platform_create_oauth_init (авторизация в браузере,
802
+ // код мимо LLM). TelegramPersonal (warmup) пока не поддержан.
803
+ defineTool({
804
+ name: 'besales_platform_create_init',
805
+ requiredFields: ['platform_type', 'name'],
806
+ defaultWorkspaceId: true,
807
+ schema: z
808
+ .object({
809
+ workspace_id: uuidSchema.optional(),
810
+ platform_type: apiKeyPlatformTypeSchema,
811
+ name: z.string().min(1).max(255),
812
+ // Публичные (НЕ секретные) поля канала. Секреты вводятся на /setup/:ref.
813
+ // Per-type allowlist: Wazzup={wazzup_channel}; Salebot={salebot_project_id,salebot_group_id};
814
+ // SmsGorod={smsgorod_channel,smsgorod_sender}; VKon={vkon_group_id};
815
+ // GetCourse={getcourse_account_domain,getcourse_employee_user_id,getcourse_available_transports,...};
816
+ // Bitrix24Bot={bitrix24_bot_name}; TelegramBot/WebWidget=none.
817
+ fields: optionalAnyObjectSchema,
818
+ })
819
+ .strict(),
820
+ createRequest: (input) => ({
821
+ method: 'POST',
822
+ path: `/workspaces/${input.workspace_id}/platform-setup-sessions`,
823
+ body: compactObject({
824
+ platform_type: input.platform_type,
825
+ name: input.name,
826
+ fields: input.fields,
827
+ }),
828
+ }),
829
+ enhanceResponse: (response) => withInstructions(response, [
830
+ 'Отдайте пользователю `setup_page_url` — он откроет его в браузере и введёт секреты (`required_secret_fields`). Секреты НЕ проходят через LLM.',
831
+ 'Затем поллите `besales_platform_create_status({ setup_ref })`: status=READY → `platform_id`.',
832
+ 'WebWidget/Bitrix24Bot не требуют секретов (required_secret_fields пуст); Bitrix24Bot требует предварительно подключённую Bitrix24 CRM.',
833
+ 'OAuth-каналы (Instagram, Avito) — отдельный `besales_platform_create_oauth_init`; TelegramPersonal пока не поддержан.',
834
+ ]),
835
+ }),
836
+ defineTool({
837
+ name: 'besales_platform_create_status',
838
+ requiredFields: ['setup_ref'],
839
+ defaultWorkspaceId: true,
840
+ schema: z
841
+ .object({
842
+ workspace_id: uuidSchema.optional(),
843
+ setup_ref: z.string().min(1),
844
+ })
845
+ .strict(),
846
+ createRequest: (input) => ({
847
+ method: 'GET',
848
+ path: `/workspaces/${input.workspace_id}/platform-setup-sessions/${input.setup_ref}`,
849
+ }),
850
+ enhanceResponse: (response) => withInstructions(response, [
851
+ 'status: AWAITING_USER → VALIDATING → READY (`platform_id` готов) | VALIDATION_FAILED (`error_message`, напр. невалидный токен — пользователь может повторить) | EXPIRED (создайте новую сессию).',
852
+ 'READY = платформа создана + токен онлайн-проверен для TelegramBot/VKon/GetCourse/Wazzup/Bitrix24Bot (битый ключ → VALIDATION_FAILED). Salebot/SmsGorod НЕ проверяются онлайн — для них READY означает «создано»; попросите пользователя проверить работу канала.',
853
+ 'OAuth-каналы (Instagram/Avito): после `besales_platform_create_oauth_init` поллите этим же tool по `setup_ref`.',
854
+ ]),
855
+ }),
856
+ defineTool({
857
+ name: 'besales_platform_create_oauth_init',
858
+ requiredFields: ['platform_type'],
859
+ defaultWorkspaceId: true,
860
+ schema: z
861
+ .object({
862
+ workspace_id: uuidSchema.optional(),
863
+ platform_type: oauthPlatformTypeSchema,
864
+ // Опционально: канал обычно сам именует платформу из подключённого аккаунта.
865
+ name: z.string().min(1).max(255).optional(),
866
+ })
867
+ .strict(),
868
+ createRequest: (input) => ({
869
+ method: 'POST',
870
+ path: `/workspaces/${input.workspace_id}/platform-oauth-sessions`,
871
+ body: compactObject({ platform_type: input.platform_type, name: input.name }),
872
+ }),
873
+ enhanceResponse: (response) => withInstructions(response, [
874
+ 'Отдайте пользователю `auth_url` — он откроет его в браузере и авторизуется у провайдера (Instagram/Avito). Код авторизации НЕ проходит через LLM.',
875
+ 'Затем поллите `besales_platform_create_status({ setup_ref })`: READY → `platform_id` | VALIDATION_FAILED (`error_message`) | EXPIRED (создайте новую сессию).',
876
+ 'Требуется настроенное провайдер-приложение (Meta App для Instagram, Avito app). Имя канала задаётся из подключённого аккаунта.',
877
+ ]),
878
+ }),
879
+ // ============================================================
880
+ // v1.5 Increment 5c — CRM Connection + Field Mapping + Operators (5 tools)
881
+ // ============================================================
882
+ // CRM create через Setup URL pattern (секреты в браузере, минуя LLM): GETCOURSE/TELEGRAM
883
+ // (API-key) + AMOCRM/BITRIX24 (вставка credentials; Bitrix24 далее завершает local-app install
884
+ // вебхуком). Field mapping + operators — для уже подключённых CRM (AmoCRM/Bitrix24 для mapping;
885
+ // AmoCRM для operators).
886
+ defineTool({
887
+ name: 'besales_crm_create_init',
888
+ requiredFields: ['crm_type'],
889
+ defaultWorkspaceId: true,
890
+ schema: z
891
+ .object({
892
+ workspace_id: uuidSchema.optional(),
893
+ crm_type: provisionableCrmTypeSchema,
894
+ // Публичные non-secret поля: GETCOURSE={account_domain, managed_tag_prefix?};
895
+ // TELEGRAM={forum_group_id}; AMOCRM={sub_domain, client_id}; BITRIX24={domain, client_id}.
896
+ // Секреты (school_api_key / crm_bot_token / client_secret / long_access_token) — на /setup/:ref.
897
+ fields: optionalAnyObjectSchema,
898
+ })
899
+ .strict(),
900
+ createRequest: (input) => ({
901
+ method: 'POST',
902
+ path: `/workspaces/${input.workspace_id}/crm-setup-sessions`,
903
+ body: compactObject({ crm_type: input.crm_type, fields: input.fields }),
904
+ }),
905
+ enhanceResponse: (response) => withInstructions(response, [
906
+ 'Отдайте пользователю `setup_page_url` — он вводит в браузере ВСЕ поля (и секреты, и несекретные sub_domain/client_id/domain/...), минуя чат. Несекретные — в `required_non_secret_fields`, секреты — в `required_secret_fields`.',
907
+ 'НЕ проси у пользователя credentials или ID в чат — всё вводится на setup-странице. `fields` в init опционален (только pre-fill).',
908
+ 'Затем поллите `besales_crm_create_status({ setup_ref })`: READY → `crm_id`.',
909
+ 'Детали подключения (где взять токены, какие scopes — в т.ч. для выгрузки аудио, активация) — ресурс besales://workflows/connect-crm-from-scratch. Bitrix24 после создания требует установки local-app в портале (вебхук).',
910
+ ]),
911
+ }),
912
+ defineTool({
913
+ name: 'besales_crm_create_status',
914
+ requiredFields: ['setup_ref'],
915
+ defaultWorkspaceId: true,
916
+ schema: z
917
+ .object({
918
+ workspace_id: uuidSchema.optional(),
919
+ setup_ref: z.string().min(1),
920
+ })
921
+ .strict(),
922
+ createRequest: (input) => ({
923
+ method: 'GET',
924
+ path: `/workspaces/${input.workspace_id}/crm-setup-sessions/${input.setup_ref}`,
925
+ }),
926
+ enhanceResponse: (response) => withInstructions(response, [
927
+ 'status: AWAITING_USER → VALIDATING → READY (`crm_id`) | VALIDATION_FAILED (`error_message`) | EXPIRED.',
928
+ 'READY = CRM создана + токен онлайн-проверен для Telegram-CRM (getMe) и GetCourse (read-only API-probe) → битый токен/ключ = VALIDATION_FAILED.',
929
+ 'AmoCRM: на READY авто-активируется, ЕСЛИ это единственная CRM workspace (токен live-проверяется при активации). Если есть другая активная CRM или токен битый — остаётся inactive → включи besales_crm_activate({ crm_id }). Bitrix24: онлайн НЕ проверяется, требует РУЧНОЙ установки local-app в портале (вебхук).',
930
+ ]),
931
+ }),
932
+ defineTool({
933
+ name: 'besales_crm_activate',
934
+ requiredFields: ['crm_id'],
935
+ defaultWorkspaceId: true,
936
+ schema: z
937
+ .object({
938
+ workspace_id: uuidSchema.optional(),
939
+ crm_id: uuidSchema,
940
+ })
941
+ .strict(),
942
+ createRequest: (input) => ({
943
+ method: 'POST',
944
+ path: `/workspaces/${input.workspace_id}/crms/${input.crm_id}/activate`,
945
+ }),
946
+ enhanceResponse: (response) => withInstructions(response, [
947
+ 'CRM активирована: токен live-проверен, остальные CRM workspace деактивированы, связанные триггеры включены.',
948
+ 'Активна одна CRM на workspace за раз — этот вызов ДЕАКТИВИРУЕТ предыдущую активную. Предупреди пользователя перед переключением.',
949
+ 'Битый/просроченный токен → ошибка активации (CRM остаётся неактивной). Зови после исправления токена либо для переключения активной CRM.',
950
+ ]),
951
+ }),
952
+ defineTool({
953
+ name: 'besales_crm_list',
954
+ requiredFields: [],
955
+ defaultWorkspaceId: true,
956
+ schema: z
957
+ .object({
958
+ workspace_id: uuidSchema.optional(),
959
+ })
960
+ .strict(),
961
+ createRequest: (input) => ({
962
+ method: 'GET',
963
+ path: `/workspaces/${input.workspace_id}/crms`,
964
+ }),
965
+ enhanceResponse: (response) => withInstructions(response, [
966
+ 'Список CRM workspace: id, type, status, is_active, account (поддомен/домен), created_at.',
967
+ 'Пустой список → CRM не подключена (нужен besales_crm_create_init).',
968
+ 'is_active=false → CRM есть, но не активна (besales_crm_activate); это объясняет, почему pipelines/field-mapping/operators могут не отвечать.',
969
+ ]),
970
+ }),
971
+ defineTool({
972
+ name: 'besales_crm_field_mapping_get',
973
+ requiredFields: ['crm_id'],
974
+ defaultWorkspaceId: true,
975
+ schema: z
976
+ .object({
977
+ workspace_id: uuidSchema.optional(),
978
+ crm_id: uuidSchema,
979
+ })
980
+ .strict(),
981
+ createRequest: (input) => ({
982
+ method: 'GET',
983
+ path: `/workspaces/${input.workspace_id}/crms/${input.crm_id}/field-mapping`,
984
+ }),
985
+ enhanceResponse: (response) => withInstructions(response, [
986
+ 'Возвращает `available_fields` (поля из CRM) + `current_mappings`. Только AmoCRM/Bitrix24 — иначе `unsupported_reason`.',
987
+ 'Используйте `available_fields[].id` как `crm_field_id` в `besales_crm_field_mapping_upsert`.',
988
+ ]),
989
+ }),
990
+ defineTool({
991
+ name: 'besales_crm_field_mapping_upsert',
992
+ requiredFields: ['crm_id', 'replace_all_existing', 'enabled', 'field_mappings'],
993
+ defaultWorkspaceId: true,
994
+ schema: z
995
+ .object({
996
+ workspace_id: uuidSchema.optional(),
997
+ crm_id: uuidSchema,
998
+ // Replace-all semantics: передаёт весь набор mappings (не патч).
999
+ replace_all_existing: z.literal(true),
1000
+ enabled: z.boolean(),
1001
+ field_mappings: z.array(crmFieldMappingItemSchema),
1002
+ })
1003
+ .strict(),
1004
+ createRequest: (input) => ({
1005
+ method: 'PUT',
1006
+ path: `/workspaces/${input.workspace_id}/crms/${input.crm_id}/field-mapping`,
1007
+ body: {
1008
+ replace_all_existing: input.replace_all_existing,
1009
+ enabled: input.enabled,
1010
+ field_mappings: input.field_mappings,
1011
+ },
1012
+ }),
1013
+ enhanceResponse: (response) => withInstructions(response, [
1014
+ 'Replace-all: передавайте ВЕСЬ набор маппингов (не патч). Только AmoCRM/Bitrix24 — иначе `unsupported_reason`.',
1015
+ 'Чтобы изменить — повторно вызовите с новым полным набором (это «undo»). Audit non-reversible (снимок before в diff).',
1016
+ ]),
1017
+ }),
1018
+ defineTool({
1019
+ name: 'besales_crm_operator_list',
1020
+ requiredFields: ['crm_id'],
1021
+ defaultWorkspaceId: true,
1022
+ schema: z
1023
+ .object({
1024
+ workspace_id: uuidSchema.optional(),
1025
+ crm_id: uuidSchema,
1026
+ sync: z.boolean().optional(),
1027
+ })
1028
+ .strict(),
1029
+ createRequest: (input) => ({
1030
+ method: 'GET',
1031
+ path: `/workspaces/${input.workspace_id}/crms/${input.crm_id}/operators${input.sync ? '?sync=true' : ''}`,
1032
+ }),
1033
+ enhanceResponse: (response) => withInstructions(response, [
1034
+ 'Операторы для assignee-стратегий PipelineSettings. Только AmoCRM/Bitrix24 — иначе `unsupported_reason`.',
1035
+ 'sync=true пересинхронизирует операторов из CRM перед возвратом.',
1036
+ ]),
1037
+ }),
1038
+ defineTool({
1039
+ name: 'besales_crm_operator_update',
1040
+ requiredFields: ['crm_id', 'operator_id'],
1041
+ defaultWorkspaceId: true,
1042
+ schema: z
1043
+ .object({
1044
+ workspace_id: uuidSchema.optional(),
1045
+ crm_id: uuidSchema,
1046
+ operator_id: uuidSchema,
1047
+ is_operator: z.boolean().optional(),
1048
+ is_sdr: z.boolean().optional(),
1049
+ })
1050
+ .strict()
1051
+ .superRefine((input, ctx) => {
1052
+ if (input.is_operator === undefined && input.is_sdr === undefined) {
1053
+ ctx.addIssue({
1054
+ code: z.ZodIssueCode.custom,
1055
+ message: 'Provide at least one of is_operator / is_sdr',
1056
+ });
1057
+ }
1058
+ }),
1059
+ createRequest: (input) => ({
1060
+ method: 'PATCH',
1061
+ path: `/workspaces/${input.workspace_id}/crms/${input.crm_id}/operators/${input.operator_id}`,
1062
+ body: compactObject({ is_operator: input.is_operator, is_sdr: input.is_sdr }),
1063
+ }),
1064
+ enhanceResponse: (response) => withInstructions(response, [
1065
+ 'Вкл/выкл роли конкретного оператора: is_operator (assignee диалогов) и/или is_sdr. Только AmoCRM/Bitrix24 — иначе `unsupported_reason`.',
1066
+ 'Передайте хотя бы один флаг; не указанный сохраняет текущее значение. operator_id берётся из `besales_crm_operator_list`.',
1067
+ ]),
1068
+ }),
1069
+ ];
1070
+ //# sourceMappingURL=crm-platform.js.map