@besales/mcp 0.47.1 → 1.0.0-beta.2

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 (587) hide show
  1. package/README.md +109 -164
  2. package/README.onprem.ru.md +35 -0
  3. package/bin/besales-mcp-http.js +9 -0
  4. package/bin/besales-onprem.mjs +19 -0
  5. package/dist/auth/connection-store.d.ts +3 -1
  6. package/dist/auth/connection-store.js +9 -3
  7. package/dist/auth/connection-store.js.map +1 -1
  8. package/dist/auth/oauth-client.d.ts +20 -27
  9. package/dist/auth/oauth-client.js +31 -23
  10. package/dist/auth/oauth-client.js.map +1 -1
  11. package/dist/auth/session-workspace.d.ts +1 -0
  12. package/dist/auth/session-workspace.js +10 -1
  13. package/dist/auth/session-workspace.js.map +1 -1
  14. package/dist/auth/token-storage.js +4 -1
  15. package/dist/auth/token-storage.js.map +1 -1
  16. package/dist/cli.d.ts +1 -0
  17. package/dist/cli.js +80 -19
  18. package/dist/cli.js.map +1 -1
  19. package/dist/constants/crm-catalog.d.ts +2 -0
  20. package/dist/constants/crm-catalog.js +3 -0
  21. package/dist/constants/crm-catalog.js.map +1 -0
  22. package/dist/http/api-client.d.ts +52 -7
  23. package/dist/http/api-client.js +131 -66
  24. package/dist/http/api-client.js.map +1 -1
  25. package/dist/http/feedback-document-error.d.ts +26 -0
  26. package/dist/http/feedback-document-error.js +22 -0
  27. package/dist/http/feedback-document-error.js.map +1 -0
  28. package/dist/http-server.d.ts +15 -0
  29. package/dist/http-server.js +378 -0
  30. package/dist/http-server.js.map +1 -0
  31. package/dist/instructions/server-instructions.d.ts +1 -1
  32. package/dist/instructions/server-instructions.js +6 -3
  33. package/dist/instructions/server-instructions.js.map +1 -1
  34. package/dist/network/constants.d.ts +25 -0
  35. package/dist/network/constants.js +26 -0
  36. package/dist/network/constants.js.map +1 -0
  37. package/dist/network/failed-verifications.d.ts +10 -0
  38. package/dist/network/failed-verifications.js +37 -0
  39. package/dist/network/failed-verifications.js.map +1 -0
  40. package/dist/network/http-test-fixture.d.ts +3 -0
  41. package/dist/network/http-test-fixture.js +86 -0
  42. package/dist/network/http-test-fixture.js.map +1 -0
  43. package/dist/network/known-keys.d.ts +10 -0
  44. package/dist/network/known-keys.js +38 -0
  45. package/dist/network/known-keys.js.map +1 -0
  46. package/dist/network/memory-credentials.d.ts +11 -0
  47. package/dist/network/memory-credentials.js +24 -0
  48. package/dist/network/memory-credentials.js.map +1 -0
  49. package/dist/network/rate-limit.d.ts +23 -0
  50. package/dist/network/rate-limit.js +62 -0
  51. package/dist/network/rate-limit.js.map +1 -0
  52. package/dist/network/rejected-keys.d.ts +9 -0
  53. package/dist/network/rejected-keys.js +39 -0
  54. package/dist/network/rejected-keys.js.map +1 -0
  55. package/dist/network/request-work.d.ts +8 -0
  56. package/dist/network/request-work.js +20 -0
  57. package/dist/network/request-work.js.map +1 -0
  58. package/dist/network/successful-keys.d.ts +17 -0
  59. package/dist/network/successful-keys.js +47 -0
  60. package/dist/network/successful-keys.js.map +1 -0
  61. package/dist/resources/concepts/external-execution.md +3 -1
  62. package/dist/resources/concepts/feedback-sheets.md +21 -12
  63. package/dist/resources/concepts/icp.md +8 -1
  64. package/dist/resources/concepts/sandbox.md +15 -6
  65. package/dist/resources/concepts/triggers.md +8 -0
  66. package/dist/resources/concepts/workbook-classification.md +104 -50
  67. package/dist/resources/docs/agent-behavior.md +51 -382
  68. package/dist/resources/docs/campaigns.md +9 -16
  69. package/dist/resources/docs/crm-integration.md +39 -523
  70. package/dist/resources/docs/files-and-uploads.md +23 -286
  71. package/dist/resources/docs/follow-up.md +47 -12
  72. package/dist/resources/docs/knowledge-base.md +52 -521
  73. package/dist/resources/docs/pipeline-builder.md +26 -216
  74. package/dist/resources/docs/pipeline-settings-deep.md +27 -220
  75. package/dist/resources/docs/platforms.md +106 -519
  76. package/dist/resources/docs/prompt-anatomy.md +39 -285
  77. package/dist/resources/docs/routing.md +8 -16
  78. package/dist/resources/docs/trigger-actions.md +40 -24
  79. package/dist/resources/docs/variables.md +8 -12
  80. package/dist/resources/operator-edit.contract.d.ts +7 -0
  81. package/dist/resources/operator-edit.contract.js +8 -0
  82. package/dist/resources/operator-edit.contract.js.map +1 -0
  83. package/dist/resources/registry.js +20 -4
  84. package/dist/resources/registry.js.map +1 -1
  85. package/dist/resources/workflows/audit-dialogues.md +23 -12
  86. package/dist/resources/workflows/compare-models.md +6 -4
  87. package/dist/resources/workflows/connect-crm-from-scratch.md +15 -12
  88. package/dist/resources/workflows/connect-datasource-from-scratch.md +5 -4
  89. package/dist/resources/workflows/extract-from-document.md +8 -6
  90. package/dist/resources/workflows/operator-edit.md +15 -0
  91. package/dist/resources/workflows/platform-setup-from-scratch.md +59 -70
  92. package/dist/resources/workflows/production-readiness-check.md +25 -15
  93. package/dist/schemas/mcp-tools.json +12061 -6753
  94. package/dist/schemas/voice-actions-api.contract.json +248 -0
  95. package/dist/shared/constants/broadcast-reason.constants.d.ts +2 -0
  96. package/dist/shared/constants/broadcast-reason.constants.js +47 -0
  97. package/dist/shared/constants/broadcast-reason.constants.js.map +1 -0
  98. package/dist/shared/constants/crm-stage-pool.constants.d.ts +2 -0
  99. package/dist/shared/constants/crm-stage-pool.constants.js +3 -0
  100. package/dist/shared/constants/crm-stage-pool.constants.js.map +1 -0
  101. package/dist/shared/constants/followup-note.constants.d.ts +3 -0
  102. package/dist/shared/constants/followup-note.constants.js +4 -0
  103. package/dist/shared/constants/followup-note.constants.js.map +1 -0
  104. package/dist/shared/constants/followup-session.constants.d.ts +2 -0
  105. package/dist/shared/constants/followup-session.constants.js +3 -0
  106. package/dist/shared/constants/followup-session.constants.js.map +1 -0
  107. package/dist/shared/constants/funnel-analytics.constants.d.ts +2 -0
  108. package/dist/shared/constants/funnel-analytics.constants.js +3 -0
  109. package/dist/shared/constants/funnel-analytics.constants.js.map +1 -0
  110. package/dist/shared/constants/message-filter.constants.d.ts +7 -0
  111. package/dist/shared/constants/message-filter.constants.js +8 -0
  112. package/dist/shared/constants/message-filter.constants.js.map +1 -0
  113. package/dist/shared/utils/trigger-repeat-mapping.d.ts +72 -0
  114. package/dist/shared/utils/trigger-repeat-mapping.js +53 -0
  115. package/dist/shared/utils/trigger-repeat-mapping.js.map +1 -0
  116. package/dist/skills/besales-dialogue-audit/SKILL.md +31 -20
  117. package/dist/tasks/task-state-store.js +5 -0
  118. package/dist/tasks/task-state-store.js.map +1 -1
  119. package/dist/tools/annotations.js +4 -3
  120. package/dist/tools/annotations.js.map +1 -1
  121. package/dist/tools/definitions/agent-design.d.ts +78 -21
  122. package/dist/tools/definitions/agent-design.js +246 -90
  123. package/dist/tools/definitions/agent-design.js.map +1 -1
  124. package/dist/tools/definitions/agent-model-notice.d.ts +5 -0
  125. package/dist/tools/definitions/agent-model-notice.js +31 -0
  126. package/dist/tools/definitions/agent-model-notice.js.map +1 -0
  127. package/dist/tools/definitions/agent-models.d.ts +5 -0
  128. package/dist/tools/definitions/agent-models.js +26 -0
  129. package/dist/tools/definitions/agent-models.js.map +1 -0
  130. package/dist/tools/definitions/agent-settings.d.ts +85 -0
  131. package/dist/tools/definitions/agent-settings.js +187 -0
  132. package/dist/tools/definitions/agent-settings.js.map +1 -0
  133. package/dist/tools/definitions/ai-costs.d.ts +12 -0
  134. package/dist/tools/definitions/ai-costs.js +142 -0
  135. package/dist/tools/definitions/ai-costs.js.map +1 -0
  136. package/dist/tools/definitions/api-tool-request.d.ts +15 -0
  137. package/dist/tools/definitions/api-tool-request.js +2 -0
  138. package/dist/tools/definitions/api-tool-request.js.map +1 -0
  139. package/dist/tools/definitions/behavior-shipped-form.d.ts +182 -0
  140. package/dist/tools/definitions/behavior-shipped-form.js +185 -0
  141. package/dist/tools/definitions/behavior-shipped-form.js.map +1 -0
  142. package/dist/tools/definitions/broadcast-alerts.d.ts +6 -0
  143. package/dist/tools/definitions/broadcast-alerts.js +72 -0
  144. package/dist/tools/definitions/broadcast-alerts.js.map +1 -0
  145. package/dist/tools/definitions/broadcast-analytics.d.ts +102 -18
  146. package/dist/tools/definitions/broadcast-analytics.js +732 -427
  147. package/dist/tools/definitions/broadcast-analytics.js.map +1 -1
  148. package/dist/tools/definitions/broadcast-scheduler.d.ts +19 -13
  149. package/dist/tools/definitions/broadcast-scheduler.js +217 -128
  150. package/dist/tools/definitions/broadcast-scheduler.js.map +1 -1
  151. package/dist/tools/definitions/call-operator-task.schema.d.ts +11 -0
  152. package/dist/tools/definitions/call-operator-task.schema.js +30 -0
  153. package/dist/tools/definitions/call-operator-task.schema.js.map +1 -0
  154. package/dist/tools/definitions/contacts.d.ts +3 -0
  155. package/dist/tools/definitions/contacts.js +57 -31
  156. package/dist/tools/definitions/contacts.js.map +1 -1
  157. package/dist/tools/definitions/crm-platform.d.ts +28 -39
  158. package/dist/tools/definitions/crm-platform.js +235 -344
  159. package/dist/tools/definitions/crm-platform.js.map +1 -1
  160. package/dist/tools/definitions/crm-stage-pool.messages.d.ts +39 -0
  161. package/dist/tools/definitions/crm-stage-pool.messages.js +43 -0
  162. package/dist/tools/definitions/crm-stage-pool.messages.js.map +1 -0
  163. package/dist/tools/definitions/crm-stage-pool.schema.d.ts +64 -0
  164. package/dist/tools/definitions/crm-stage-pool.schema.js +109 -0
  165. package/dist/tools/definitions/crm-stage-pool.schema.js.map +1 -0
  166. package/dist/tools/definitions/custom-reports.d.ts +12 -0
  167. package/dist/tools/definitions/custom-reports.js +68 -0
  168. package/dist/tools/definitions/custom-reports.js.map +1 -0
  169. package/dist/tools/definitions/datasource.d.ts +2 -1
  170. package/dist/tools/definitions/datasource.js +28 -13
  171. package/dist/tools/definitions/datasource.js.map +1 -1
  172. package/dist/tools/definitions/dialog-review-findings-response.d.ts +14 -0
  173. package/dist/tools/definitions/dialog-review-findings-response.js +23 -0
  174. package/dist/tools/definitions/dialog-review-findings-response.js.map +1 -0
  175. package/dist/tools/definitions/dialog-review-safe-response.d.ts +5 -0
  176. package/dist/tools/definitions/dialog-review-safe-response.js +152 -0
  177. package/dist/tools/definitions/dialog-review-safe-response.js.map +1 -0
  178. package/dist/tools/definitions/dialog-review.d.ts +49 -0
  179. package/dist/tools/definitions/dialog-review.js +383 -0
  180. package/dist/tools/definitions/dialog-review.js.map +1 -0
  181. package/dist/tools/definitions/dialogue-analysis.d.ts +46 -0
  182. package/dist/tools/definitions/dialogue-analysis.js +170 -0
  183. package/dist/tools/definitions/dialogue-analysis.js.map +1 -0
  184. package/dist/tools/definitions/dialogue-audit-agent-scope.d.ts +7 -0
  185. package/dist/tools/definitions/dialogue-audit-agent-scope.js +41 -0
  186. package/dist/tools/definitions/dialogue-audit-agent-scope.js.map +1 -0
  187. package/dist/tools/definitions/dialogue-audit-alias.d.ts +67 -0
  188. package/dist/tools/definitions/dialogue-audit-alias.js +559 -0
  189. package/dist/tools/definitions/dialogue-audit-alias.js.map +1 -0
  190. package/dist/tools/definitions/dialogue-export.d.ts +7 -1
  191. package/dist/tools/definitions/dialogue-export.js +89 -32
  192. package/dist/tools/definitions/dialogue-export.js.map +1 -1
  193. package/dist/tools/definitions/dialogue-followup-state.d.ts +9 -0
  194. package/dist/tools/definitions/dialogue-followup-state.js +69 -0
  195. package/dist/tools/definitions/dialogue-followup-state.js.map +1 -0
  196. package/dist/tools/definitions/edit-version-transport.test-fixtures.d.ts +13 -0
  197. package/dist/tools/definitions/edit-version-transport.test-fixtures.js +292 -0
  198. package/dist/tools/definitions/edit-version-transport.test-fixtures.js.map +1 -0
  199. package/dist/tools/definitions/edit-version.d.ts +5 -0
  200. package/dist/tools/definitions/edit-version.js +20 -0
  201. package/dist/tools/definitions/edit-version.js.map +1 -0
  202. package/dist/tools/definitions/external-api-channels.js +17 -15
  203. package/dist/tools/definitions/external-api-channels.js.map +1 -1
  204. package/dist/tools/definitions/feedback-document-access.d.ts +16 -0
  205. package/dist/tools/definitions/feedback-document-access.js +63 -0
  206. package/dist/tools/definitions/feedback-document-access.js.map +1 -0
  207. package/dist/tools/definitions/fixtures/shipped-setup-status.d.ts +3 -0
  208. package/dist/tools/definitions/fixtures/shipped-setup-status.js +9 -0
  209. package/dist/tools/definitions/fixtures/shipped-setup-status.js.map +1 -0
  210. package/dist/tools/definitions/follow-up-config.d.ts +79 -0
  211. package/dist/tools/definitions/follow-up-config.js +258 -0
  212. package/dist/tools/definitions/follow-up-config.js.map +1 -0
  213. package/dist/tools/definitions/follow-up-funnel-test.d.ts +4 -3
  214. package/dist/tools/definitions/follow-up-funnel-test.js +65 -52
  215. package/dist/tools/definitions/follow-up-funnel-test.js.map +1 -1
  216. package/dist/tools/definitions/follow-up-promotion-instructions.d.ts +2 -0
  217. package/dist/tools/definitions/follow-up-promotion-instructions.js +7 -0
  218. package/dist/tools/definitions/follow-up-promotion-instructions.js.map +1 -0
  219. package/dist/tools/definitions/follow-up-setup.d.ts +4 -28
  220. package/dist/tools/definitions/follow-up-setup.js +20 -87
  221. package/dist/tools/definitions/follow-up-setup.js.map +1 -1
  222. package/dist/tools/definitions/follow-up.d.ts +20 -122
  223. package/dist/tools/definitions/follow-up.js +200 -354
  224. package/dist/tools/definitions/follow-up.js.map +1 -1
  225. package/dist/tools/definitions/followup-getcourse-compat.d.ts +29 -0
  226. package/dist/tools/definitions/followup-getcourse-compat.js +62 -0
  227. package/dist/tools/definitions/followup-getcourse-compat.js.map +1 -0
  228. package/dist/tools/definitions/followup-sequence-config.schema.d.ts +10 -0
  229. package/dist/tools/definitions/followup-sequence-config.schema.js +14 -0
  230. package/dist/tools/definitions/followup-sequence-config.schema.js.map +1 -0
  231. package/dist/tools/definitions/funnel-analytics.d.ts +18 -0
  232. package/dist/tools/definitions/funnel-analytics.js +150 -0
  233. package/dist/tools/definitions/funnel-analytics.js.map +1 -0
  234. package/dist/tools/definitions/getcourse-anchor-refresh.d.ts +5 -9
  235. package/dist/tools/definitions/getcourse-anchor-refresh.js +26 -31
  236. package/dist/tools/definitions/getcourse-anchor-refresh.js.map +1 -1
  237. package/dist/tools/definitions/getcourse-cold-outbound-settings.d.ts +46 -0
  238. package/dist/tools/definitions/getcourse-cold-outbound-settings.js +37 -0
  239. package/dist/tools/definitions/getcourse-cold-outbound-settings.js.map +1 -0
  240. package/dist/tools/definitions/getcourse-cold-welcome.d.ts +17 -29
  241. package/dist/tools/definitions/getcourse-cold-welcome.js +182 -242
  242. package/dist/tools/definitions/getcourse-cold-welcome.js.map +1 -1
  243. package/dist/tools/definitions/getcourse-wave-welcome.messages.d.ts +10 -0
  244. package/dist/tools/definitions/getcourse-wave-welcome.messages.js +17 -0
  245. package/dist/tools/definitions/getcourse-wave-welcome.messages.js.map +1 -0
  246. package/dist/tools/definitions/help.js.map +1 -1
  247. package/dist/tools/definitions/icp-import-source.d.ts +75 -0
  248. package/dist/tools/definitions/icp-import-source.js +88 -0
  249. package/dist/tools/definitions/icp-import-source.js.map +1 -0
  250. package/dist/tools/definitions/inbound-analytics.d.ts +13 -0
  251. package/dist/tools/definitions/inbound-analytics.js +55 -0
  252. package/dist/tools/definitions/inbound-analytics.js.map +1 -0
  253. package/dist/tools/definitions/instagram-comment.templates.d.ts +19 -0
  254. package/dist/tools/definitions/instagram-comment.templates.js +34 -0
  255. package/dist/tools/definitions/instagram-comment.templates.js.map +1 -0
  256. package/dist/tools/definitions/instagram-settings-compat.d.ts +33 -0
  257. package/dist/tools/definitions/instagram-settings-compat.js +7 -0
  258. package/dist/tools/definitions/instagram-settings-compat.js.map +1 -0
  259. package/dist/tools/definitions/knowledge-chunking.d.ts +42 -0
  260. package/dist/tools/definitions/knowledge-chunking.js +83 -0
  261. package/dist/tools/definitions/knowledge-chunking.js.map +1 -0
  262. package/dist/tools/definitions/knowledge-space-search-fields.d.ts +12 -0
  263. package/dist/tools/definitions/knowledge-space-search-fields.js +30 -0
  264. package/dist/tools/definitions/knowledge-space-search-fields.js.map +1 -0
  265. package/dist/tools/definitions/knowledge.d.ts +24 -14
  266. package/dist/tools/definitions/knowledge.js +342 -233
  267. package/dist/tools/definitions/knowledge.js.map +1 -1
  268. package/dist/tools/definitions/legacy-trigger-repeat.d.ts +17 -0
  269. package/dist/tools/definitions/legacy-trigger-repeat.js +53 -0
  270. package/dist/tools/definitions/legacy-trigger-repeat.js.map +1 -0
  271. package/dist/tools/definitions/link-tracking.d.ts +8 -0
  272. package/dist/tools/definitions/link-tracking.js +141 -38
  273. package/dist/tools/definitions/link-tracking.js.map +1 -1
  274. package/dist/tools/definitions/mailing-variant-content.d.ts +75 -0
  275. package/dist/tools/definitions/mailing-variant-content.js +73 -0
  276. package/dist/tools/definitions/mailing-variant-content.js.map +1 -0
  277. package/dist/tools/definitions/model-comparison.d.ts +1 -0
  278. package/dist/tools/definitions/model-comparison.js +4 -5
  279. package/dist/tools/definitions/model-comparison.js.map +1 -1
  280. package/dist/tools/definitions/money-currency.d.ts +5 -0
  281. package/dist/tools/definitions/money-currency.js +10 -0
  282. package/dist/tools/definitions/money-currency.js.map +1 -0
  283. package/dist/tools/definitions/operator-workflow.d.ts +5 -5
  284. package/dist/tools/definitions/operator-workflow.js +80 -27
  285. package/dist/tools/definitions/operator-workflow.js.map +1 -1
  286. package/dist/tools/definitions/platform-funnel-analytics.d.ts +42 -0
  287. package/dist/tools/definitions/platform-funnel-analytics.js +72 -0
  288. package/dist/tools/definitions/platform-funnel-analytics.js.map +1 -0
  289. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.d.ts +42 -0
  290. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.js +51 -0
  291. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.js.map +1 -0
  292. package/dist/tools/definitions/platform-settings-limits.d.ts +5 -0
  293. package/dist/tools/definitions/platform-settings-limits.js +6 -0
  294. package/dist/tools/definitions/platform-settings-limits.js.map +1 -0
  295. package/dist/tools/definitions/platform-settings-shipped-form.d.ts +37 -0
  296. package/dist/tools/definitions/platform-settings-shipped-form.js +310 -0
  297. package/dist/tools/definitions/platform-settings-shipped-form.js.map +1 -0
  298. package/dist/tools/definitions/platform-settings-update.d.ts +300 -0
  299. package/dist/tools/definitions/platform-settings-update.js +134 -0
  300. package/dist/tools/definitions/platform-settings-update.js.map +1 -0
  301. package/dist/tools/definitions/{getcourse-wave.d.ts → platform-setup-wave.d.ts} +75 -6
  302. package/dist/tools/definitions/platform-setup-wave.js +1079 -0
  303. package/dist/tools/definitions/platform-setup-wave.js.map +1 -0
  304. package/dist/tools/definitions/platform-setup.d.ts +701 -222
  305. package/dist/tools/definitions/platform-setup.js +313 -752
  306. package/dist/tools/definitions/platform-setup.js.map +1 -1
  307. package/dist/tools/definitions/platform-widget-page-rules.schema.d.ts +10 -0
  308. package/dist/tools/definitions/platform-widget-page-rules.schema.js +7 -0
  309. package/dist/tools/definitions/platform-widget-page-rules.schema.js.map +1 -0
  310. package/dist/tools/definitions/prompt-authoring.constants.d.ts +2 -0
  311. package/dist/tools/definitions/prompt-authoring.constants.js +3 -0
  312. package/dist/tools/definitions/prompt-authoring.constants.js.map +1 -0
  313. package/dist/tools/definitions/prompt-batch-controls.d.ts +5 -0
  314. package/dist/tools/definitions/prompt-batch-controls.js +40 -0
  315. package/dist/tools/definitions/prompt-batch-controls.js.map +1 -0
  316. package/dist/tools/definitions/prompt-draft-authoring.d.ts +5 -0
  317. package/dist/tools/definitions/prompt-draft-authoring.js +24 -0
  318. package/dist/tools/definitions/prompt-draft-authoring.js.map +1 -0
  319. package/dist/tools/definitions/prompt-text-projection.d.ts +11 -0
  320. package/dist/tools/definitions/prompt-text-projection.js +62 -0
  321. package/dist/tools/definitions/prompt-text-projection.js.map +1 -0
  322. package/dist/tools/definitions/reviewer-paid.d.ts +19 -0
  323. package/dist/tools/definitions/reviewer-paid.js +115 -0
  324. package/dist/tools/definitions/reviewer-paid.js.map +1 -0
  325. package/dist/tools/definitions/routing-condition-projection.d.ts +31 -0
  326. package/dist/tools/definitions/routing-condition-projection.js +97 -0
  327. package/dist/tools/definitions/routing-condition-projection.js.map +1 -0
  328. package/dist/tools/definitions/schedule-selection-hints.d.ts +3 -0
  329. package/dist/tools/definitions/schedule-selection-hints.js +42 -0
  330. package/dist/tools/definitions/schedule-selection-hints.js.map +1 -0
  331. package/dist/tools/definitions/{scheduled-tasks.d.ts → scheduled-task-alias.d.ts} +12 -10
  332. package/dist/tools/definitions/scheduled-task-alias.js +207 -0
  333. package/dist/tools/definitions/scheduled-task-alias.js.map +1 -0
  334. package/dist/tools/definitions/session.js +3 -3
  335. package/dist/tools/definitions/session.js.map +1 -1
  336. package/dist/tools/definitions/setup-init-operation.schema.d.ts +2 -0
  337. package/dist/tools/definitions/setup-init-operation.schema.js +6 -0
  338. package/dist/tools/definitions/setup-init-operation.schema.js.map +1 -0
  339. package/dist/tools/definitions/shared-input.helpers.d.ts +51 -0
  340. package/dist/tools/definitions/shared-input.helpers.js +323 -0
  341. package/dist/tools/definitions/shared-input.helpers.js.map +1 -0
  342. package/dist/tools/definitions/shared.d.ts +414 -284
  343. package/dist/tools/definitions/shared.js +436 -619
  344. package/dist/tools/definitions/shared.js.map +1 -1
  345. package/dist/tools/definitions/shipped-output-names.d.ts +19 -0
  346. package/dist/tools/definitions/shipped-output-names.js +123 -0
  347. package/dist/tools/definitions/shipped-output-names.js.map +1 -0
  348. package/dist/tools/definitions/task-state.d.ts +5 -5
  349. package/dist/tools/definitions/task-state.js +2 -1
  350. package/dist/tools/definitions/task-state.js.map +1 -1
  351. package/dist/tools/definitions/telegram-personal-capacity.d.ts +4 -0
  352. package/dist/tools/definitions/telegram-personal-capacity.js +18 -0
  353. package/dist/tools/definitions/telegram-personal-capacity.js.map +1 -0
  354. package/dist/tools/definitions/telegram-pool-policy.d.ts +8 -0
  355. package/dist/tools/definitions/telegram-pool-policy.js +56 -0
  356. package/dist/tools/definitions/telegram-pool-policy.js.map +1 -0
  357. package/dist/tools/definitions/touch-history-alias.d.ts +4 -0
  358. package/dist/tools/definitions/touch-history-alias.js +99 -0
  359. package/dist/tools/definitions/touch-history-alias.js.map +1 -0
  360. package/dist/tools/definitions/trigger-action-item.schema.d.ts +254 -0
  361. package/dist/tools/definitions/trigger-action-item.schema.js +121 -0
  362. package/dist/tools/definitions/trigger-action-item.schema.js.map +1 -0
  363. package/dist/tools/definitions/trigger-argument.schema.d.ts +14 -0
  364. package/dist/tools/definitions/trigger-argument.schema.js +23 -0
  365. package/dist/tools/definitions/trigger-argument.schema.js.map +1 -0
  366. package/dist/tools/definitions/trigger-claim-alias.d.ts +10 -0
  367. package/dist/tools/definitions/trigger-claim-alias.js +90 -0
  368. package/dist/tools/definitions/trigger-claim-alias.js.map +1 -0
  369. package/dist/tools/definitions/trigger-echo-projection.d.ts +1 -0
  370. package/dist/tools/definitions/trigger-echo-projection.js +41 -0
  371. package/dist/tools/definitions/trigger-echo-projection.js.map +1 -0
  372. package/dist/tools/definitions/trigger-upsert.tool.d.ts +29 -0
  373. package/dist/tools/definitions/trigger-upsert.tool.js +122 -0
  374. package/dist/tools/definitions/trigger-upsert.tool.js.map +1 -0
  375. package/dist/tools/definitions/v15-port-descriptions.d.ts +11 -0
  376. package/dist/tools/definitions/v15-port-descriptions.js +30 -0
  377. package/dist/tools/definitions/v15-port-descriptions.js.map +1 -0
  378. package/dist/tools/definitions/variables.d.ts +44 -0
  379. package/dist/tools/definitions/variables.js +310 -0
  380. package/dist/tools/definitions/variables.js.map +1 -0
  381. package/dist/tools/definitions/voice-action-input.d.ts +9 -0
  382. package/dist/tools/definitions/voice-action-input.js +141 -0
  383. package/dist/tools/definitions/voice-action-input.js.map +1 -0
  384. package/dist/tools/definitions/widget-customization-compat.d.ts +51 -0
  385. package/dist/tools/definitions/widget-customization-compat.js +23 -0
  386. package/dist/tools/definitions/widget-customization-compat.js.map +1 -0
  387. package/dist/tools/definitions.d.ts +1105 -621
  388. package/dist/tools/definitions.js +57 -27
  389. package/dist/tools/definitions.js.map +1 -1
  390. package/dist/tools/description-localization.js +3 -2
  391. package/dist/tools/description-localization.js.map +1 -1
  392. package/dist/tools/follow-up-request.d.ts +19 -0
  393. package/dist/tools/follow-up-request.js +81 -0
  394. package/dist/tools/follow-up-request.js.map +1 -0
  395. package/dist/tools/followup-revision.d.ts +4 -0
  396. package/dist/tools/followup-revision.js +29 -0
  397. package/dist/tools/followup-revision.js.map +1 -0
  398. package/dist/tools/incomplete-list.js +4 -0
  399. package/dist/tools/incomplete-list.js.map +1 -1
  400. package/dist/tools/inventory.js +47 -11
  401. package/dist/tools/inventory.js.map +1 -1
  402. package/dist/tools/operation-recovery.d.ts +4 -0
  403. package/dist/tools/operation-recovery.js +33 -0
  404. package/dist/tools/operation-recovery.js.map +1 -0
  405. package/dist/tools/registry-mapping-primary.cases.d.ts +2 -0
  406. package/dist/tools/registry-mapping-primary.cases.js +753 -0
  407. package/dist/tools/registry-mapping-primary.cases.js.map +1 -0
  408. package/dist/tools/registry-mapping-secondary.cases.d.ts +2 -0
  409. package/dist/tools/registry-mapping-secondary.cases.js +626 -0
  410. package/dist/tools/registry-mapping-secondary.cases.js.map +1 -0
  411. package/dist/tools/registry-mapping.cases.d.ts +8 -0
  412. package/dist/tools/registry-mapping.cases.js +13 -0
  413. package/dist/tools/registry-mapping.cases.js.map +1 -0
  414. package/dist/tools/registry-test-fixtures.d.ts +50 -0
  415. package/dist/tools/registry-test-fixtures.js +120 -0
  416. package/dist/tools/registry-test-fixtures.js.map +1 -0
  417. package/dist/tools/registry.d.ts +2 -0
  418. package/dist/tools/registry.js +82 -52
  419. package/dist/tools/registry.js.map +1 -1
  420. package/dist/tools/result.d.ts +1 -0
  421. package/dist/tools/result.js +90 -22
  422. package/dist/tools/result.js.map +1 -1
  423. package/dist/tools/toolsets.d.ts +16 -0
  424. package/dist/tools/toolsets.js +25 -1
  425. package/dist/tools/toolsets.js.map +1 -1
  426. package/dist/types/api-contract.gen.d.ts +105862 -22115
  427. package/dist/utils/credential-signatures.d.ts +3 -0
  428. package/dist/utils/credential-signatures.js +10 -0
  429. package/dist/utils/credential-signatures.js.map +1 -0
  430. package/dist/utils/data-source-column-name.d.ts +10 -0
  431. package/dist/utils/data-source-column-name.js +36 -0
  432. package/dist/utils/data-source-column-name.js.map +1 -0
  433. package/dist/utils/logger.d.ts +1 -0
  434. package/dist/utils/logger.js +32 -8
  435. package/dist/utils/logger.js.map +1 -1
  436. package/dist/utils/secret-read-boundary.d.ts +2 -0
  437. package/dist/utils/secret-read-boundary.js +88 -0
  438. package/dist/utils/secret-read-boundary.js.map +1 -0
  439. package/dist/utils/setup-secrets.d.ts +4 -0
  440. package/dist/utils/setup-secrets.js +40 -0
  441. package/dist/utils/setup-secrets.js.map +1 -0
  442. package/docs/agent-models.md +9 -0
  443. package/docs/agent-usage-guide.full.ru.md +1 -4
  444. package/docs/analytics-contract.md +67 -0
  445. package/docs/async-operation-lifecycle.md +10 -3
  446. package/docs/connect.ru.md +144 -4
  447. package/docs/contact-company-profile.md +17 -0
  448. package/docs/contact-recommendations.md +7 -0
  449. package/docs/crm-stage-pool.md +73 -0
  450. package/docs/customer-data.md +61 -0
  451. package/docs/edit-versions.md +36 -0
  452. package/docs/failure-semantics.md +17 -9
  453. package/docs/help-navigation.md +15 -42
  454. package/docs/host-compatibility-matrix.md +72 -93
  455. package/docs/host-setup.md +33 -35
  456. package/docs/invariants/public-api-and-contracts.md +16 -0
  457. package/docs/legacy-action-settings.md +21 -0
  458. package/docs/main-transfer-dependencies.md +38 -0
  459. package/docs/mcp2-parity.md +50 -0
  460. package/docs/modules/analytics.md +79 -0
  461. package/docs/modules/besales-mcp.md +137 -0
  462. package/docs/modules/dialog-review-report.md +38 -0
  463. package/docs/modules/dialogue-followup-adapters.md +39 -0
  464. package/docs/modules/message-reactions.md +17 -0
  465. package/docs/modules/network-mode.md +284 -0
  466. package/docs/modules/prompt-batch-and-dialogue-controls.md +69 -0
  467. package/docs/pii-policy.md +21 -16
  468. package/docs/response-size-budget.md +197 -7
  469. package/docs/secret-read-boundary.md +102 -0
  470. package/docs/task-state.md +7 -2
  471. package/docs/telegram-pool-policy.md +19 -0
  472. package/docs/tool-effects.md +15 -9
  473. package/docs/tool-surface.md +205 -4
  474. package/docs/voice-actions-and-inbox.md +95 -0
  475. package/docs/wazzup-instagram-comments.md +26 -0
  476. package/package.json +17 -10
  477. package/scripts/api-source-loader.spec.mjs +40 -0
  478. package/scripts/audit-bridge-controls.mjs +129 -0
  479. package/scripts/audit-revert-mutants.mjs +107 -0
  480. package/scripts/bridge-api-input.spec.mjs +98 -0
  481. package/scripts/bridge-controls-mutants.mjs +130 -0
  482. package/scripts/brx3-shipped-refusals.spec.mjs +149 -0
  483. package/scripts/brx4-ledger-payload.spec.mjs +127 -0
  484. package/scripts/build-onprem.mjs +108 -0
  485. package/scripts/connect-clients.sh +141 -0
  486. package/scripts/connect-scopes.mjs +166 -0
  487. package/scripts/connect-scopes.spec.mjs +83 -0
  488. package/scripts/emit-personal-link-carrier.mjs +31 -0
  489. package/scripts/emit-setup-init-schemas.mjs +24 -0
  490. package/scripts/emit-tool-routes.mjs +878 -0
  491. package/scripts/emit-tool-routes.spec.mjs +300 -0
  492. package/scripts/followup-registry.spec.mjs +107 -0
  493. package/scripts/generate-crm-rotation-contract.mjs +98 -0
  494. package/scripts/generate-followup-session-contract.mjs +16 -0
  495. package/scripts/icp-import-verification/mutate.cjs +70 -0
  496. package/scripts/import-secret-read-contract.mjs +53 -0
  497. package/scripts/install-claude-desktop.js +1 -1
  498. package/scripts/lag-decisions.mjs +37 -0
  499. package/scripts/lag-decisions.spec.mjs +87 -0
  500. package/scripts/lag-open-task-owners.mjs +46 -0
  501. package/scripts/lag-open-task-owners.spec.mjs +78 -0
  502. package/scripts/lag-payload.mjs +10 -0
  503. package/scripts/legacy-action-lag.spec.mjs +22 -0
  504. package/scripts/legacy-field-refusal.shipped.spec.mjs +99 -0
  505. package/scripts/mcpcompat-handler.mjs +34 -0
  506. package/scripts/mcpcompat-loader.mjs +36 -0
  507. package/scripts/mcpcompat-typescript.mjs +3 -0
  508. package/scripts/platform-settings-shipped-parity.spec.mjs +89 -0
  509. package/scripts/prbr-round1-recheck.spec.mjs +605 -0
  510. package/scripts/prbr-shipped-compatibility.spec.mjs +276 -0
  511. package/scripts/publish-mcp-registry.mjs +98 -0
  512. package/scripts/publish-mcp-registry.spec.mjs +68 -0
  513. package/scripts/record-scheduled-task-call.mjs +26 -0
  514. package/scripts/refusal-probe.mjs +17 -0
  515. package/scripts/refusal-probe.spec.mjs +29 -0
  516. package/scripts/registry-diff.mjs +502 -0
  517. package/scripts/registry-diff.spec.mjs +787 -0
  518. package/scripts/request-query.mjs +43 -0
  519. package/scripts/request-query.spec.mjs +82 -0
  520. package/scripts/route-lag.mjs +23 -0
  521. package/scripts/route-lag.spec.mjs +30 -0
  522. package/scripts/sample-args.mjs +174 -0
  523. package/scripts/sample-args.spec.mjs +160 -0
  524. package/scripts/shipped-definitions.mjs +197 -0
  525. package/scripts/shipped-definitions.spec.mjs +57 -0
  526. package/scripts/shipped-inputs.spec.mjs +136 -0
  527. package/scripts/shipped-recordings.spec.mjs +19 -0
  528. package/scripts/shipped-ref.d.mts +5 -0
  529. package/scripts/shipped-ref.mjs +20 -0
  530. package/scripts/shipped-ref.spec.mjs +59 -0
  531. package/scripts/shipped-registry.mjs +107 -0
  532. package/scripts/shipped-sample-candidates.mjs +69 -0
  533. package/scripts/shipped-sample-candidates.spec.mjs +117 -0
  534. package/scripts/shipped-sample.mjs +868 -0
  535. package/scripts/shipped-sample.spec.mjs +451 -0
  536. package/scripts/sync-agent-model-schemas.mjs +72 -0
  537. package/scripts/sync-api-contracts.mjs +85 -0
  538. package/scripts/sync-api-contracts.spec.mjs +144 -0
  539. package/scripts/sync-contract-ledger-schemas.mjs +161 -0
  540. package/scripts/sync-crm-stage-pool-schema.mjs +85 -0
  541. package/scripts/sync-prompt-draft-schema.mjs +92 -0
  542. package/scripts/sync-route-map.mjs +94 -0
  543. package/scripts/sync-schema-descriptions.mjs +25 -0
  544. package/scripts/sync-tool-inventory.mjs +27 -0
  545. package/scripts/sync-trigger-argument-schema.mjs +121 -0
  546. package/scripts/sync-trigger-repeat-mapping.mjs +12 -0
  547. package/scripts/sync-voice-actions-contract.mjs +67 -0
  548. package/scripts/sync-voice-actions-tools.mjs +97 -0
  549. package/scripts/sync-wave-id-schemas.mjs +55 -0
  550. package/scripts/tool-input-schemas.mjs +46 -0
  551. package/scripts/tool-input-schemas.spec.mjs +50 -0
  552. package/scripts/verify-followup-adapters.mjs +235 -0
  553. package/scripts/verify-secret-read-import.mjs +84 -0
  554. package/scripts/verify-secret-read-mutants.mjs +143 -0
  555. package/dist/tools/definitions/analytics.d.ts +0 -40
  556. package/dist/tools/definitions/analytics.js +0 -233
  557. package/dist/tools/definitions/analytics.js.map +0 -1
  558. package/dist/tools/definitions/broadcast-mailing-variants.d.ts +0 -41
  559. package/dist/tools/definitions/broadcast-mailing-variants.js +0 -194
  560. package/dist/tools/definitions/broadcast-mailing-variants.js.map +0 -1
  561. package/dist/tools/definitions/dialogue-audit.d.ts +0 -38
  562. package/dist/tools/definitions/dialogue-audit.js +0 -292
  563. package/dist/tools/definitions/dialogue-audit.js.map +0 -1
  564. package/dist/tools/definitions/getcourse-wave.js +0 -519
  565. package/dist/tools/definitions/getcourse-wave.js.map +0 -1
  566. package/dist/tools/definitions/operational-overview.d.ts +0 -3
  567. package/dist/tools/definitions/operational-overview.js +0 -23
  568. package/dist/tools/definitions/operational-overview.js.map +0 -1
  569. package/dist/tools/definitions/scheduled-tasks.js +0 -173
  570. package/dist/tools/definitions/scheduled-tasks.js.map +0 -1
  571. package/dist/tools/definitions/trigger-contact-claims.d.ts +0 -7
  572. package/dist/tools/definitions/trigger-contact-claims.js +0 -60
  573. package/dist/tools/definitions/trigger-contact-claims.js.map +0 -1
  574. package/dist/tools/registry-mapping-cases-core.d.ts +0 -38
  575. package/dist/tools/registry-mapping-cases-core.js +0 -927
  576. package/dist/tools/registry-mapping-cases-core.js.map +0 -1
  577. package/dist/tools/registry-mapping-cases-platform.d.ts +0 -2
  578. package/dist/tools/registry-mapping-cases-platform.js +0 -1052
  579. package/dist/tools/registry-mapping-cases-platform.js.map +0 -1
  580. package/dist/tools/trigger-action-config.d.ts +0 -49
  581. package/dist/tools/trigger-action-config.js +0 -171
  582. package/dist/tools/trigger-action-config.js.map +0 -1
  583. package/docs/evals/model-behavior-scorecard.md +0 -80
  584. package/docs/operation-coverage.md +0 -114
  585. package/scripts/mock-credentials.js +0 -77
  586. package/scripts/model-eval-server.js +0 -213
  587. package/scripts/run-model-eval.js +0 -447
