@besales/mcp 0.48.0 → 1.0.0-beta.4

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