@besales/mcp 0.48.0 → 1.0.0-beta.2

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