@@ -1,128 +1,94 @@
1
+ // «Первое сообщение» GetCourse (холодное приветствие): предпросмотр, запуск, пакет сегментов, ход пакета,
2
+ // возобновление и отмена. Схемы — с отгруженной линии (`getcourse-cold-welcome.ts`): клиент,
3
+ // зовущий инструменты сегодня, не получает отказа ни на одном поле, которое принимал. Отличия v2 — в теле
4
+ // запроса (канал называется channelId, подтверждение отправки людям уходит на сервер), в текстах для модели и
5
+ // в одном добавленном поле: `product_code` у предпросмотра — прогноз предохранителя обязан судить тот же
6
+ // продукт, что запуск.
1
7
  import { z } from 'zod';
2
- import { compactObject, defineTool, coldWelcomeChannelsModeEnum, getcourseTransportEnum, guardModeEnum, uuidSchema, withInstructions, } from './shared.js';
8
+ import { guardModeEnum } from './getcourse-cold-outbound-settings.js';
9
+ import { compactObject, defineTool, getcourseTransportEnum, uuidSchema, withInstructions, } from './shared.js';
3
10
  const MESSAGE_MAX_LENGTH = 4000;
4
11
  const LABEL_MAX_LENGTH = 200;
5
12
  const MAX_CONTACTS = 5000;
6
- /** Потолок сегментов в пакете — паритет с бэкендом и с батчем волны. */
13
+ /** Потолок сегментов в пакете — паритет с сервером и с пакетом волны. */
7
14
  const MAX_BATCH_SEGMENTS = 10;
15
+ const continuationSchema = z
16
+ .literal(true)
17
+ .describe('Только по явной просьбе человека продолжить обращение по файлу contacts. Сначала preview с теми же текстом, сегментом, продуктом и каналами, затем подтверждённый start. Не выбирать автоматически по alreadyWelcomed.')
18
+ .optional();
19
+ const continuationHasContacts = (input) => input.continuation === undefined || input.contacts !== undefined;
20
+ const continuationHasText = (input) => input.continuation === undefined ||
21
+ input.message !== undefined ||
22
+ input.mailing_variant_id !== undefined;
23
+ const CONTINUATION_AUDIENCE = 'continuation разрешён только по файлу contacts, без created_after';
24
+ const CONTINUATION_TEXT = 'continuation: передай message или mailing_variant_id с выбранным текстом и сегментом';
25
+ function continuationPreviewHints(response) {
26
+ const data = response;
27
+ const total = data?.totalContacts;
28
+ const narrowed = data?.narrowedOutByTransports ?? data?.narrowedOutByChannels;
29
+ return [
30
+ 'ПРОДОЛЖЕНИЕ: totalContacts — аудитория прогноза. alreadyWelcomed — справка о прежнем первом сообщении, НЕ вычитается из охвата. Охват = totalContacts − narrowedOutByTransports (прежнее имя narrowedOutByChannels); channelsUnknown не вычитается. В текущем v2 channelsUnknown=0: отправка школу не переспрашивает.',
31
+ ...(typeof total === 'number' &&
32
+ Number.isSafeInteger(total) &&
33
+ typeof narrowed === 'number' &&
34
+ Number.isSafeInteger(narrowed) &&
35
+ narrowed >= 0 &&
36
+ total >= narrowed
37
+ ? [
38
+ `Охват прогноза: ${total - narrowed}. Это прогноз, окончательная доставка видна в статусе задачи.`,
39
+ ]
40
+ : []),
41
+ 'В продолжении touchClaimed может быть больше нуля; blockedBy.alreadyGreeted=0. alreadySentSegment запрещает повтор того же текста тому же сегменту; тот же шаблон другому сегменту разрешается лишь спустя больше трёх суток. Прежние приветствия права не освобождают.',
42
+ 'Разговор блокирует отправку, только если прежде была наша реплика: человек ответил после неё либо наша реплика моложе трёх суток. Ровно трое суток временной гейт отпускает. Без нашей реплики одно сообщение человека этот гейт не включает.',
43
+ 'Продолжение выбирай только по явной просьбе человека. Сначала preview с теми же continuation, файлом, текстом, сегментом, продуктом и каналами; после подтверждения start с explicit_confirmation=true. При 400 исправь названные в ошибке текст, сегмент или список; не повторяй отправку автоматически.',
44
+ ];
45
+ }
8
46
  /**
9
- * Строка предсписка: getcourseUserId, опциональный пин сделки для штампа полей и — опционально —
10
- * момент входа человека в когорту.
11
- *
12
- * Оба ИДЕНТИФИКАТОРА — ЧИСЛА в строковом виде, и проверяется именно форма: паритет с сестринской
13
- * `besales_getcourse_wave_run_start`. Непустоты мало — «AB-123» доезжал до штампа уже ПОСЛЕ
14
- * доставки сообщения, а «user@mail.ru» молча выпадал в отбракованные строки.
47
+ * Режим набора каналов, снятый прежней системой: принимается ради старых клиентов и на поведение не
48
+ * влияет. Список `channels` значит «слать сюда», судьбу КАЖДОГО канала решает сервер по фактам о человеке.
15
49
  */
16
- const numericIdSchema = (field) => z
17
- .string()
18
- .trim()
19
- .regex(/^\d+$/, `${field} обязан быть числовым идентификатором GetCourse`);
50
+ export const coldWelcomeChannelsModeEnum = z.enum(['RESTRICT', 'ADD']);
51
+ /**
52
+ * Идентификаторы строки — ЧИСЛА в строковом виде, и проверяется именно форма: «AB-123» доезжал бы до штампа
53
+ * уже ПОСЛЕ доставки сообщения.
54
+ */
55
+ const numericIdSchema = (field) => z.string().trim().regex(/^\d+$/, `${field} обязан быть числовым идентификатором GetCourse`);
20
56
  const contactSchema = z
21
57
  .object({
22
58
  contact_id: numericIdSchema('contact_id'),
23
59
  deal_id: numericIdSchema('deal_id').optional(),
24
60
  /**
25
- * Момент входа контакта в когорту — на нём считается свежесть в «Аналитике рассылок».
26
- *
27
- * ⚠️⚠️ Заполняй его, когда предсписок собран ПРАВИЛОМ (события LeakEngine, выгрузка по
28
- * сегменту): у каждой строки там свой момент, и он ФАКТ О ЧЕЛОВЕКЕ. Пока поле не принималось,
29
- * все холодные строки уезжали без возраста и выпадали из любого непустого фильтра свежести.
30
- *
31
- * ⚠️⚠️ Дату запуска сюда подставлять НЕЛЬЗЯ. Разброс возраста в таких когортах — от часа до
32
- * 294 дней при медиане 6,3 дня: общий момент схлопнул бы месяцы когорт в одну корзину, а
33
- * разрез при этом выглядел бы наполненным. Не знаешь момента строки — не передавай ключ.
34
- *
35
- * `{ offset: true }` — зона обязательна (…Z либо ±ЧЧ:ММ), паритет с сестринской
36
- * `besales_getcourse_wave_run_start`: без зоны момент читался бы по зоне процесса, и возраст
37
- * разошёлся бы на часы между средами.
61
+ * Момент входа человека в когорту — на нём считается свежесть в «Аналитике рассылок». Дату запуска
62
+ * подставлять НЕЛЬЗЯ: общий момент схлопнул бы месяцы когорт в одну корзину. Зона обязательна.
38
63
  */
39
64
  segment_entered_at: z.string().datetime({ offset: true }).optional(),
40
65
  /**
41
- * Воронка ЭТОГО контакта — паритет со строкой волнового импорта (колонка
42
- * `funnel | funnel_name | воронка | funnel_code`).
43
- *
44
- * ⚠️⚠️ Заполняй, когда файл собран из НЕСКОЛЬКИХ воронок: одна метка на запуск заставляла
45
- * писать в поле воронки когорту («reg-no-show|дбо»), и ось «Воронка» переставала отвечать
46
- * на свой вопрос — по f11 фильтр показывал НОЛЬ отправок за 30 дней при 3267 регистрациях
47
- * и 1496 реально написанных людях (замер 02.09.2026). Когорта и продукт при этом уже лежат
48
- * в своих полях `segment` и `product_code` — дублировать их в воронке нечем.
49
- *
50
- * Не передан — берётся воронка запуска (`funnel`), поведение прежнее.
66
+ * Момент РЕГИСТРАЦИИ, по которой человек попал в сегмент, — граница потока у предохранителя: заявки и
67
+ * оплаты раньше неё не держат. Подставлять `segment_entered_at` или «созрел с» НЕЛЬЗЯ. Зона обязательна.
51
68
  */
69
+ registered_at: z.string().datetime({ offset: true }).optional(),
70
+ /** Воронка ЭТОГО человека (файл из нескольких воронок); не передана — воронка запуска. */
52
71
  funnel: z.string().min(1).max(LABEL_MAX_LENGTH).optional(),
53
72
  })
54
73
  .strict();
55
- const toContacts = (contacts) => contacts?.map((contact) => ({
56
- contactId: contact.contact_id,
74
+ const toContacts = (contacts) => contacts?.map((contact) => compactObject({
75
+ getcourseUserId: contact.contact_id,
57
76
  dealId: contact.deal_id,
58
77
  segmentEnteredAt: contact.segment_entered_at,
78
+ registeredAt: contact.registered_at,
59
79
  funnel: contact.funnel,
60
80
  }));
61
- /**
62
- * Пустая строка = поля НЕТ.
63
- *
64
- * ⚠️ В экспорте GetCourse пустая ячейка приезжает как `''`, и список, собранный из него, почти
65
- * всегда содержит пустые почты, телефоны и имена. Без этого `email: ''` завалил бы ВЕСЬ вызов
66
- * валидацией, хотя у файлового импорта та же строка проходит нормально: разбор считает пустое
67
- * значение отсутствующим. Симметрично `EmptyToUndefined` в DTO бэкенда.
68
- */
69
- const emptyAsAbsent = (schema) => z.preprocess((value) => (typeof value === 'string' && value.trim() === '' ? undefined : value), schema.optional());
70
- /**
71
- * Строка ИМПОРТА — шире отправочной: кроме идентификаторов несёт данные самого контакта.
72
- *
73
- * Ключи остаются snake_case и уезжают на бэкенд КАК ЕСТЬ: там их читает разбор по алиасам колонок
74
- * экспорта GetCourse (`contact_id`, `gc_deal_id`, `first_name`, …), тот же, что и для файла.
75
- * Переименование в camelCase завело бы вторую копию правил разбора — а разбор решает, какой
76
- * транспорт у контакта и попадёт ли он в базу вообще.
77
- */
78
- const importContactSchema = z
79
- .object({
80
- contact_id: numericIdSchema('contact_id'),
81
- deal_id: emptyAsAbsent(numericIdSchema('deal_id')),
82
- dialog_id: emptyAsAbsent(numericIdSchema('dialog_id')),
83
- transport: emptyAsAbsent(z.string().trim().max(50)),
84
- email: emptyAsAbsent(z.string().trim().email('email невалиден').max(320)),
85
- phone: emptyAsAbsent(z.string().trim().max(64)),
86
- first_name: emptyAsAbsent(z.string().trim().max(200)),
87
- last_name: emptyAsAbsent(z.string().trim().max(200)),
88
- })
89
- .strict();
90
- /**
91
- * Рассылка «Первое сообщение» (холодное приветствие GetCourse): МЫ пишем первыми тем, кому НАШЕГО
92
- * исходящего ещё не было.
93
- *
94
- * ⚠️⚠️ Именно «нашего исходящего не было», а не «диалога нет»: человек, который написал школе и
95
- * остался без ответа, — законный адресат (замер 01.09.2026: 4216 таких карточек из 8428 с диалогом,
96
- * и их частые реплики «я буду», «ок», «+» это подтверждения участия в эфире). Исключать их из
97
- * когорты НЕ надо. Отправка НЕОБРАТИМА и не идемпотентна на стороне GetCourse — повтор создал
98
- * бы второе сообщение живому человеку, поэтому старт требует explicit_confirmation и всегда
99
- * предваряется preview.
100
- */
81
+ const dealFieldsSchema = z.record(z.string().regex(/^\d+$/, 'Ключ deal_fields обязан быть числовым fieldId GetCourse'), z.union([z.string(), z.number(), z.boolean(), z.null()]));
82
+ /** Ровно одна аудитория. */
83
+ const ONE_AUDIENCE = 'Ровно одна аудитория: created_after (по дате) ЛИБО contacts (по файлу)';
84
+ /** Ровно один источник текста — у запуска и пакета. */
85
+ const ONE_TEXT_SOURCE = 'Ровно один источник текста: message (разовый) ЛИБО mailing_variant_id (сохранённая рассылка)';
86
+ const CHANNELS_MODE_DEPRECATED = '⚠️ channels_mode УСТАРЕЛ и на поведение НЕ влияет. Список channels значит «слать сюда», а судьбу КАЖДОГО канала решает сервер: мессенджер (Telegram/VK/MAX) уходит, только если школа подтвердила его наличие у человека; SMS — только при известном телефоне; Email, WhatsApp, Viber, Сайт, Chatium уходят вслепую всем выбранным. Просят «написать ещё и на почту» — добавь Email в channels, а мессенджеры перечисли явно, иначе уедет только письмо. Поле принимается ради обратной совместимости и игнорируется.';
87
+ /** Предохранитель — общий текст запуска, пакета и предпросмотра. */
88
+ const GUARD_INSTRUCTION = 'ПРЕДОХРАНИТЕЛИ. Перед отправкой каждый человек проверяется по фактам школы: идёт ли у него вебинарная серия, есть ли действующая заявка (с ним работает менеджер), оплачен ли продукт запуска. Заявка держит только отправку своего продукта. Заявки и оплаты судятся только после регистрации, по которой человек попал в сегмент: передавай её моментом contacts[].registered_at (не segment_entered_at и не «созрел с»: у reg-no-show и postweb они на дни позже, и заявка или оплата во время серии выпала бы из правила); без него судится вся история человека. Режимы правил (выключено / предупреждать / блокировать) настраиваются у канала — besales_platform_settings_update, getcourse_settings.guard_*: там решение видно всей команде. Исходы в итоге: SKIPPED_ACTIVE_SERIES, SKIPPED_ACTIVE_DEAL, SKIPPED_GUARD_UNAVAILABLE.';
89
+ const GUARD_SERIES_IS_DEFERRAL = '⚠️ Блокировка по серии — это ОТСРОЧКА, а не потеря: серия кончится, и человек станет доступен следующему запуску. Не пытайся обойти правило, чтобы «добрать объём».';
90
+ const GUARD_PAID_NEEDS_PRODUCT = '⚠️ Правило оплаты сверяет продукт запуска с оплаченными. Не задан product_code (и у рассылки нет продукта по умолчанию), а у человека есть оплата — отправка пропускается с SKIPPED_GUARD_UNAVAILABLE («сверять не с чем»): чинится запуском с продуктом.';
101
91
  export const getcourseColdWelcomeTools = [
102
- defineTool({
103
- name: 'besales_contact_touch_history',
104
- sideEffect: 'none',
105
- requiredFields: ['getcourse_user_id'],
106
- defaultWorkspaceId: true,
107
- schema: z
108
- .object({
109
- workspace_id: uuidSchema.optional(),
110
- getcourse_user_id: z.number().int().positive(),
111
- })
112
- .strict(),
113
- createRequest: (input) => ({
114
- method: 'GET',
115
- path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/touch-history/${input.getcourse_user_id}`,
116
- }),
117
- enhanceResponse: (response) => withInstructions(response, [
118
- '⚠️⚠️ touches и sends отвечают на РАЗНЫЕ вопросы, складывать их нельзя. touches — журнал касаний: это ПРАВО, взятое ДО отправки, поэтому строка есть и у попытки, которая никуда не ушла (status UNKNOWN или зависший CLAIMED). sends — реестр получателей, он пишется ПОСЛЕ доставки и best-effort: его отсутствие неотправки НЕ доказывает (замер: 10 198 приветствий против 9 802 строк).',
119
- 'На вопрос «писали ли мы этому человеку» отвечает НАЛИЧИЕ строки в touches со статусом не RELEASED (RELEASED — доказанная неотправка, касание свободно) либо непустой firstWelcomeAt.',
120
- 'touchKey — служебное имя права, не показывай его пользователю как есть. Четыре рода: ярлык сегмента, text:<отпечаток шаблона> (правило «тот же текст не дважды»), registration:<заказ> (приветствие регистранта), legacy:welcome (право, восстановленное за период до появления журнала). Человеку называй mailingName и дату.',
121
- 'mailingName пуст у строк до 09.09.2026 — колонки тогда не было; это не потеря данных.',
122
- 'Ключ — человек ШКОЛЫ, поэтому история включает касания со ВСЕХ площадок воркспейса. Это и есть право: две площадки одной школы — один живой человек.',
123
- 'truncated=true — строк больше, чем поместилось; для разбора этого обычно достаточно, полная выгрузка сюда не помещается намеренно.',
124
- ]),
125
- }),
126
92
  defineTool({
127
93
  name: 'besales_getcourse_cold_welcome_preview',
128
94
  sideEffect: 'none',
@@ -134,47 +100,76 @@ export const getcourseColdWelcomeTools = [
134
100
  platform_id: uuidSchema,
135
101
  created_after: z.string().datetime({ offset: true }).optional(),
136
102
  contacts: z.array(contactSchema).min(1).max(MAX_CONTACTS).optional(),
103
+ continuation: continuationSchema,
137
104
  mailing_variant_id: uuidSchema.optional(),
138
105
  /**
139
- * ⚠️⚠️ ИДЕНТИЧНОСТЬ КАСАНИЯ: метка сегмента и разовый текст. Ими считается прогноз правил
140
- * отправки — «этим сегментом уже слали» ключуется меткой, «тот же текст не дважды»
141
- * отпечатком шаблона. Без них прогноз молчит там, где старт ответит DUPLICATE.
106
+ * ⚠️⚠️ ИДЕНТИЧНОСТЬ КАСАНИЯ: сегмент и текст запуска — ими считается прогноз правил («этим сегментом
107
+ * уже слали», «тот же текст не дважды»). Передавай ровно те, с которыми пойдёт старт.
142
108
  */
143
109
  segment: z.string().max(LABEL_MAX_LENGTH).optional(),
144
110
  message: z.string().max(MESSAGE_MAX_LENGTH).optional(),
111
+ /**
112
+ * ⚠️⚠️ Продукт запуска — тот же, что пойдёт в старт: им предохранитель сверяет оплату и живую заявку.
113
+ * Без него прогноз судил бы «продукт неизвестен» и показал бы охват, которого у запуска с продуктом не будет.
114
+ */
115
+ product_code: z.string().max(LABEL_MAX_LENGTH).optional(),
145
116
  channels: z.array(getcourseTransportEnum).optional(),
146
117
  channels_mode: coldWelcomeChannelsModeEnum.optional(),
147
118
  })
148
119
  .strict()
149
120
  .refine((input) => (input.created_after !== undefined) !== (input.contacts !== undefined), {
150
- message: 'Ровно одна аудитория: created_after (по дате) ЛИБО contacts (по файлу)',
151
- }),
121
+ message: ONE_AUDIENCE,
122
+ })
123
+ .refine(continuationHasContacts, { message: CONTINUATION_AUDIENCE })
124
+ .refine(continuationHasText, { message: CONTINUATION_TEXT })
125
+ .refine((input) => input.continuation !== true ||
126
+ input.message === undefined ||
127
+ input.mailing_variant_id === undefined, { message: ONE_TEXT_SOURCE }),
152
128
  createRequest: (input) => ({
153
129
  method: 'POST',
154
130
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/preview`,
155
131
  body: compactObject({
156
- platformId: input.platform_id,
132
+ channelId: input.platform_id,
157
133
  createdAfter: input.created_after,
158
134
  contacts: toContacts(input.contacts),
135
+ continuation: input.continuation,
159
136
  mailingVariantId: input.mailing_variant_id,
160
137
  segment: input.segment,
161
138
  message: input.message,
162
- channels: input.channels,
163
- channelsMode: input.channels_mode,
139
+ productCode: input.product_code,
140
+ transports: input.channels,
141
+ transportsMode: input.channels_mode,
164
142
  }),
165
143
  }),
166
- enhanceResponse: (response) => withInstructions(response, [
167
- 'totalContacts — сколько ЕЩЁ может получить первое сообщение; alreadyWelcomed — сколько уже получали его раньше (оно одноразово НА КОНТАКТ, не на рассылку).',
168
- '⚠️⚠️ Если задано ограничение каналов, ФАКТИЧЕСКИЙ охват = totalContacts − narrowedOutByChannels (доказанно отсечённые каналами). Называй пользователю именно эту разность: сырой totalContacts завысит обещание, а отправку не отозвать. channelsUnknown вычитать НЕ надо — у этих контактов привязки просто ещё не проверены, при отправке их спросят у школы живьём.',
169
- `⚠️ channels_mode УСТАРЕЛ и на поведение НЕ влияет (с 23.08.2026). Список channels значит «слать сюда», а судьбу КАЖДОГО канала решает сервер: мессенджер (Telegram/VK/MAX) уходит, только если школа подтвердила его наличие у контакта; SMS — только при известном телефоне; Email, WhatsApp, Viber, Сайт, Chatium уходят вслепую всем выбранным. Просят «написать ещё и на почту» — добавь Email в channels, а мессенджеры перечисли явно, иначе уедет только письмо. Поле принимается ради обратной совместимости и игнорируется.`,
170
- 'channelsNotAllowedByPlatform — выбранные каналы, не включённые на площадке: они не уедут ни в каком режиме. Назови их пользователю, это чинится в настройках площадки, а не повтором запуска.',
171
- 'Для аудитории по файлу дополнительно приходят alreadyInDialog и notInBase: контакты предсписка обязаны быть импортированы (Шаг 1) до запуска.',
172
- 'Всегда показывай пользователю фактический охват и alreadyWelcomed ДО старта: отправку нельзя отозвать.',
173
- 'Часть контактов отсеется уже в прогоне (мьют бота, нет даты эфира, недостижимость, переменные без значения) — разбивка по причинам будет в outcomes задачи.',
174
- '⚠️⚠️ policyForecast — ПРАВИЛА ОТПРАВКИ. blockedBy.alreadySentSegment — этим сегментом человеку уже уходило НАШЕЙ отправкой; blockedBy.alreadyGreeted — уже получал первое сообщение; blockedBy.activeEpisode — идёт приветствие по регистрации; blockedBy.cooldown — тихий период (пока не настраивается); touchClaimed — тот же ТЕКСТ уже занят в журнале под другим ярлыком. Причины взаимно исключают друг друга, поэтому сумма — это ЛЮДИ, а не срабатывания: называй пользователю сумму, разбор — только по запросу.',
175
- '⚠️⚠️ policyForecast считается по МЕТКЕ и ТЕКСТУ. Без segment (и без message у запуска разовым текстом) правила молча дают нули — передавай в preview ровно те segment/message, с которыми пойдёт старт.',
176
- '⚠️ policyForecast.externalWaveTouched — ИНФОРМАЦИЯ, а не запрет: этим ярлыком человека рассылала школа своими процессами (или WABA-волна), и наш догон в мессенджерах уйдёт как обычно. В отсев это число НЕ входит.',
177
- '⚠️ Прогноз — на МОМЕНТ ЗАПРОСА. Блокирует сама отправка: она пересчитывает правила постранично вплотную к транспорту, поэтому итог в outcomes может отличаться (SKIPPED_ALREADY_SENT_SEGMENT, SKIPPED_ACTIVE_EPISODE, SKIPPED_SESSION_AHEAD).',
144
+ enhanceResponse: (response, input) => withInstructions(response, [
145
+ ...(input.continuation === true ? continuationPreviewHints(response) : []),
146
+ input.continuation === true
147
+ ? 'Ничего не отправляет и ничего не пишет. Для продолжения нужен ровно один источник текста: message либо mailing_variant_id с выбранным непустым текстом и сегментом. Отсутствие или пробельный текст, вариант без выбранного текста или сегмента — ошибка: исправь текст/сегмент до запуска.'
148
+ : 'Ничего не отправляет и ничего не пишет. В предпросмотре message может переопределять текст mailing_variant_id; настройки рассылки при этом сохраняются. У запуска выбери ровно один источник текста.',
149
+ ...(input.continuation === true
150
+ ? []
151
+ : [
152
+ '⚠️ Текст предпросмотру не обязателен: без message и mailing_variant_id (или когда у рассылки нет текста для сегмента запуска) охват считается без проверок текста, textSource: null, а warnings[] с code TEXT_CHECKS_SKIPPED словами называет причину. Скажи пользователю, что запуск без текста не начнётся: нужен текст или рассылка с текстом для этого сегмента.',
153
+ 'totalContacts — сколько ЕЩЁ может получить первое сообщение; alreadyWelcomed — сколько уже получали его раньше (оно одноразово НА ЧЕЛОВЕКА, не на рассылку); foundContacts — сколько нашлось всего.',
154
+ '⚠️⚠️ ФАКТИЧЕСКИЙ охват = totalContacts − narrowedOutByChannels (доказанно недостижимые выбранными каналами). Называй пользователю именно эту разность: сырой totalContacts завысит обещание, а отправку не отозвать. channelsUnknown всегда 0: отправка школу не переспрашивает.',
155
+ ]),
156
+ CHANNELS_MODE_DEPRECATED,
157
+ 'transportsNotAllowedByChannel — выбранные каналы, не включённые у канала GetCourse: они не уедут ни в каком случае. Назови их пользователю, это чинится в настройках канала, а не повтором запуска.',
158
+ 'segment и textSource — какой сегмент (в том числе сегмент рассылки по умолчанию) и какой текст уйдут: разовый (MESSAGE), текст сегмента у рассылки (SEGMENT) или общий текст рассылки (DEFAULT); null — текста нет (причина — в warnings). Назови их пользователю ДО подтверждения.',
159
+ 'alreadyInDialog — разговор с человеком уже идёт; blockedByContact — состояние человека запрещает писать (удалён, заблокирован, отказался, у менеджера, бот заглушён) по исходу. Для аудитории по файлу дополнительно приходят notInBase (человека нет на канале: импортируй его до запуска), invalidRows, duplicateRows, rowsWithoutSegmentEntry и rowsWithoutRegistration.',
160
+ '⚠️⚠️ guardForecast — ПРЕДОХРАНИТЕЛЬ: blockedBy — сколько людей он остановит по правилу (идёт вебинарная серия, действующая заявка, оплатил продукт; guardUnverified — проверить было нечем), warned — правило в режиме «предупреждать» сработало, отправка пойдёт; unverifiedCause — почему проверить не смогли. Заявки и оплаты судятся после contacts[].registered_at, если он передан, иначе за всю историю человека.',
161
+ '⚠️⚠️ product_code передавай ТОТ ЖЕ, с которым пойдёт besales_getcourse_cold_welcome_start: им предохранитель сверяет оплату и живую заявку (заявка держит только отправку своего продукта). Не передан — берётся продукт рассылки по умолчанию; нет и его — продукт неизвестен, и прогноз назовёт заблокированными людей, которых запуск с продуктом пропустил бы.',
162
+ 'Часть людей отсеется только в прогоне: нет даты эфира календарной цепочки, переменные текста без значения — разбивка будет в outcomes задачи.',
163
+ ...(input.continuation === true
164
+ ? [
165
+ 'policyForecast — правила отправки: alreadySentSegment — этот текст этим сегментом уже отправлен; activeEpisode — идёт приветствие по регистрации; cooldown — тихий период. Причины взаимно исключают друг друга: сумма — люди, а не срабатывания.',
166
+ ]
167
+ : [
168
+ '⚠️⚠️ policyForecast — ПРАВИЛА ОТПРАВКИ. blockedBy.alreadySentSegment — этим сегментом человеку уже уходило НАШЕЙ отправкой; blockedBy.alreadyGreeted — уже получал первое сообщение; blockedBy.activeEpisode — идёт приветствие по регистрации; blockedBy.resendPause — пауза повтора сегмента (только у расписания); blockedBy.cooldown — тихий период; touchClaimed — касание этого сегмента уже занято (только у продолжения расписания; у ручного запуска 0). Причины взаимно исключают друг друга, поэтому сумма — это ЛЮДИ, а не срабатывания: называй пользователю сумму, разбор — только по запросу.',
169
+ ]),
170
+ '⚠️ policyForecast.externalWaveTouched — ИНФОРМАЦИЯ, а не запрет: этим сегментом человека рассылала школа своими процессами, и наш догон в мессенджерах уйдёт как обычно. В отсев это число НЕ входит.',
171
+ 'warnings — предупреждения словами, с кодом: TEXT_CHECKS_SKIPPED — текста нет, охват посчитан без проверок текста (почему — в message); ENTRY_VARIABLE_NOT_IN_FIELD_MAP — переменная входа календарной цепочки не в карте полей сделки: первое сообщение уйдёт, а цепочка человека не подхватит. Назови их пользователю до запуска.',
172
+ '⚠️ Прогноз — на МОМЕНТ ЗАПРОСА. Блокирует сама отправка: она пересчитывает правила постранично вплотную к транспорту, поэтому итог в outcomes может отличаться.',
178
173
  ]),
179
174
  }),
180
175
  defineTool({
@@ -189,39 +184,39 @@ export const getcourseColdWelcomeTools = [
189
184
  platform_id: uuidSchema.optional(),
190
185
  created_after: z.string().datetime({ offset: true }).optional(),
191
186
  contacts: z.array(contactSchema).min(1).max(MAX_CONTACTS).optional(),
192
- deal_fields: z
193
- .record(z.string().regex(/^\d+$/, 'Ключ deal_fields обязан быть числовым fieldId GetCourse'), z.union([z.string(), z.number(), z.boolean(), z.null()]))
194
- .optional(),
187
+ continuation: continuationSchema,
188
+ deal_fields: dealFieldsSchema.optional(),
195
189
  message: z.string().trim().min(1).max(MESSAGE_MAX_LENGTH).optional(),
196
190
  mailing_variant_id: uuidSchema.optional(),
197
191
  segment: z.string().max(LABEL_MAX_LENGTH).optional(),
198
192
  product_code: z.string().max(LABEL_MAX_LENGTH).optional(),
199
- /**
200
- * ⚠️⚠️ Воронка ШКОЛЫ («f11 — ДБО / NR / ВК / In Stream»), а НЕ когорта. Тип отбора идёт
201
- * в `segment`, продукт в `product_code`, подпись запуска в `cohort_label`. Текст правила
202
- * живёт в описании поля (`mcp-tools.json`) — его читает агент; здесь напоминание для
203
- * того, кто правит схему.
204
- */
193
+ /** Воронка ШКОЛЫ («f11 — ДБО / NR / ВК / In Stream»), а НЕ когорта: текст правила — в описании поля. */
205
194
  funnel: z.string().max(LABEL_MAX_LENGTH).optional(),
206
195
  cohort_label: z.string().max(LABEL_MAX_LENGTH).optional(),
207
196
  channels: z.array(getcourseTransportEnum).optional(),
208
197
  channels_mode: coldWelcomeChannelsModeEnum.optional(),
209
198
  explicit_confirmation: z.literal(true),
199
+ idempotency_key: uuidSchema
200
+ .describe('UUID намерения: сохрани до отправки с подтверждёнными файлом и текстом. Повтор того же запуска — с тем же ключом; новый подтверждённый запуск — с новым. Передаётся заголовком Idempotency-Key.')
201
+ .optional(),
210
202
  })
211
203
  .strict()
212
204
  .refine((input) => (input.message !== undefined) !== (input.mailing_variant_id !== undefined), {
213
- message: 'Ровно один источник текста: message (разовый) ЛИБО mailing_variant_id (сохранённая рассылка)',
205
+ message: ONE_TEXT_SOURCE,
214
206
  })
215
207
  .refine((input) => (input.created_after !== undefined) !== (input.contacts !== undefined), {
216
- message: 'Ровно одна аудитория: created_after (по дате) ЛИБО contacts (по файлу)',
217
- }),
208
+ message: ONE_AUDIENCE,
209
+ })
210
+ .refine(continuationHasContacts, { message: CONTINUATION_AUDIENCE }),
218
211
  createRequest: (input) => ({
219
212
  method: 'POST',
220
213
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome`,
214
+ ...(input.idempotency_key === undefined ? {} : { idempotencyKey: input.idempotency_key }),
221
215
  body: compactObject({
222
- platformId: input.platform_id,
216
+ channelId: input.platform_id,
223
217
  createdAfter: input.created_after,
224
218
  contacts: toContacts(input.contacts),
219
+ continuation: input.continuation,
225
220
  dealFields: input.deal_fields,
226
221
  message: input.message,
227
222
  mailingVariantId: input.mailing_variant_id,
@@ -229,23 +224,30 @@ export const getcourseColdWelcomeTools = [
229
224
  productCode: input.product_code,
230
225
  funnel: input.funnel,
231
226
  cohortLabel: input.cohort_label,
232
- channels: input.channels,
233
- channelsMode: input.channels_mode,
227
+ transports: input.channels,
228
+ transportsMode: input.channels_mode,
229
+ explicitConfirmation: input.explicit_confirmation,
234
230
  }),
235
231
  }),
236
- enhanceResponse: (response) => withInstructions(response, [
237
- 'Рассылка запущена фоном: опрашивай besales_getcourse_contact_import_status по jobId каждые 10–30 секунд до COMPLETED, PARTIAL или FAILED; прогресс и разбивка исходов живут в этой задаче импорта.',
238
- 'cohort_label — имя ЭТОГО запуска в «Аналитике рассылок». Задавай его на каждом запуске: без него разрез «по запускам» различает их только датой, а два запуска одной рассылки за день сольются в одну подпись.',
232
+ enhanceResponse: (response, input) => withInstructions(response, [
233
+ 'Сохрани idempotency_key вместе с подтверждёнными параметрами. При неизвестном исходе сверяй статус и повторяй только тот же запуск с тем же ключом, не создавай новое намерение автоматически.',
234
+ 'Рассылка запущена фоном: опрашивай besales_getcourse_contact_import_status по jobId каждые 10–30 секунд до COMPLETED, PARTIAL или FAILED; прогресс, разбивка исходов (outcomes), отправленные «вслепую» и штамп полей сделки (dealStamp) живут в этой задаче. Запуск идёт пакетом из одного сегмента: batchId — его пакет, ход виден и через besales_getcourse_cold_welcome_batch_status.',
235
+ 'policyForecast — прогноз правил на момент старта; null — прогноз не посчитался (отказ чтения), запуск при этом идёт. null НЕ значит «никого не заблокировали».',
236
+ 'cohort_label — имя ЭТОГО запуска в «Аналитике рассылок». Задавай его на каждом запуске: без него разрез «по запускам» различает их только датой.',
239
237
  'Текст заморожен снимком: правка рассылки НЕ меняет идущий запуск.',
240
- 'Если у рассылки заданы message_text_by_segment и segment совпал — уедет ТЕКСТ СЕГМЕНТА. Назови пользователю, какой текст выбран, ДО подтверждения: отправку не отозвать.',
241
- '⚠️ Метка segment аудиторию НЕ отбирает. Поэтому посегментный текст разрешён только с contacts (файл): с created_after он ушёл бы ВСЕЙ когорте после даты и выжег бы отметку приветствия — второй сегмент получил бы нулевой охват. Такой запуск отвергается 400.',
242
- 'Повторный запуск по той же границе безопасен: уже получившие первое сообщение не получат второе.',
238
+ 'Если у рассылки заданы тексты по сегментам и segment совпал — уедет ТЕКСТ СЕГМЕНТА. Назови пользователю, какой текст выбран, ДО подтверждения: отправку не отозвать.',
239
+ '⚠️ Сегмент аудиторию НЕ отбирает. Поэтому посегментный текст разрешён только с contacts (файл): с created_after он ушёл бы ВСЕЙ когорте после даты. Такой запуск отвергается 400.',
240
+ input.continuation === true
241
+ ? 'Продолжение поставлено в очередь. alreadyWelcomed — справка; тот же текст тому же сегменту повторно не уйдёт. Прогноз и постановка не доказывают доставку: итог — в статусе задачи.'
242
+ : 'Повторный запуск по той же границе безопасен: уже получившие первое сообщение не получат второе.',
243
243
  'platform_id и deal_fields можно опустить, если они заданы в рассылке как «по умолчанию» — сервер подставит их сам.',
244
- 'Пустой объект deal_fields — НЕ то же, что отсутствие ключа: он означает «на этом запуске без полей» и гасит дефолты рассылки.',
245
- `⚠️ channels_mode УСТАРЕЛ и на поведение НЕ влияет (с 23.08.2026). Список channels значит «слать сюда», а судьбу КАЖДОГО канала решает сервер: мессенджер (Telegram/VK/MAX) уходит, только если школа подтвердила его наличие у контакта; SMS — только при известном телефоне; Email, WhatsApp, Viber, Сайт, Chatium уходят вслепую всем выбранным. Просят «написать ещё и на почту» — добавь Email в channels, а мессенджеры перечисли явно, иначе уедет только письмо. Поле принимается ради обратной совместимости и игнорируется.`,
246
- 'deal_fields штампуются ПОСЛЕ успешной отправки (пин из строки файла, иначе закреплённая сделка контакта); их отказ не отменяет отправку и виден счётчиком dealStamp.',
247
- 'contacts[].segment_entered_at — точный ISO-момент (с зоной) входа человека в когорту; на нём считается свежесть в «Аналитике рассылок». Передавай его, когда предсписок собран ПРАВИЛОМ и у каждой строки есть свой момент события. ⚠️ Дату запуска подставлять НЕЛЬЗЯ: разброс возраста бывает от часа до 294 дней, и общий момент схлопнул бы месяцы когорт в одну.',
248
- 'Контакт, у которого переменная текста осталась без значения, ПРОПУСКАЕТСЯ (outcome SKIPPED_UNRESOLVED_VARS) — сырые {скобки} живому человеку не уедут. Отметка приветствия при этом не сгорает: проставьте значение или перезапись у агента и запустите повторно, контакт доберётся.',
244
+ 'Пустой объект deal_fields — НЕ то же, что отсутствие ключа: он означает «на этом запуске без полей» и гасит умолчания рассылки.',
245
+ CHANNELS_MODE_DEPRECATED,
246
+ 'deal_fields штампуются ПОСЛЕ успешной отправки (пин из строки файла, иначе сделка человека); их отказ не отменяет отправку и виден счётчиком dealStamp в besales_getcourse_contact_import_status.',
247
+ 'contacts[].segment_entered_at — точный ISO-момент (с зоной) входа человека в когорту; на нём считается свежесть в «Аналитике рассылок». ⚠️ Дату запуска подставлять НЕЛЬЗЯ.',
248
+ GUARD_INSTRUCTION,
249
+ GUARD_PAID_NEEDS_PRODUCT,
250
+ 'Человек, у которого переменная текста осталась без значения, ПРОПУСКАЕТСЯ (outcome SKIPPED_UNRESOLVED_VARS) — сырые {скобки} живому человеку не уедут. Отметка приветствия при этом не сгорает: проставь значение и запусти повторно, человек доберётся.',
249
251
  ]),
250
252
  }),
251
253
  defineTool({
@@ -268,19 +270,14 @@ export const getcourseColdWelcomeTools = [
268
270
  product_code: z.string().max(LABEL_MAX_LENGTH).optional(),
269
271
  funnel: z.string().max(LABEL_MAX_LENGTH).optional(),
270
272
  cohort_label: z.string().max(LABEL_MAX_LENGTH).optional(),
271
- deal_fields: z
272
- .record(z
273
- .string()
274
- .regex(/^\d+$/, 'Ключ deal_fields обязан быть числовым fieldId GetCourse'), z.union([z.string(), z.number(), z.boolean(), z.null()]))
275
- .optional(),
273
+ deal_fields: dealFieldsSchema.optional(),
276
274
  contacts: z.array(contactSchema).min(1).max(MAX_CONTACTS),
277
275
  })
278
276
  .strict())
279
277
  .min(1)
280
278
  .max(MAX_BATCH_SEGMENTS),
281
- // Перекрытие предохранителей на ЭТОТ пакет. Не передано — настройки площадки.
282
- // Ослабление здесь — аварийный обход одной отправки; постоянное решение принимается
283
- // в настройках площадки, чтобы его было видно всем, а не только автору вызова.
279
+ // Перекрытие предохранителей на ЭТОТ пакет. Не передано — настройки канала. Ослабление здесь —
280
+ // аварийный обход одной отправки; постоянное решение принимается в настройках канала.
284
281
  guards: z
285
282
  .object({
286
283
  active_series: guardModeEnum.optional(),
@@ -292,18 +289,16 @@ export const getcourseColdWelcomeTools = [
292
289
  explicit_confirmation: z.literal(true),
293
290
  })
294
291
  .strict()
295
- .refine((input) => (input.message !== undefined) !== (input.mailing_variant_id !== undefined), {
296
- message: 'Ровно один источник текста: message (разовый) ЛИБО mailing_variant_id (сохранённая рассылка)',
297
- }),
292
+ .refine((input) => (input.message !== undefined) !== (input.mailing_variant_id !== undefined), { message: ONE_TEXT_SOURCE }),
298
293
  createRequest: (input) => ({
299
294
  method: 'POST',
300
295
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/batch`,
301
296
  body: compactObject({
302
- platformId: input.platform_id,
297
+ channelId: input.platform_id,
303
298
  message: input.message,
304
299
  mailingVariantId: input.mailing_variant_id,
305
- channels: input.channels,
306
- channelsMode: input.channels_mode,
300
+ transports: input.channels,
301
+ transportsMode: input.channels_mode,
307
302
  segments: input.segments.map((segment) => compactObject({
308
303
  segment: segment.segment,
309
304
  productCode: segment.product_code,
@@ -319,24 +314,24 @@ export const getcourseColdWelcomeTools = [
319
314
  paid: input.guards.paid,
320
315
  })
321
316
  : undefined,
317
+ explicitConfirmation: input.explicit_confirmation,
322
318
  }),
323
319
  }),
324
320
  enhanceResponse: (response) => withInstructions(response, [
325
- 'Пакет: несколько сегментов одним вызовом, у каждого свой список контактов, свои метки и свои поля сделки.',
326
- 'cohort_label задаётся ПОСЕГМЕНТНО: каждый сегмент — отдельный запуск со своей задачей и когортой, и общая на пакет подпись схлопнула бы их в отчёте в одну корзину.',
327
- 'Сегменты идут ПО ОЧЕРЕДИ — задачи импорта не могут выполняться параллельно. Прогресс смотри через besales_getcourse_cold_welcome_batch_status по batchId; о ЗАВЕРШЕНИИ пакета платформа доложит сама в чат уведомлений воркспейса, поэтому опрашивать в цикле не нужно.',
328
- 'contacts[].segment_entered_at — точный ISO-момент (с зоной) входа человека в когорту; на нём считается свежесть в «Аналитике рассылок». Передавай его посегментно, когда предсписок собран ПРАВИЛОМ и у каждой строки есть свой момент события. ⚠️ Дату запуска подставлять НЕЛЬЗЯ: разброс возраста бывает от часа до 294 дней, и общий момент схлопнул бы месяцы когорт в одну.',
329
- 'Весь пакет проверяется ДО первой отправки: сегмент без текста ИЛИ текст с переменными, которые не подставляются его контактам, отменяет пакет целиком, пока никому ничего не ушло. В ошибке названы сами переменные, контакт проверки и агент — чинить надо значение переменной или перезапись у этого агента.',
330
- '⚠️ Проверка переменных на плане ВЫБОРОЧНАЯ (несколько контактов сегмента) и не гарантирует, что подставится всем: агента выбирает роутер по контакту. Полноту держит гейт на самой отправке — контакт с сырыми скобками пропускается с outcome SKIPPED_UNRESOLVED_VARS.',
331
- '⚠️ Переменные, которые отправка дочитывает из сделки (календарные поля замапленной площадки), план НЕ проверяет — судить по до-синковому снимку значило бы блокировать каждый пакет календарной площадки. Все остальные проверяются. Если пакет упал на календарной переменной — это баг, а не настройка.',
321
+ 'Пакет: несколько сегментов одним вызовом, у каждого свой список людей, свои метки и свои поля сделки.',
322
+ 'cohort_label задаётся ПОСЕГМЕНТНО: каждый сегмент — отдельный запуск со своей задачей и когортой.',
323
+ 'Сегменты идут ПО ОЧЕРЕДИ. Прогресс смотри через besales_getcourse_cold_welcome_batch_status по batchId; о ЗАВЕРШЕНИИ пакета платформа доложит сама в чат уведомлений пространства, поэтому опрашивать в цикле не нужно.',
324
+ 'contacts[].segment_entered_at — точный ISO-момент (с зоной) входа человека в когорту; передавай его посегментно, когда предсписок собран ПРАВИЛОМ. ⚠️ Дату запуска подставлять НЕЛЬЗЯ.',
325
+ 'Весь пакет проверяется ДО первой отправки: сегмент без текста ИЛИ текст с переменными, которые не подставляются его людям, отменяет пакет целиком, пока никому ничего не ушло. В ошибке названы сегмент и сами переменные: чинить надо значение переменной.',
326
+ '⚠️ Проверка переменных на плане ВЫБОРОЧНАЯ (несколько человек сегмента) и не гарантирует, что подставится всем. Полноту держит гейт на самой отправке — человек с сырыми скобками пропускается с outcome SKIPPED_UNRESOLVED_VARS. Имена, которые отправка дочитает из закреплённой сделки, план у человека с закреплённой сделкой не проверяет.',
332
327
  'Отказ одного сегмента НЕ останавливает остальные — смотри разбивку в статусе.',
333
- 'ПРЕДОХРАНИТЕЛИ. Перед отправкой каждый контакт проверяется по фактам школы: идёт ли у него вебинарная серия, есть ли действующая заявка (работает менеджер), оплачен ли этот продукт. Исходы: SKIPPED_ACTIVE_SERIES, SKIPPED_ACTIVE_DEAL, SKIPPED_GUARD_UNAVAILABLE.',
334
- '⚠️ Правило серии видит только тех, кто ДЕЙСТВИТЕЛЬНО смотрел эфиры: серии школы строятся из фактов посещения. Зарегистрировавшийся и не пришедший под это правило не попадает — это не пробел проверки, а граница данных.',
335
- '⚠️ Правило оплаты сверяет продукт запуска с оплаченными. Если у пакета не задан product_code, а у контакта есть оплата, отправка пропускается с SKIPPED_GUARD_UNAVAILABLE («сверять не с чем») — это чинится повторным запуском с продуктом, а не считается работой менеджера.',
336
- '⚠️ Блокировка по серии — это ОТСРОЧКА, а не потеря: серия кончится, и контакт станет доступен следующему запуску (замер: из 55 контактов сегодня проходят 10, завтра 39, через трое суток 47). Не пытайся обойти гейт, чтобы «добрать объём».',
337
- '⚠️ Поле guards ослабляет проверку на ОДИН пакет и предназначено для аварийного обхода. Постоянное решение принимается в настройках площадки (besales_platform_settings_update) — там его видно всей команде, а не только автору вызова.',
338
- '⚠️⚠️ policyForecast — прогноз ПРАВИЛ ОТПРАВКИ ПОСЕГМЕНТНО: сколько людей блока не пропустят «этим сегментом уже слали», «уже получал первое сообщение», «идёт приветствие по регистрации», «эфир ещё не прошёл», тихий период и «тот же текст». Показывай пользователю сумму по блокам сразу после запуска: у пакета отдельного предпросмотра нет, и это единственные числа правил, которые он увидит.',
328
+ GUARD_INSTRUCTION,
329
+ GUARD_SERIES_IS_DEFERRAL,
330
+ GUARD_PAID_NEEDS_PRODUCT,
331
+ '⚠️ Поле guards ослабляет проверку на ОДИН пакет и предназначено для аварийного обхода; оно сохраняется в план и переживает возобновление. Постоянное решение принимается в настройках канала (besales_platform_settings_update, getcourse_settings.guard_*) — там его видно всей команде.',
332
+ '⚠️⚠️ policyForecast — прогноз ПРАВИЛ ОТПРАВКИ ПОСЕГМЕНТНО (segment — сегмент блока): сколько людей блока не пропустят «этим сегментом уже слали», «уже получал первое сообщение», «идёт приветствие по регистрации», тихий период. Показывай пользователю сумму по блокам сразу после запуска.',
339
333
  '⚠️ policyForecast — best-effort: ПУСТОЙ массив значит «прогноз не посчитался» (отказ чтения), а НЕ «никого не заблокировали». Пакет при этом идёт. Нужны надёжные числа до запуска — сделай besales_getcourse_cold_welcome_preview на каждый блок с его segment.',
334
+ '⚠️ 409 «в пространстве уже идёт пакет „Первого сообщения“» — не сбой: дождись его (besales_getcourse_cold_welcome_batch_status) или отмени. Людям при таком отказе ничего не ушло.',
340
335
  ]),
341
336
  }),
342
337
  defineTool({
@@ -344,20 +339,19 @@ export const getcourseColdWelcomeTools = [
344
339
  sideEffect: 'none',
345
340
  requiredFields: ['batch_id'],
346
341
  defaultWorkspaceId: true,
347
- schema: z
348
- .object({ workspace_id: uuidSchema.optional(), batch_id: uuidSchema })
349
- .strict(),
342
+ schema: z.object({ workspace_id: uuidSchema.optional(), batch_id: uuidSchema }).strict(),
350
343
  createRequest: (input) => ({
351
344
  method: 'GET',
352
345
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/batch/${input.batch_id}`,
353
346
  }),
354
347
  enhanceResponse: (response) => withInstructions(response, [
355
- 'state — агрегат по пакету, segments[] — разбивка: у каждого свой статус, свой importJobId и своя причина отказа.',
356
- 'completedAt — момент, когда закрылись ВСЕ сегменты; null означает «пакет ещё идёт». Это и есть ответ на вопрос «закончилось ли»: лезть в import_jobs и chat_users не нужно.',
357
- 'summary — план, ДОКАЗАННЫЕ доставки (строки реестра рассылок) и слитая разбивка исходов; у каждого сегмента те же delivered/outcomes. SKIPPED_UNRESOLVED_VARS означает «переменные текста без значения»: такие контакты не приветствованы и доберутся повторным запуском.',
348
+ 'state — агрегат по пакету, segments[] — разбивка: у каждого свой статус, свой importJobId и своя причина отказа (error — словами).',
349
+ 'completedAt — момент, когда закрылись ВСЕ сегменты; null означает «пакет ещё идёт». Это и есть ответ на вопрос «закончилось ли».',
350
+ 'summary — план, ДОКАЗАННЫЕ доставки и слитая разбивка исходов; у каждого сегмента те же delivered/outcomes; sentWithoutChannelEvidence — отправлено «вслепую» (без доказательства, что человек есть в этом канале). SKIPPED_UNRESOLVED_VARS означает «переменные текста без значения»: такие люди не приветствованы и доберутся повторным запуском. SKIPPED_RESEND_PAUSE бывает только у пакета расписания.',
358
351
  '⚠️ summary.segmentsWithoutOutcomes > 0 — у стольких сегментов прогон оборвался, не записав итог: их исходы в разбивку НЕ вошли, и судить о них можно только по delivered. outcomes=null у сегмента значит ровно это, а НЕ «все нули».',
359
- '⚠️ Опрашивать в цикле не нужно: о завершении пакета и о его обрыве платформа сама докладывает в чат уведомлений воркспейса (там же batchId, доставлено/из скольких и разбивка исходов). Оборванный пакет крон-подметатель находит сам в пределах ~15 минут и освобождает задачу импорта, чтобы воркспейс не оставался заблокированным.',
360
- 'stalled=true означает, что драйвер оборвался (рестарт инстанса). НЕ запускай пакет заново — зови besales_getcourse_cold_welcome_batch_resume по тому же batch_id: источник текста хранится в плане, пересобирать списки не нужно, а уже отправленным контактам повтор не уйдёт (приветствие одноразово НА КОНТАКТ).',
352
+ '⚠️ Опрашивать в цикле не нужно: о завершении пакета и о его обрыве платформа сама докладывает в чат уведомлений пространства. Оборванный пакет подметатель находит сам в пределах ~15 минут и освобождает задачу импорта.',
353
+ 'stalled=true означает, что исполнитель пакета оборвался (перезапуск сервиса). НЕ запускай пакет заново — зови besales_getcourse_cold_welcome_batch_resume по тому же batch_id: план хранится на сервере, пересобирать списки не нужно, а уже отправленным людям повтор не уйдёт.',
354
+ '⚠️ Исключение — пакет, который собрало РАСПИСАНИЕ (origin = SCHEDULE): resume ответит 409. Не повторяй вызов: не начатые сегменты такого пакета подметатель гасит сам, а их люди уйдут следующим запуском расписания.',
361
355
  ]),
362
356
  }),
363
357
  defineTool({
@@ -365,15 +359,15 @@ export const getcourseColdWelcomeTools = [
365
359
  sideEffect: 'spend',
366
360
  requiredFields: ['batch_id'],
367
361
  defaultWorkspaceId: true,
368
- schema: z
369
- .object({ workspace_id: uuidSchema.optional(), batch_id: uuidSchema })
370
- .strict(),
362
+ schema: z.object({ workspace_id: uuidSchema.optional(), batch_id: uuidSchema }).strict(),
371
363
  createRequest: (input) => ({
372
364
  method: 'POST',
373
365
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/batch/${input.batch_id}/resume`,
374
366
  }),
375
367
  enhanceResponse: (response) => withInstructions(response, [
376
- 'Поднимает драйвер для НЕ НАЧАТЫХ сегментов пакета. Подтверждения не требует: повтора отправки не будет — приветствие одноразово НА КОНТАКТ, а уже начатые сегменты не перезапускаются.',
368
+ 'Поднимает исполнителя для НЕ НАЧАТЫХ сегментов оборвавшегося пакета. Подтверждения не требует: повтора отправки не будет — приветствие одноразово НА ЧЕЛОВЕКА, а оборвавшийся идущий сегмент не переигрывается.',
369
+ '⚠️ Пакет, собранный РАСПИСАНИЕМ, вручную не возобновляется — 409. Это не сбой: владелец его остатка — прогон расписания. Не повторяй вызов и не запускай пакет заново руками.',
370
+ '⚠️ 409 «пакет не оборвался» — возобновлять нечего: пакет идёт сам.',
377
371
  ]),
378
372
  }),
379
373
  defineTool({
@@ -393,7 +387,7 @@ export const getcourseColdWelcomeTools = [
393
387
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/batch/${input.batch_id}/cancel`,
394
388
  }),
395
389
  enhanceResponse: (response) => withInstructions(response, [
396
- 'Отменяет ОСТАТОК пакета: не начатые сегменты и идущую задачу. Уже отправленное не отзывается.',
390
+ 'Отменяет ОСТАТОК пакета: не начатые сегменты и идущий сегмент. Уже отправленное не отзывается.',
397
391
  ]),
398
392
  }),
399
393
  defineTool({
@@ -412,61 +406,7 @@ export const getcourseColdWelcomeTools = [
412
406
  path: `/workspaces/${input.workspace_id}/getcourse-cold-welcome/${input.job_id}/cancel`,
413
407
  }),
414
408
  enhanceResponse: (response) => withInstructions(response, [
415
- 'Останавливает ОСТАТОК когорты в пределах ~10 секунд; уже отправленное не отзывается.',
416
- ]),
417
- }),
418
- defineTool({
419
- name: 'besales_getcourse_contacts_import',
420
- sideEffect: 'write',
421
- verifyWithToolName: 'besales_getcourse_contact_import_status',
422
- requiredFields: ['platform_id', 'contacts'],
423
- defaultWorkspaceId: true,
424
- schema: z
425
- .object({
426
- workspace_id: uuidSchema.optional(),
427
- platform_id: uuidSchema,
428
- contacts: z.array(importContactSchema).min(1).max(MAX_CONTACTS),
429
- check_messenger_presence: z.boolean().optional(),
430
- })
431
- .strict(),
432
- createRequest: (input) => ({
433
- method: 'POST',
434
- path: `/workspaces/${input.workspace_id}/chat-users/import/getcourse-contacts`,
435
- body: compactObject({
436
- platformId: input.platform_id,
437
- contacts: input.contacts,
438
- checkMessengerPresence: input.check_messenger_presence,
439
- }),
440
- }),
441
- enhanceResponse: (response) => withInstructions(response, [
442
- 'НИЧЕГО НЕ ОТПРАВЛЯЕТ: заводит или обновляет контакты в базе платформы, чтобы по ним можно было запускать «Нашу отправку». Матчинг существующих — по contact_id (getcourse_user_id).',
443
- 'Импорт нужен ровно тогда, когда preview показал notInBase > 0: отправке нужен ChatUser, иначе контакт получает исход SKIPPED_NOT_IN_BASE и остаётся без сообщения.',
444
- 'Идёт фоном — вернувшийся jobId опрашивай через besales_getcourse_contact_import_status каждые 10–30 секунд до COMPLETED, PARTIAL или FAILED; отвечает сразу, а не по завершении.',
445
- '⚠️ Гейт «один активный импорт на воркспейс» общий с волнами и пакетами: 409 означает «идёт другой импорт», а не сбой — дождись его и повтори.',
446
- '⚠️ first_name прямо влияет на текст: обращение подставляется системным {greeting}, и у контакта без имени обращения не будет вовсе. Передавай имя, если оно есть в источнике.',
447
- 'check_messenger_presence по умолчанию включён и отсеивает контакты без Telegram/MAX/VK. Это API-вызов на КАЖДЫЙ контакт, поэтому на тысячах строк импорт идёт заметно дольше.',
448
- 'Повторный импорт того же списка безопасен: существующий контакт обновляется, второй не создаётся.',
449
- ]),
450
- }),
451
- defineTool({
452
- name: 'besales_getcourse_contact_import_status',
453
- sideEffect: 'none',
454
- requiredFields: ['job_id'],
455
- defaultWorkspaceId: true,
456
- schema: z
457
- .object({
458
- workspace_id: uuidSchema.optional(),
459
- job_id: uuidSchema,
460
- })
461
- .strict(),
462
- createRequest: (input) => ({
463
- method: 'GET',
464
- path: `/workspaces/${input.workspace_id}/getcourse-contact-imports/${input.job_id}`,
465
- }),
466
- enhanceResponse: (response) => withInstructions(response, [
467
- 'Опрашивай каждые 10–30 секунд до terminal state: COMPLETED, PARTIAL или FAILED.',
468
- 'PARTIAL означает, что часть контактов записана, а часть завершилась ошибками; сверяй successCount, failedCount и errors перед следующим шагом.',
469
- 'Отмены нет: импорт пишет контакты по мере обработки. Повтор того же списка безопасен — существующие карточки обновятся без дублей.',
409
+ 'Останавливает ОСТАТОК аудитории в пределах ~10 секунд; уже отправленное не отзывается. 409 — запуск уже завершён, останавливать нечего.',
470
410
  ]),
471
411
  }),
472
412
  ];