@besales/mcp 0.47.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (587) hide show
  1. package/README.md +109 -164
  2. package/README.onprem.ru.md +35 -0
  3. package/bin/besales-mcp-http.js +9 -0
  4. package/bin/besales-onprem.mjs +19 -0
  5. package/dist/auth/connection-store.d.ts +3 -1
  6. package/dist/auth/connection-store.js +9 -3
  7. package/dist/auth/connection-store.js.map +1 -1
  8. package/dist/auth/oauth-client.d.ts +20 -27
  9. package/dist/auth/oauth-client.js +31 -23
  10. package/dist/auth/oauth-client.js.map +1 -1
  11. package/dist/auth/session-workspace.d.ts +1 -0
  12. package/dist/auth/session-workspace.js +10 -1
  13. package/dist/auth/session-workspace.js.map +1 -1
  14. package/dist/auth/token-storage.js +4 -1
  15. package/dist/auth/token-storage.js.map +1 -1
  16. package/dist/cli.d.ts +1 -0
  17. package/dist/cli.js +80 -19
  18. package/dist/cli.js.map +1 -1
  19. package/dist/constants/crm-catalog.d.ts +2 -0
  20. package/dist/constants/crm-catalog.js +3 -0
  21. package/dist/constants/crm-catalog.js.map +1 -0
  22. package/dist/http/api-client.d.ts +52 -7
  23. package/dist/http/api-client.js +131 -66
  24. package/dist/http/api-client.js.map +1 -1
  25. package/dist/http/feedback-document-error.d.ts +26 -0
  26. package/dist/http/feedback-document-error.js +22 -0
  27. package/dist/http/feedback-document-error.js.map +1 -0
  28. package/dist/http-server.d.ts +15 -0
  29. package/dist/http-server.js +378 -0
  30. package/dist/http-server.js.map +1 -0
  31. package/dist/instructions/server-instructions.d.ts +1 -1
  32. package/dist/instructions/server-instructions.js +6 -3
  33. package/dist/instructions/server-instructions.js.map +1 -1
  34. package/dist/network/constants.d.ts +25 -0
  35. package/dist/network/constants.js +26 -0
  36. package/dist/network/constants.js.map +1 -0
  37. package/dist/network/failed-verifications.d.ts +10 -0
  38. package/dist/network/failed-verifications.js +37 -0
  39. package/dist/network/failed-verifications.js.map +1 -0
  40. package/dist/network/http-test-fixture.d.ts +3 -0
  41. package/dist/network/http-test-fixture.js +86 -0
  42. package/dist/network/http-test-fixture.js.map +1 -0
  43. package/dist/network/known-keys.d.ts +10 -0
  44. package/dist/network/known-keys.js +38 -0
  45. package/dist/network/known-keys.js.map +1 -0
  46. package/dist/network/memory-credentials.d.ts +11 -0
  47. package/dist/network/memory-credentials.js +24 -0
  48. package/dist/network/memory-credentials.js.map +1 -0
  49. package/dist/network/rate-limit.d.ts +23 -0
  50. package/dist/network/rate-limit.js +62 -0
  51. package/dist/network/rate-limit.js.map +1 -0
  52. package/dist/network/rejected-keys.d.ts +9 -0
  53. package/dist/network/rejected-keys.js +39 -0
  54. package/dist/network/rejected-keys.js.map +1 -0
  55. package/dist/network/request-work.d.ts +8 -0
  56. package/dist/network/request-work.js +20 -0
  57. package/dist/network/request-work.js.map +1 -0
  58. package/dist/network/successful-keys.d.ts +17 -0
  59. package/dist/network/successful-keys.js +47 -0
  60. package/dist/network/successful-keys.js.map +1 -0
  61. package/dist/resources/concepts/external-execution.md +3 -1
  62. package/dist/resources/concepts/feedback-sheets.md +21 -12
  63. package/dist/resources/concepts/icp.md +8 -1
  64. package/dist/resources/concepts/sandbox.md +15 -6
  65. package/dist/resources/concepts/triggers.md +8 -0
  66. package/dist/resources/concepts/workbook-classification.md +104 -50
  67. package/dist/resources/docs/agent-behavior.md +51 -382
  68. package/dist/resources/docs/campaigns.md +9 -16
  69. package/dist/resources/docs/crm-integration.md +39 -523
  70. package/dist/resources/docs/files-and-uploads.md +23 -286
  71. package/dist/resources/docs/follow-up.md +47 -12
  72. package/dist/resources/docs/knowledge-base.md +52 -521
  73. package/dist/resources/docs/pipeline-builder.md +26 -216
  74. package/dist/resources/docs/pipeline-settings-deep.md +27 -220
  75. package/dist/resources/docs/platforms.md +106 -519
  76. package/dist/resources/docs/prompt-anatomy.md +39 -285
  77. package/dist/resources/docs/routing.md +8 -16
  78. package/dist/resources/docs/trigger-actions.md +40 -24
  79. package/dist/resources/docs/variables.md +8 -12
  80. package/dist/resources/operator-edit.contract.d.ts +7 -0
  81. package/dist/resources/operator-edit.contract.js +8 -0
  82. package/dist/resources/operator-edit.contract.js.map +1 -0
  83. package/dist/resources/registry.js +20 -4
  84. package/dist/resources/registry.js.map +1 -1
  85. package/dist/resources/workflows/audit-dialogues.md +23 -12
  86. package/dist/resources/workflows/compare-models.md +6 -4
  87. package/dist/resources/workflows/connect-crm-from-scratch.md +15 -12
  88. package/dist/resources/workflows/connect-datasource-from-scratch.md +5 -4
  89. package/dist/resources/workflows/extract-from-document.md +8 -6
  90. package/dist/resources/workflows/operator-edit.md +15 -0
  91. package/dist/resources/workflows/platform-setup-from-scratch.md +59 -70
  92. package/dist/resources/workflows/production-readiness-check.md +25 -15
  93. package/dist/schemas/mcp-tools.json +12061 -6753
  94. package/dist/schemas/voice-actions-api.contract.json +248 -0
  95. package/dist/shared/constants/broadcast-reason.constants.d.ts +2 -0
  96. package/dist/shared/constants/broadcast-reason.constants.js +47 -0
  97. package/dist/shared/constants/broadcast-reason.constants.js.map +1 -0
  98. package/dist/shared/constants/crm-stage-pool.constants.d.ts +2 -0
  99. package/dist/shared/constants/crm-stage-pool.constants.js +3 -0
  100. package/dist/shared/constants/crm-stage-pool.constants.js.map +1 -0
  101. package/dist/shared/constants/followup-note.constants.d.ts +3 -0
  102. package/dist/shared/constants/followup-note.constants.js +4 -0
  103. package/dist/shared/constants/followup-note.constants.js.map +1 -0
  104. package/dist/shared/constants/followup-session.constants.d.ts +2 -0
  105. package/dist/shared/constants/followup-session.constants.js +3 -0
  106. package/dist/shared/constants/followup-session.constants.js.map +1 -0
  107. package/dist/shared/constants/funnel-analytics.constants.d.ts +2 -0
  108. package/dist/shared/constants/funnel-analytics.constants.js +3 -0
  109. package/dist/shared/constants/funnel-analytics.constants.js.map +1 -0
  110. package/dist/shared/constants/message-filter.constants.d.ts +7 -0
  111. package/dist/shared/constants/message-filter.constants.js +8 -0
  112. package/dist/shared/constants/message-filter.constants.js.map +1 -0
  113. package/dist/shared/utils/trigger-repeat-mapping.d.ts +72 -0
  114. package/dist/shared/utils/trigger-repeat-mapping.js +53 -0
  115. package/dist/shared/utils/trigger-repeat-mapping.js.map +1 -0
  116. package/dist/skills/besales-dialogue-audit/SKILL.md +31 -20
  117. package/dist/tasks/task-state-store.js +5 -0
  118. package/dist/tasks/task-state-store.js.map +1 -1
  119. package/dist/tools/annotations.js +4 -3
  120. package/dist/tools/annotations.js.map +1 -1
  121. package/dist/tools/definitions/agent-design.d.ts +78 -21
  122. package/dist/tools/definitions/agent-design.js +246 -90
  123. package/dist/tools/definitions/agent-design.js.map +1 -1
  124. package/dist/tools/definitions/agent-model-notice.d.ts +5 -0
  125. package/dist/tools/definitions/agent-model-notice.js +31 -0
  126. package/dist/tools/definitions/agent-model-notice.js.map +1 -0
  127. package/dist/tools/definitions/agent-models.d.ts +5 -0
  128. package/dist/tools/definitions/agent-models.js +26 -0
  129. package/dist/tools/definitions/agent-models.js.map +1 -0
  130. package/dist/tools/definitions/agent-settings.d.ts +85 -0
  131. package/dist/tools/definitions/agent-settings.js +187 -0
  132. package/dist/tools/definitions/agent-settings.js.map +1 -0
  133. package/dist/tools/definitions/ai-costs.d.ts +12 -0
  134. package/dist/tools/definitions/ai-costs.js +142 -0
  135. package/dist/tools/definitions/ai-costs.js.map +1 -0
  136. package/dist/tools/definitions/api-tool-request.d.ts +15 -0
  137. package/dist/tools/definitions/api-tool-request.js +2 -0
  138. package/dist/tools/definitions/api-tool-request.js.map +1 -0
  139. package/dist/tools/definitions/behavior-shipped-form.d.ts +182 -0
  140. package/dist/tools/definitions/behavior-shipped-form.js +185 -0
  141. package/dist/tools/definitions/behavior-shipped-form.js.map +1 -0
  142. package/dist/tools/definitions/broadcast-alerts.d.ts +6 -0
  143. package/dist/tools/definitions/broadcast-alerts.js +72 -0
  144. package/dist/tools/definitions/broadcast-alerts.js.map +1 -0
  145. package/dist/tools/definitions/broadcast-analytics.d.ts +102 -18
  146. package/dist/tools/definitions/broadcast-analytics.js +732 -427
  147. package/dist/tools/definitions/broadcast-analytics.js.map +1 -1
  148. package/dist/tools/definitions/broadcast-scheduler.d.ts +19 -13
  149. package/dist/tools/definitions/broadcast-scheduler.js +217 -128
  150. package/dist/tools/definitions/broadcast-scheduler.js.map +1 -1
  151. package/dist/tools/definitions/call-operator-task.schema.d.ts +11 -0
  152. package/dist/tools/definitions/call-operator-task.schema.js +30 -0
  153. package/dist/tools/definitions/call-operator-task.schema.js.map +1 -0
  154. package/dist/tools/definitions/contacts.d.ts +3 -0
  155. package/dist/tools/definitions/contacts.js +57 -31
  156. package/dist/tools/definitions/contacts.js.map +1 -1
  157. package/dist/tools/definitions/crm-platform.d.ts +28 -39
  158. package/dist/tools/definitions/crm-platform.js +235 -344
  159. package/dist/tools/definitions/crm-platform.js.map +1 -1
  160. package/dist/tools/definitions/crm-stage-pool.messages.d.ts +39 -0
  161. package/dist/tools/definitions/crm-stage-pool.messages.js +43 -0
  162. package/dist/tools/definitions/crm-stage-pool.messages.js.map +1 -0
  163. package/dist/tools/definitions/crm-stage-pool.schema.d.ts +64 -0
  164. package/dist/tools/definitions/crm-stage-pool.schema.js +109 -0
  165. package/dist/tools/definitions/crm-stage-pool.schema.js.map +1 -0
  166. package/dist/tools/definitions/custom-reports.d.ts +12 -0
  167. package/dist/tools/definitions/custom-reports.js +68 -0
  168. package/dist/tools/definitions/custom-reports.js.map +1 -0
  169. package/dist/tools/definitions/datasource.d.ts +2 -1
  170. package/dist/tools/definitions/datasource.js +28 -13
  171. package/dist/tools/definitions/datasource.js.map +1 -1
  172. package/dist/tools/definitions/dialog-review-findings-response.d.ts +14 -0
  173. package/dist/tools/definitions/dialog-review-findings-response.js +23 -0
  174. package/dist/tools/definitions/dialog-review-findings-response.js.map +1 -0
  175. package/dist/tools/definitions/dialog-review-safe-response.d.ts +5 -0
  176. package/dist/tools/definitions/dialog-review-safe-response.js +152 -0
  177. package/dist/tools/definitions/dialog-review-safe-response.js.map +1 -0
  178. package/dist/tools/definitions/dialog-review.d.ts +49 -0
  179. package/dist/tools/definitions/dialog-review.js +383 -0
  180. package/dist/tools/definitions/dialog-review.js.map +1 -0
  181. package/dist/tools/definitions/dialogue-analysis.d.ts +46 -0
  182. package/dist/tools/definitions/dialogue-analysis.js +170 -0
  183. package/dist/tools/definitions/dialogue-analysis.js.map +1 -0
  184. package/dist/tools/definitions/dialogue-audit-agent-scope.d.ts +7 -0
  185. package/dist/tools/definitions/dialogue-audit-agent-scope.js +41 -0
  186. package/dist/tools/definitions/dialogue-audit-agent-scope.js.map +1 -0
  187. package/dist/tools/definitions/dialogue-audit-alias.d.ts +67 -0
  188. package/dist/tools/definitions/dialogue-audit-alias.js +559 -0
  189. package/dist/tools/definitions/dialogue-audit-alias.js.map +1 -0
  190. package/dist/tools/definitions/dialogue-export.d.ts +7 -1
  191. package/dist/tools/definitions/dialogue-export.js +89 -32
  192. package/dist/tools/definitions/dialogue-export.js.map +1 -1
  193. package/dist/tools/definitions/dialogue-followup-state.d.ts +9 -0
  194. package/dist/tools/definitions/dialogue-followup-state.js +69 -0
  195. package/dist/tools/definitions/dialogue-followup-state.js.map +1 -0
  196. package/dist/tools/definitions/edit-version-transport.test-fixtures.d.ts +13 -0
  197. package/dist/tools/definitions/edit-version-transport.test-fixtures.js +292 -0
  198. package/dist/tools/definitions/edit-version-transport.test-fixtures.js.map +1 -0
  199. package/dist/tools/definitions/edit-version.d.ts +5 -0
  200. package/dist/tools/definitions/edit-version.js +20 -0
  201. package/dist/tools/definitions/edit-version.js.map +1 -0
  202. package/dist/tools/definitions/external-api-channels.js +17 -15
  203. package/dist/tools/definitions/external-api-channels.js.map +1 -1
  204. package/dist/tools/definitions/feedback-document-access.d.ts +16 -0
  205. package/dist/tools/definitions/feedback-document-access.js +63 -0
  206. package/dist/tools/definitions/feedback-document-access.js.map +1 -0
  207. package/dist/tools/definitions/fixtures/shipped-setup-status.d.ts +3 -0
  208. package/dist/tools/definitions/fixtures/shipped-setup-status.js +9 -0
  209. package/dist/tools/definitions/fixtures/shipped-setup-status.js.map +1 -0
  210. package/dist/tools/definitions/follow-up-config.d.ts +79 -0
  211. package/dist/tools/definitions/follow-up-config.js +258 -0
  212. package/dist/tools/definitions/follow-up-config.js.map +1 -0
  213. package/dist/tools/definitions/follow-up-funnel-test.d.ts +4 -3
  214. package/dist/tools/definitions/follow-up-funnel-test.js +65 -52
  215. package/dist/tools/definitions/follow-up-funnel-test.js.map +1 -1
  216. package/dist/tools/definitions/follow-up-promotion-instructions.d.ts +2 -0
  217. package/dist/tools/definitions/follow-up-promotion-instructions.js +7 -0
  218. package/dist/tools/definitions/follow-up-promotion-instructions.js.map +1 -0
  219. package/dist/tools/definitions/follow-up-setup.d.ts +4 -28
  220. package/dist/tools/definitions/follow-up-setup.js +20 -87
  221. package/dist/tools/definitions/follow-up-setup.js.map +1 -1
  222. package/dist/tools/definitions/follow-up.d.ts +20 -122
  223. package/dist/tools/definitions/follow-up.js +200 -354
  224. package/dist/tools/definitions/follow-up.js.map +1 -1
  225. package/dist/tools/definitions/followup-getcourse-compat.d.ts +29 -0
  226. package/dist/tools/definitions/followup-getcourse-compat.js +62 -0
  227. package/dist/tools/definitions/followup-getcourse-compat.js.map +1 -0
  228. package/dist/tools/definitions/followup-sequence-config.schema.d.ts +10 -0
  229. package/dist/tools/definitions/followup-sequence-config.schema.js +14 -0
  230. package/dist/tools/definitions/followup-sequence-config.schema.js.map +1 -0
  231. package/dist/tools/definitions/funnel-analytics.d.ts +18 -0
  232. package/dist/tools/definitions/funnel-analytics.js +150 -0
  233. package/dist/tools/definitions/funnel-analytics.js.map +1 -0
  234. package/dist/tools/definitions/getcourse-anchor-refresh.d.ts +5 -9
  235. package/dist/tools/definitions/getcourse-anchor-refresh.js +26 -31
  236. package/dist/tools/definitions/getcourse-anchor-refresh.js.map +1 -1
  237. package/dist/tools/definitions/getcourse-cold-outbound-settings.d.ts +46 -0
  238. package/dist/tools/definitions/getcourse-cold-outbound-settings.js +37 -0
  239. package/dist/tools/definitions/getcourse-cold-outbound-settings.js.map +1 -0
  240. package/dist/tools/definitions/getcourse-cold-welcome.d.ts +17 -29
  241. package/dist/tools/definitions/getcourse-cold-welcome.js +182 -242
  242. package/dist/tools/definitions/getcourse-cold-welcome.js.map +1 -1
  243. package/dist/tools/definitions/getcourse-wave-welcome.messages.d.ts +10 -0
  244. package/dist/tools/definitions/getcourse-wave-welcome.messages.js +17 -0
  245. package/dist/tools/definitions/getcourse-wave-welcome.messages.js.map +1 -0
  246. package/dist/tools/definitions/help.js.map +1 -1
  247. package/dist/tools/definitions/icp-import-source.d.ts +75 -0
  248. package/dist/tools/definitions/icp-import-source.js +88 -0
  249. package/dist/tools/definitions/icp-import-source.js.map +1 -0
  250. package/dist/tools/definitions/inbound-analytics.d.ts +13 -0
  251. package/dist/tools/definitions/inbound-analytics.js +55 -0
  252. package/dist/tools/definitions/inbound-analytics.js.map +1 -0
  253. package/dist/tools/definitions/instagram-comment.templates.d.ts +19 -0
  254. package/dist/tools/definitions/instagram-comment.templates.js +34 -0
  255. package/dist/tools/definitions/instagram-comment.templates.js.map +1 -0
  256. package/dist/tools/definitions/instagram-settings-compat.d.ts +33 -0
  257. package/dist/tools/definitions/instagram-settings-compat.js +7 -0
  258. package/dist/tools/definitions/instagram-settings-compat.js.map +1 -0
  259. package/dist/tools/definitions/knowledge-chunking.d.ts +42 -0
  260. package/dist/tools/definitions/knowledge-chunking.js +83 -0
  261. package/dist/tools/definitions/knowledge-chunking.js.map +1 -0
  262. package/dist/tools/definitions/knowledge-space-search-fields.d.ts +12 -0
  263. package/dist/tools/definitions/knowledge-space-search-fields.js +30 -0
  264. package/dist/tools/definitions/knowledge-space-search-fields.js.map +1 -0
  265. package/dist/tools/definitions/knowledge.d.ts +24 -14
  266. package/dist/tools/definitions/knowledge.js +342 -233
  267. package/dist/tools/definitions/knowledge.js.map +1 -1
  268. package/dist/tools/definitions/legacy-trigger-repeat.d.ts +17 -0
  269. package/dist/tools/definitions/legacy-trigger-repeat.js +53 -0
  270. package/dist/tools/definitions/legacy-trigger-repeat.js.map +1 -0
  271. package/dist/tools/definitions/link-tracking.d.ts +8 -0
  272. package/dist/tools/definitions/link-tracking.js +141 -38
  273. package/dist/tools/definitions/link-tracking.js.map +1 -1
  274. package/dist/tools/definitions/mailing-variant-content.d.ts +75 -0
  275. package/dist/tools/definitions/mailing-variant-content.js +73 -0
  276. package/dist/tools/definitions/mailing-variant-content.js.map +1 -0
  277. package/dist/tools/definitions/model-comparison.d.ts +1 -0
  278. package/dist/tools/definitions/model-comparison.js +4 -5
  279. package/dist/tools/definitions/model-comparison.js.map +1 -1
  280. package/dist/tools/definitions/money-currency.d.ts +5 -0
  281. package/dist/tools/definitions/money-currency.js +10 -0
  282. package/dist/tools/definitions/money-currency.js.map +1 -0
  283. package/dist/tools/definitions/operator-workflow.d.ts +5 -5
  284. package/dist/tools/definitions/operator-workflow.js +80 -27
  285. package/dist/tools/definitions/operator-workflow.js.map +1 -1
  286. package/dist/tools/definitions/platform-funnel-analytics.d.ts +42 -0
  287. package/dist/tools/definitions/platform-funnel-analytics.js +72 -0
  288. package/dist/tools/definitions/platform-funnel-analytics.js.map +1 -0
  289. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.d.ts +42 -0
  290. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.js +51 -0
  291. package/dist/tools/definitions/platform-getcourse-wave-field-bindings.js.map +1 -0
  292. package/dist/tools/definitions/platform-settings-limits.d.ts +5 -0
  293. package/dist/tools/definitions/platform-settings-limits.js +6 -0
  294. package/dist/tools/definitions/platform-settings-limits.js.map +1 -0
  295. package/dist/tools/definitions/platform-settings-shipped-form.d.ts +37 -0
  296. package/dist/tools/definitions/platform-settings-shipped-form.js +310 -0
  297. package/dist/tools/definitions/platform-settings-shipped-form.js.map +1 -0
  298. package/dist/tools/definitions/platform-settings-update.d.ts +300 -0
  299. package/dist/tools/definitions/platform-settings-update.js +134 -0
  300. package/dist/tools/definitions/platform-settings-update.js.map +1 -0
  301. package/dist/tools/definitions/{getcourse-wave.d.ts → platform-setup-wave.d.ts} +75 -6
  302. package/dist/tools/definitions/platform-setup-wave.js +1079 -0
  303. package/dist/tools/definitions/platform-setup-wave.js.map +1 -0
  304. package/dist/tools/definitions/platform-setup.d.ts +701 -222
  305. package/dist/tools/definitions/platform-setup.js +313 -752
  306. package/dist/tools/definitions/platform-setup.js.map +1 -1
  307. package/dist/tools/definitions/platform-widget-page-rules.schema.d.ts +10 -0
  308. package/dist/tools/definitions/platform-widget-page-rules.schema.js +7 -0
  309. package/dist/tools/definitions/platform-widget-page-rules.schema.js.map +1 -0
  310. package/dist/tools/definitions/prompt-authoring.constants.d.ts +2 -0
  311. package/dist/tools/definitions/prompt-authoring.constants.js +3 -0
  312. package/dist/tools/definitions/prompt-authoring.constants.js.map +1 -0
  313. package/dist/tools/definitions/prompt-batch-controls.d.ts +5 -0
  314. package/dist/tools/definitions/prompt-batch-controls.js +40 -0
  315. package/dist/tools/definitions/prompt-batch-controls.js.map +1 -0
  316. package/dist/tools/definitions/prompt-draft-authoring.d.ts +5 -0
  317. package/dist/tools/definitions/prompt-draft-authoring.js +24 -0
  318. package/dist/tools/definitions/prompt-draft-authoring.js.map +1 -0
  319. package/dist/tools/definitions/prompt-text-projection.d.ts +11 -0
  320. package/dist/tools/definitions/prompt-text-projection.js +62 -0
  321. package/dist/tools/definitions/prompt-text-projection.js.map +1 -0
  322. package/dist/tools/definitions/reviewer-paid.d.ts +19 -0
  323. package/dist/tools/definitions/reviewer-paid.js +115 -0
  324. package/dist/tools/definitions/reviewer-paid.js.map +1 -0
  325. package/dist/tools/definitions/routing-condition-projection.d.ts +31 -0
  326. package/dist/tools/definitions/routing-condition-projection.js +97 -0
  327. package/dist/tools/definitions/routing-condition-projection.js.map +1 -0
  328. package/dist/tools/definitions/schedule-selection-hints.d.ts +3 -0
  329. package/dist/tools/definitions/schedule-selection-hints.js +42 -0
  330. package/dist/tools/definitions/schedule-selection-hints.js.map +1 -0
  331. package/dist/tools/definitions/{scheduled-tasks.d.ts → scheduled-task-alias.d.ts} +12 -10
  332. package/dist/tools/definitions/scheduled-task-alias.js +207 -0
  333. package/dist/tools/definitions/scheduled-task-alias.js.map +1 -0
  334. package/dist/tools/definitions/session.js +3 -3
  335. package/dist/tools/definitions/session.js.map +1 -1
  336. package/dist/tools/definitions/setup-init-operation.schema.d.ts +2 -0
  337. package/dist/tools/definitions/setup-init-operation.schema.js +6 -0
  338. package/dist/tools/definitions/setup-init-operation.schema.js.map +1 -0
  339. package/dist/tools/definitions/shared-input.helpers.d.ts +51 -0
  340. package/dist/tools/definitions/shared-input.helpers.js +323 -0
  341. package/dist/tools/definitions/shared-input.helpers.js.map +1 -0
  342. package/dist/tools/definitions/shared.d.ts +414 -284
  343. package/dist/tools/definitions/shared.js +436 -619
  344. package/dist/tools/definitions/shared.js.map +1 -1
  345. package/dist/tools/definitions/shipped-output-names.d.ts +19 -0
  346. package/dist/tools/definitions/shipped-output-names.js +123 -0
  347. package/dist/tools/definitions/shipped-output-names.js.map +1 -0
  348. package/dist/tools/definitions/task-state.d.ts +5 -5
  349. package/dist/tools/definitions/task-state.js +2 -1
  350. package/dist/tools/definitions/task-state.js.map +1 -1
  351. package/dist/tools/definitions/telegram-personal-capacity.d.ts +4 -0
  352. package/dist/tools/definitions/telegram-personal-capacity.js +18 -0
  353. package/dist/tools/definitions/telegram-personal-capacity.js.map +1 -0
  354. package/dist/tools/definitions/telegram-pool-policy.d.ts +8 -0
  355. package/dist/tools/definitions/telegram-pool-policy.js +56 -0
  356. package/dist/tools/definitions/telegram-pool-policy.js.map +1 -0
  357. package/dist/tools/definitions/touch-history-alias.d.ts +4 -0
  358. package/dist/tools/definitions/touch-history-alias.js +99 -0
  359. package/dist/tools/definitions/touch-history-alias.js.map +1 -0
  360. package/dist/tools/definitions/trigger-action-item.schema.d.ts +254 -0
  361. package/dist/tools/definitions/trigger-action-item.schema.js +121 -0
  362. package/dist/tools/definitions/trigger-action-item.schema.js.map +1 -0
  363. package/dist/tools/definitions/trigger-argument.schema.d.ts +14 -0
  364. package/dist/tools/definitions/trigger-argument.schema.js +23 -0
  365. package/dist/tools/definitions/trigger-argument.schema.js.map +1 -0
  366. package/dist/tools/definitions/trigger-claim-alias.d.ts +10 -0
  367. package/dist/tools/definitions/trigger-claim-alias.js +90 -0
  368. package/dist/tools/definitions/trigger-claim-alias.js.map +1 -0
  369. package/dist/tools/definitions/trigger-echo-projection.d.ts +1 -0
  370. package/dist/tools/definitions/trigger-echo-projection.js +41 -0
  371. package/dist/tools/definitions/trigger-echo-projection.js.map +1 -0
  372. package/dist/tools/definitions/trigger-upsert.tool.d.ts +29 -0
  373. package/dist/tools/definitions/trigger-upsert.tool.js +122 -0
  374. package/dist/tools/definitions/trigger-upsert.tool.js.map +1 -0
  375. package/dist/tools/definitions/v15-port-descriptions.d.ts +11 -0
  376. package/dist/tools/definitions/v15-port-descriptions.js +30 -0
  377. package/dist/tools/definitions/v15-port-descriptions.js.map +1 -0
  378. package/dist/tools/definitions/variables.d.ts +44 -0
  379. package/dist/tools/definitions/variables.js +310 -0
  380. package/dist/tools/definitions/variables.js.map +1 -0
  381. package/dist/tools/definitions/voice-action-input.d.ts +9 -0
  382. package/dist/tools/definitions/voice-action-input.js +141 -0
  383. package/dist/tools/definitions/voice-action-input.js.map +1 -0
  384. package/dist/tools/definitions/widget-customization-compat.d.ts +51 -0
  385. package/dist/tools/definitions/widget-customization-compat.js +23 -0
  386. package/dist/tools/definitions/widget-customization-compat.js.map +1 -0
  387. package/dist/tools/definitions.d.ts +1105 -621
  388. package/dist/tools/definitions.js +57 -27
  389. package/dist/tools/definitions.js.map +1 -1
  390. package/dist/tools/description-localization.js +3 -2
  391. package/dist/tools/description-localization.js.map +1 -1
  392. package/dist/tools/follow-up-request.d.ts +19 -0
  393. package/dist/tools/follow-up-request.js +81 -0
  394. package/dist/tools/follow-up-request.js.map +1 -0
  395. package/dist/tools/followup-revision.d.ts +4 -0
  396. package/dist/tools/followup-revision.js +29 -0
  397. package/dist/tools/followup-revision.js.map +1 -0
  398. package/dist/tools/incomplete-list.js +4 -0
  399. package/dist/tools/incomplete-list.js.map +1 -1
  400. package/dist/tools/inventory.js +47 -11
  401. package/dist/tools/inventory.js.map +1 -1
  402. package/dist/tools/operation-recovery.d.ts +4 -0
  403. package/dist/tools/operation-recovery.js +33 -0
  404. package/dist/tools/operation-recovery.js.map +1 -0
  405. package/dist/tools/registry-mapping-primary.cases.d.ts +2 -0
  406. package/dist/tools/registry-mapping-primary.cases.js +753 -0
  407. package/dist/tools/registry-mapping-primary.cases.js.map +1 -0
  408. package/dist/tools/registry-mapping-secondary.cases.d.ts +2 -0
  409. package/dist/tools/registry-mapping-secondary.cases.js +626 -0
  410. package/dist/tools/registry-mapping-secondary.cases.js.map +1 -0
  411. package/dist/tools/registry-mapping.cases.d.ts +8 -0
  412. package/dist/tools/registry-mapping.cases.js +13 -0
  413. package/dist/tools/registry-mapping.cases.js.map +1 -0
  414. package/dist/tools/registry-test-fixtures.d.ts +50 -0
  415. package/dist/tools/registry-test-fixtures.js +120 -0
  416. package/dist/tools/registry-test-fixtures.js.map +1 -0
  417. package/dist/tools/registry.d.ts +2 -0
  418. package/dist/tools/registry.js +82 -52
  419. package/dist/tools/registry.js.map +1 -1
  420. package/dist/tools/result.d.ts +1 -0
  421. package/dist/tools/result.js +90 -22
  422. package/dist/tools/result.js.map +1 -1
  423. package/dist/tools/toolsets.d.ts +16 -0
  424. package/dist/tools/toolsets.js +25 -1
  425. package/dist/tools/toolsets.js.map +1 -1
  426. package/dist/types/api-contract.gen.d.ts +105862 -22115
  427. package/dist/utils/credential-signatures.d.ts +3 -0
  428. package/dist/utils/credential-signatures.js +10 -0
  429. package/dist/utils/credential-signatures.js.map +1 -0
  430. package/dist/utils/data-source-column-name.d.ts +10 -0
  431. package/dist/utils/data-source-column-name.js +36 -0
  432. package/dist/utils/data-source-column-name.js.map +1 -0
  433. package/dist/utils/logger.d.ts +1 -0
  434. package/dist/utils/logger.js +32 -8
  435. package/dist/utils/logger.js.map +1 -1
  436. package/dist/utils/secret-read-boundary.d.ts +2 -0
  437. package/dist/utils/secret-read-boundary.js +88 -0
  438. package/dist/utils/secret-read-boundary.js.map +1 -0
  439. package/dist/utils/setup-secrets.d.ts +4 -0
  440. package/dist/utils/setup-secrets.js +40 -0
  441. package/dist/utils/setup-secrets.js.map +1 -0
  442. package/docs/agent-models.md +9 -0
  443. package/docs/agent-usage-guide.full.ru.md +1 -4
  444. package/docs/analytics-contract.md +67 -0
  445. package/docs/async-operation-lifecycle.md +10 -3
  446. package/docs/connect.ru.md +144 -4
  447. package/docs/contact-company-profile.md +17 -0
  448. package/docs/contact-recommendations.md +7 -0
  449. package/docs/crm-stage-pool.md +73 -0
  450. package/docs/customer-data.md +61 -0
  451. package/docs/edit-versions.md +36 -0
  452. package/docs/failure-semantics.md +17 -9
  453. package/docs/help-navigation.md +15 -42
  454. package/docs/host-compatibility-matrix.md +72 -93
  455. package/docs/host-setup.md +33 -35
  456. package/docs/invariants/public-api-and-contracts.md +16 -0
  457. package/docs/legacy-action-settings.md +21 -0
  458. package/docs/main-transfer-dependencies.md +38 -0
  459. package/docs/mcp2-parity.md +50 -0
  460. package/docs/modules/analytics.md +79 -0
  461. package/docs/modules/besales-mcp.md +137 -0
  462. package/docs/modules/dialog-review-report.md +38 -0
  463. package/docs/modules/dialogue-followup-adapters.md +39 -0
  464. package/docs/modules/message-reactions.md +17 -0
  465. package/docs/modules/network-mode.md +284 -0
  466. package/docs/modules/prompt-batch-and-dialogue-controls.md +69 -0
  467. package/docs/pii-policy.md +21 -16
  468. package/docs/response-size-budget.md +197 -7
  469. package/docs/secret-read-boundary.md +102 -0
  470. package/docs/task-state.md +7 -2
  471. package/docs/telegram-pool-policy.md +19 -0
  472. package/docs/tool-effects.md +15 -9
  473. package/docs/tool-surface.md +205 -4
  474. package/docs/voice-actions-and-inbox.md +95 -0
  475. package/docs/wazzup-instagram-comments.md +26 -0
  476. package/package.json +17 -10
  477. package/scripts/api-source-loader.spec.mjs +40 -0
  478. package/scripts/audit-bridge-controls.mjs +129 -0
  479. package/scripts/audit-revert-mutants.mjs +107 -0
  480. package/scripts/bridge-api-input.spec.mjs +98 -0
  481. package/scripts/bridge-controls-mutants.mjs +130 -0
  482. package/scripts/brx3-shipped-refusals.spec.mjs +149 -0
  483. package/scripts/brx4-ledger-payload.spec.mjs +127 -0
  484. package/scripts/build-onprem.mjs +108 -0
  485. package/scripts/connect-clients.sh +141 -0
  486. package/scripts/connect-scopes.mjs +166 -0
  487. package/scripts/connect-scopes.spec.mjs +83 -0
  488. package/scripts/emit-personal-link-carrier.mjs +31 -0
  489. package/scripts/emit-setup-init-schemas.mjs +24 -0
  490. package/scripts/emit-tool-routes.mjs +878 -0
  491. package/scripts/emit-tool-routes.spec.mjs +300 -0
  492. package/scripts/followup-registry.spec.mjs +107 -0
  493. package/scripts/generate-crm-rotation-contract.mjs +98 -0
  494. package/scripts/generate-followup-session-contract.mjs +16 -0
  495. package/scripts/icp-import-verification/mutate.cjs +70 -0
  496. package/scripts/import-secret-read-contract.mjs +53 -0
  497. package/scripts/install-claude-desktop.js +1 -1
  498. package/scripts/lag-decisions.mjs +37 -0
  499. package/scripts/lag-decisions.spec.mjs +87 -0
  500. package/scripts/lag-open-task-owners.mjs +46 -0
  501. package/scripts/lag-open-task-owners.spec.mjs +78 -0
  502. package/scripts/lag-payload.mjs +10 -0
  503. package/scripts/legacy-action-lag.spec.mjs +22 -0
  504. package/scripts/legacy-field-refusal.shipped.spec.mjs +99 -0
  505. package/scripts/mcpcompat-handler.mjs +34 -0
  506. package/scripts/mcpcompat-loader.mjs +36 -0
  507. package/scripts/mcpcompat-typescript.mjs +3 -0
  508. package/scripts/platform-settings-shipped-parity.spec.mjs +89 -0
  509. package/scripts/prbr-round1-recheck.spec.mjs +605 -0
  510. package/scripts/prbr-shipped-compatibility.spec.mjs +276 -0
  511. package/scripts/publish-mcp-registry.mjs +98 -0
  512. package/scripts/publish-mcp-registry.spec.mjs +68 -0
  513. package/scripts/record-scheduled-task-call.mjs +26 -0
  514. package/scripts/refusal-probe.mjs +17 -0
  515. package/scripts/refusal-probe.spec.mjs +29 -0
  516. package/scripts/registry-diff.mjs +502 -0
  517. package/scripts/registry-diff.spec.mjs +787 -0
  518. package/scripts/request-query.mjs +43 -0
  519. package/scripts/request-query.spec.mjs +82 -0
  520. package/scripts/route-lag.mjs +23 -0
  521. package/scripts/route-lag.spec.mjs +30 -0
  522. package/scripts/sample-args.mjs +174 -0
  523. package/scripts/sample-args.spec.mjs +160 -0
  524. package/scripts/shipped-definitions.mjs +197 -0
  525. package/scripts/shipped-definitions.spec.mjs +57 -0
  526. package/scripts/shipped-inputs.spec.mjs +136 -0
  527. package/scripts/shipped-recordings.spec.mjs +19 -0
  528. package/scripts/shipped-ref.d.mts +5 -0
  529. package/scripts/shipped-ref.mjs +20 -0
  530. package/scripts/shipped-ref.spec.mjs +59 -0
  531. package/scripts/shipped-registry.mjs +107 -0
  532. package/scripts/shipped-sample-candidates.mjs +69 -0
  533. package/scripts/shipped-sample-candidates.spec.mjs +117 -0
  534. package/scripts/shipped-sample.mjs +868 -0
  535. package/scripts/shipped-sample.spec.mjs +451 -0
  536. package/scripts/sync-agent-model-schemas.mjs +72 -0
  537. package/scripts/sync-api-contracts.mjs +85 -0
  538. package/scripts/sync-api-contracts.spec.mjs +144 -0
  539. package/scripts/sync-contract-ledger-schemas.mjs +161 -0
  540. package/scripts/sync-crm-stage-pool-schema.mjs +85 -0
  541. package/scripts/sync-prompt-draft-schema.mjs +92 -0
  542. package/scripts/sync-route-map.mjs +94 -0
  543. package/scripts/sync-schema-descriptions.mjs +25 -0
  544. package/scripts/sync-tool-inventory.mjs +27 -0
  545. package/scripts/sync-trigger-argument-schema.mjs +121 -0
  546. package/scripts/sync-trigger-repeat-mapping.mjs +12 -0
  547. package/scripts/sync-voice-actions-contract.mjs +67 -0
  548. package/scripts/sync-voice-actions-tools.mjs +97 -0
  549. package/scripts/sync-wave-id-schemas.mjs +55 -0
  550. package/scripts/tool-input-schemas.mjs +46 -0
  551. package/scripts/tool-input-schemas.spec.mjs +50 -0
  552. package/scripts/verify-followup-adapters.mjs +235 -0
  553. package/scripts/verify-secret-read-import.mjs +84 -0
  554. package/scripts/verify-secret-read-mutants.mjs +143 -0
  555. package/dist/tools/definitions/analytics.d.ts +0 -40
  556. package/dist/tools/definitions/analytics.js +0 -233
  557. package/dist/tools/definitions/analytics.js.map +0 -1
  558. package/dist/tools/definitions/broadcast-mailing-variants.d.ts +0 -41
  559. package/dist/tools/definitions/broadcast-mailing-variants.js +0 -194
  560. package/dist/tools/definitions/broadcast-mailing-variants.js.map +0 -1
  561. package/dist/tools/definitions/dialogue-audit.d.ts +0 -38
  562. package/dist/tools/definitions/dialogue-audit.js +0 -292
  563. package/dist/tools/definitions/dialogue-audit.js.map +0 -1
  564. package/dist/tools/definitions/getcourse-wave.js +0 -519
  565. package/dist/tools/definitions/getcourse-wave.js.map +0 -1
  566. package/dist/tools/definitions/operational-overview.d.ts +0 -3
  567. package/dist/tools/definitions/operational-overview.js +0 -23
  568. package/dist/tools/definitions/operational-overview.js.map +0 -1
  569. package/dist/tools/definitions/scheduled-tasks.js +0 -173
  570. package/dist/tools/definitions/scheduled-tasks.js.map +0 -1
  571. package/dist/tools/definitions/trigger-contact-claims.d.ts +0 -7
  572. package/dist/tools/definitions/trigger-contact-claims.js +0 -60
  573. package/dist/tools/definitions/trigger-contact-claims.js.map +0 -1
  574. package/dist/tools/registry-mapping-cases-core.d.ts +0 -38
  575. package/dist/tools/registry-mapping-cases-core.js +0 -927
  576. package/dist/tools/registry-mapping-cases-core.js.map +0 -1
  577. package/dist/tools/registry-mapping-cases-platform.d.ts +0 -2
  578. package/dist/tools/registry-mapping-cases-platform.js +0 -1052
  579. package/dist/tools/registry-mapping-cases-platform.js.map +0 -1
  580. package/dist/tools/trigger-action-config.d.ts +0 -49
  581. package/dist/tools/trigger-action-config.js +0 -171
  582. package/dist/tools/trigger-action-config.js.map +0 -1
  583. package/docs/evals/model-behavior-scorecard.md +0 -80
  584. package/docs/operation-coverage.md +0 -114
  585. package/scripts/mock-credentials.js +0 -77
  586. package/scripts/model-eval-server.js +0 -213
  587. package/scripts/run-model-eval.js +0 -447
@@ -1,29 +1,41 @@
1
+ import { SCHEDULE_GUARD_HINT, SCHEDULE_BLOCKS_HINT } from './v15-port-descriptions.js';
2
+ // Планировщик рассылок: расписания (список, сохранение, удаление, история прогонов) и правило отбора
3
+ // сегмента. Схемы инструментов — ровно отгруженные: клиент, зовущий инструмент сегодня, не получает
4
+ // отказа ни на одном поле, которое принимал. Тело запроса — в именах сервера v2 (канал, а не
5
+ // «площадка»). «Запустить сейчас» и настройка источника фактов мосту не выставлены намеренно, как и у
6
+ // отгруженного: необратимая массовая отправка и адрес источника остаются за человеком.
1
7
  import { z } from 'zod';
8
+ import { SCHEDULE_EXCLUSION_HINT, SCHEDULE_POOL_GROUPS_HINT, SCHEDULE_CLOSE_CLASS_HINT } from './schedule-selection-hints.js';
2
9
  import { buildListQuery, compactObject, defineTool, getcourseTransportEnum, uuidSchema, withInstructions, } from './shared.js';
3
- /** Границы — паритет с DTO бэкенда: расхождение даёт 400 уже после подтверждения оператором. */
10
+ /** Границы расписания — паритет с DTO бэкенда: расхождение даёт 400 уже после подтверждения человеком. */
4
11
  const DAILY_CAP_MAX = 5000;
5
12
  /**
6
- * ⚠️⚠️ 78, а не 365. Бэкенд выводит потолок из потолка источника фактов
7
- * (92 дня окна минус 14 дней добавки на созревание серии), и значение 79…365 проходило zod,
8
- * а падало 400 уже ПОСЛЕ того, как оператор подтвердил расписание.
13
+ * ⚠️⚠️ 78, а не 365. Сервер выводит потолок из потолка окна источника фактов (92 дня) минус запас
14
+ * окна регистраций (14 дней): значение 79…365 проходило бы схему и падало 400 уже ПОСЛЕ того, как
15
+ * человек подтвердил расписание.
9
16
  */
10
17
  const FRESHNESS_DAYS_MAX = 78;
11
18
  const COOLDOWN_DAYS_MAX = 365;
19
+ /** Потолок паузы повтора сегмента — серверный `SCHEDULE_RESEND_PAUSE_DAYS_MAX`. */
20
+ const RESEND_PAUSE_DAYS_MAX = 365;
21
+ /** Потолок списка «не писать после сегментов» — серверный `SCHEDULE_SKIP_AFTER_SEGMENTS_MAX`. */
22
+ const SKIP_AFTER_SEGMENTS_MAX = 20;
23
+ /** Потолок списка каналов — отгруженный. */
24
+ const CHANNELS_MAX = 20;
12
25
  /**
13
26
  * Окно свежести строк без расписания в «Пуле сегодня» — паритет с `POOL_TODAY_WINDOW_DAYS_MAX`
14
27
  * бэкенда: окно регистраций строки (окно + 14 суток запаса) обязано уложиться в общую выкачку
15
28
  * (30 суток). Больше сервер отвергнет 400.
16
29
  */
17
30
  const POOL_TODAY_WINDOW_DAYS_MAX = 16;
18
- const WAIT_HOURS_MAX = 720;
19
- /** Локальная дата площадки — та же форма, что у бэкенда. */
31
+ /** Локальная дата канала — та же форма, что у бэкенда. */
20
32
  const LOCAL_DATE_RE = /^\d{4}-\d{2}-\d{2}$/u;
21
33
  /**
22
34
  * Списки фильтров — ОДНА форма у расписания и у «Пула сегодня»: `'all'` либо список кодов.
23
35
  *
24
- * ⚠️ Границы повторяют DTO бэкенда (`POOL_TODAY_FUNNELS_MAX`, `POOL_TODAY_PRODUCTS_MAX`,
25
- * `POOL_TODAY_FILTER_VALUE_MAX_LENGTH`): разойдясь, схема пропускала бы то, что сервер отвергает
26
- * 400-м, и модель узнавала бы о границе из ошибки.
36
+ * ⚠️ Границы повторяют DTO бэкенда (`SCHEDULE_FUNNELS_MAX`, `SCHEDULE_PRODUCT_CODES_MAX`,
37
+ * `SCHEDULE_FILTER_VALUE_MAX_LENGTH`): разойдясь, схема пропускала бы то, что сервер отвергает 400-м,
38
+ * и модель узнавала бы о границе из ошибки.
27
39
  */
28
40
  const FILTER_VALUE_MAX_LENGTH = 64;
29
41
  const FUNNELS_MAX = 100;
@@ -33,25 +45,42 @@ const filterListSchema = (max) => z.union([
33
45
  z.array(z.string().trim().min(1).max(FILTER_VALUE_MAX_LENGTH)).max(max),
34
46
  ]);
35
47
  const orderSchema = z.enum(['FRESH_FIRST', 'OLD_FIRST']);
48
+ /** Потолок ожидания после эфира — серверный `SEGMENT_WAIT_AFTER_SERIES_END_HOURS_MAX`. */
49
+ const WAIT_HOURS_MAX = 720;
50
+ /** Границы приоритета — серверные `SEGMENT_RULE_PRIORITY_MIN/MAX`. */
51
+ const PRIORITY_MIN = -1000;
52
+ const PRIORITY_MAX = 1000;
53
+ /**
54
+ * Суток с прошлого касания: снизу — граница владельца (повтор снимает единственную вечную защиту
55
+ * от второго сообщения), сверху — окно свежести расписания. Паритет серверных констант: ниже 30
56
+ * сервер откажет уже после подтверждения человеком.
57
+ */
58
+ const REPEAT_TOUCH_MIN_DAYS = 30;
59
+ const REPEAT_TOUCH_MAX_DAYS = 78;
60
+ /** Потолок длины имени прошлого касания — отгруженный; сервер принимает и длиннее. */
61
+ const PREVIOUS_SEGMENT_KEY_MAX_LENGTH = 64;
62
+ const triStateSchema = z.enum(['YES', 'NO', 'ANY']);
63
+ /**
64
+ * Откуда берётся момент входа человека в сегмент.
65
+ *
66
+ * ⚠️ Схема СТРОГАЯ: значение, объявленное на сервере и забытое здесь, отвергается ещё до запроса.
67
+ */
36
68
  const anchorSchema = z.enum([
37
69
  'SERIES_END',
38
70
  'SERIES_END_PLUS_WAIT',
71
+ // Конец ПЕРВОГО эфира серии плюс ожидание — для сегментов, которые пишут пропустившему первый
72
+ // эфир, пока серия идёт. Требует признака attendedFirstSession.
73
+ 'FIRST_SESSION_END_PLUS_WAIT',
39
74
  'LAST_APPLICATION',
40
75
  'REGISTRATION',
41
- // ⚠️ Схема СТРОГАЯ: значение, объявленное на сервере и забытое здесь, отвергается ещё до
42
- // запроса — фича остаётся недостижимой через MCP, и выглядит это как ошибка ввода агента.
43
76
  'PRELIST',
44
77
  ]);
45
- /**
46
- * Тело расписания. `funnels`/`product_codes` принимают литерал `all` — обычный пользователь
47
- * говорит «шли по всем воронкам», и заставлять его перечислять их руками незачем.
48
- */
49
78
  /**
50
79
  * Пара «дни недели + числа месяца» запрещена.
51
80
  *
52
- * ⚠️ Правило продублировано в схеме намеренно: бэкенд и CHECK базы её тоже не пускают, но модель
53
- * узнавала бы об этом из 400-й — а описание инструмента для того и существует, чтобы правило было
54
- * известно ДО вызова.
81
+ * ⚠️ Правило продублировано в схеме намеренно: сервер и проверка таблицы её тоже не пускают, но
82
+ * модель узнавала бы об этом из 400-й — а описание инструмента для того и существует, чтобы правило
83
+ * было известно ДО вызова.
55
84
  */
56
85
  const assertSingleRepeatAxis = (input, ctx) => {
57
86
  if ((input.days_of_week?.length ?? 0) > 0 && (input.days_of_month?.length ?? 0) > 0) {
@@ -63,6 +92,11 @@ const assertSingleRepeatAxis = (input, ctx) => {
63
92
  });
64
93
  }
65
94
  };
95
+ /**
96
+ * Тело расписания. `funnels`/`product_codes` принимают литерал `all` — человек говорит «шли по всем
97
+ * воронкам», и заставлять его перечислять их руками незачем. Имена полей входа — отгруженные
98
+ * (`platform_id`, `handoff_from_school_platforms`): клиент зовёт инструмент ими уже сегодня.
99
+ */
66
100
  const scheduleShape = {
67
101
  workspace_id: uuidSchema.optional(),
68
102
  segment_id: uuidSchema,
@@ -73,34 +107,56 @@ const scheduleShape = {
73
107
  product_codes: filterListSchema(PRODUCTS_MAX).optional(),
74
108
  freshness_days: z.number().int().min(1).max(FRESHNESS_DAYS_MAX),
75
109
  window_mode: z.enum(['ROLLING', 'FIXED']).optional(),
76
- // ⚠️ ЛОКАЛЬНАЯ дата площадки, а не момент: момент сдвинул бы границу на смещение зоны.
110
+ // ⚠️ ЛОКАЛЬНАЯ дата канала, а не момент: момент сдвинул бы границу на смещение пояса.
77
111
  window_from_local: z.string().regex(LOCAL_DATE_RE, 'window_from_local: дата ГГГГ-ММ-ДД').optional(),
78
112
  // Конец ВКЛЮЧИТЕЛЬНО: «по 20 августа» берёт весь двадцатый день.
79
113
  window_to_local: z.string().regex(LOCAL_DATE_RE, 'window_to_local: дата ГГГГ-ММ-ДД').optional(),
80
114
  time_local: z
81
115
  .string()
82
- .regex(/^([01]\d|2[0-3]):([0-5]\d)$/, 'time_local: формат HH:mm в таймзоне площадки'),
116
+ .regex(/^([01]\d|2[0-3]):([0-5]\d)$/, 'time_local: формат HH:mm в часовом поясе канала'),
83
117
  days_of_week: z.array(z.number().int().min(1).max(7)).max(7).optional(),
84
- // ⚠️ Взаимоисключимо с `days_of_week`: вместе они означают «31-е И понедельник». Пару отвергает
85
- // и бэкенд 400-й, и CHECK базы — но узнавать об этом из ошибки сервера модель не должна, поэтому
86
- // запрет продублирован схемой (см. `superRefine` ниже). Число больше длины месяца попадает на
87
- // ПОСЛЕДНИЙ его день.
118
+ // ⚠️ Взаимоисключимо с `days_of_week` (см. `assertSingleRepeatAxis`). Число больше длины месяца
119
+ // попадает на ПОСЛЕДНИЙ его день.
88
120
  days_of_month: z.array(z.number().int().min(1).max(31)).max(31).optional(),
89
121
  daily_cap: z.number().int().min(1).max(DAILY_CAP_MAX),
90
122
  selection_order: orderSchema,
91
123
  cooldown_days: z.number().int().min(0).max(COOLDOWN_DAYS_MAX),
92
- handoff_from_school_platforms: z.boolean().optional().describe('ПЕРЕДАЧА: забирать людей с ДРУГИХ площадок GetCourse той же школы. Включённый флаг меняет смысл отбора: берутся ТОЛЬКО люди, чья карточка живёт на других площадках школы (и те, кого прошлая попытка уже перевезла, но сообщение не ушло), а перед отправкой карточка переносится сюда — с перепином на заказ сегмента и остановкой цепочек прежней площадки. Площадки-источники считает сам отбор («вся школа минус своя»), поэтому новая площадка школы подхватывается без правки расписания. Требует сегмента-ПРОДОЛЖЕНИЯ (снят «только нетронутым», без правила повтора): у человека с соседней площадки право первого сообщения уже сожжено. Ещё требует заполненного домена школы у площадки расписания и хотя бы одной другой площадки GetCourse в школе. Выключено — передачи нет.'),
124
+ handoff_from_school_platforms: z
125
+ .boolean()
126
+ .optional()
127
+ .describe('ПЕРЕДАЧА: забирать людей с ДРУГИХ каналов GetCourse той же школы В ДОПОЛНЕНИЕ к своим. Требует сегмента-ПРОДОЛЖЕНИЯ (снят «только нетронутым», без правила повтора), заполненного домена школы у канала расписания и хотя бы одного другого канала GetCourse в школе. Не передано — у существующего остаётся как было.'),
128
+ resend_segment_on_new_registration: z
129
+ .boolean()
130
+ .optional()
131
+ .describe('ПОВТОР СЕГМЕНТА ПО НОВОЙ РЕГИСТРАЦИИ. Включено — человеку, которому этим сегментом уже писали, расписание напишет снова, только если он зарегистрировался ПОСЛЕ нашей последней отправки этого сегмента и с неё прошла пауза resend_segment_pause_days. Действует у продолжения без previousSegment; продукт регистрации повтор не ограничивает. У «только нетронутым» сохраняется право первого сообщения. Не передано — у нового расписания включено, у существующего остаётся как было.'),
132
+ resend_segment_pause_days: z
133
+ .number()
134
+ .int()
135
+ .min(0)
136
+ .max(RESEND_PAUSE_DAYS_MAX)
137
+ .optional()
138
+ .describe('Пауза повтора сегмента: сутки по 24 часа от нашей последней отправки этого сегмента человеку. Не передано — у нового расписания 3, у существующего остаётся как было.'),
93
139
  channels: z
94
- // ⚠️ Канон транспортов, а не свободная строка: иначе про допустимые значения модель узнаёт
95
- // из 400-й ошибки. Ровно этот дефект уже закрыт parity-тестом контракта для соседних тулов.
140
+ // ⚠️ Канон транспортов, а не свободная строка: иначе про допустимые значения модель узнаёт из
141
+ // 400-й ошибки.
96
142
  .array(getcourseTransportEnum)
97
- .max(20)
143
+ .max(CHANNELS_MAX)
98
144
  .optional()
99
- .describe('Каналы «Первого сообщения» (GetCourseTransport). Пусто или ключ не передан — дефолт рассылки. Канал вне списка площадки отвергается: школа не сужает набор, а отвергает его ЦЕЛИКОМ.'),
145
+ .describe('Каналы «Первого сообщения» (GetCourseTransport), первый — канал денег. [] — каналы рассылки; не передано — у существующего остаётся как было. Канал вне способов отправки канала GetCourse отвергается: школа не сужает набор, а отвергает его ЦЕЛИКОМ.'),
100
146
  channels_mode: z
101
147
  .enum(['RESTRICT', 'ADD'])
102
148
  .optional()
103
- .describe(`⚠️ УСТАРЕЛ и на поведение НЕ влияет (с 23.08.2026). Список channels значит «слать сюда», судьбу КАЖДОГО канала решает сервер: мессенджер уходит, только если школа подтвердила его наличие; SMS — только при известном телефоне; Email, WhatsApp, Viber, Сайт, Chatium — вслепую. Поле принимается ради обратной совместимости и игнорируется.`),
149
+ .describe('⚠️ УСТАРЕЛ и на поведение НЕ влияет. Список channels значит «слать сюда», судьбу КАЖДОГО канала решает сервер. Поле принимается ради обратной совместимости и игнорируется.'),
150
+ guard_active_series: z
151
+ .enum(['OFF', 'WARN', 'BLOCK'])
152
+ .nullable()
153
+ .optional()
154
+ .describe('Перекрытие предохранителя «идёт вебинарная серия» ДЛЯ ЭТОГО расписания. Не передано — у существующего остаётся как было; null — вернуть под настройку канала. Перекрывается только правило серии: заявка и оплата остаются настройкой канала.'),
155
+ skip_after_segment_ids: z
156
+ .array(uuidSchema)
157
+ .max(SKIP_AFTER_SEGMENTS_MAX)
158
+ .optional()
159
+ .describe('НЕ ПИСАТЬ ПОСЛЕ СЕГМЕНТОВ: идентификаторы сегментов справочника (segment.id из besales_broadcast_schedule_list). Не передано — у существующего остаётся как было; [] — снять правило. Запрет действует, если текст указанного сегмента ушёл не раньше регистрации, по которой человек попал в пул; регистрация новее касания открывает человека снова, неизвестная регистрация запрет не снимает. Сам сегмент расписания, чужие идентификаторы и сегменты со служебным именем сервер отвергает.'),
104
160
  is_enabled: z.boolean().optional(),
105
161
  };
106
162
  /** `all` — это «фильтра нет», то есть ПУСТОЙ список на стороне API. */
@@ -109,10 +165,18 @@ const toList = (value) => {
109
165
  return undefined;
110
166
  return value === 'all' ? [] : value;
111
167
  };
168
+ /**
169
+ * Вход инструмента — в тело сервера v2.
170
+ *
171
+ * ⚠️ `compactObject` режет только `undefined`: поле, которого нет во входе, не уходит вовсе и у
172
+ * существующего расписания остаётся как было, а `null` у `guard_active_series` доезжает и снимает
173
+ * перекрытие — «не передал» обязано отличаться от «верни под канал».
174
+ */
112
175
  const toScheduleBody = (input) => compactObject({
113
176
  segmentId: input.segment_id,
114
177
  mailingVariantId: input.mailing_variant_id,
115
- platformId: input.platform_id,
178
+ // Имя v2 — канал; отгруженное имя поля входа сохранено ради клиента.
179
+ channelId: input.platform_id,
116
180
  mechanism: input.mechanism,
117
181
  funnels: toList(input.funnels),
118
182
  productCodes: toList(input.product_codes),
@@ -126,20 +190,20 @@ const toScheduleBody = (input) => compactObject({
126
190
  dailyCap: input.daily_cap,
127
191
  selectionOrder: input.selection_order,
128
192
  cooldownDays: input.cooldown_days,
129
- handoffFromSchoolPlatforms: input.handoff_from_school_platforms,
130
- channels: input.channels,
131
- // ⚠️ Режим передаётся ТОЛЬКО если его задал вызывающий: сам он на поведение не влияет, но
132
- // подставлять сюда `RESTRICT` значит записать в строку расписания решение, которого никто
133
- // не принимал, — а прочитано оно будет как действующая настройка.
134
- channelsMode: input.channels_mode,
193
+ handoffFromSchoolChannels: input.handoff_from_school_platforms,
194
+ resendSegmentOnNewRegistration: input.resend_segment_on_new_registration,
195
+ resendSegmentPauseDays: input.resend_segment_pause_days,
196
+ transports: input.channels,
197
+ // ⚠️ Режим передаётся ТОЛЬКО если его задал вызывающий: сервер его принимает и игнорирует, а
198
+ // подставленный мостом он выглядел бы решением, которого никто не принимал.
199
+ transportsMode: input.channels_mode,
200
+ guardActiveSeries: input.guard_active_series,
201
+ skipAfterSegmentIds: input.skip_after_segment_ids,
135
202
  isEnabled: input.is_enabled,
136
203
  });
137
204
  /**
138
- * Планировщик рассылок: сегмент-правило × расписание, отбор идёт кроном.
139
- *
140
- * ⚠️⚠️ Расписание отправляет ЖИВЫМ ЛЮДЯМ без участия человека в момент отправки. Поэтому
141
- * создание всегда даёт выключенное расписание, а включение — отдельное действие после
142
- * предпросмотра.
205
+ * ⚠️⚠️ Расписание отправляет ЖИВЫМ ЛЮДЯМ без участия человека в момент отправки. Поэтому создание
206
+ * по умолчанию даёт выключенное расписание, а включение — отдельное действие после предпросмотра.
143
207
  */
144
208
  export const broadcastSchedulerTools = [
145
209
  defineTool({
@@ -153,8 +217,9 @@ export const broadcastSchedulerTools = [
153
217
  path: `/workspaces/${input.workspace_id}/broadcast-schedules`,
154
218
  }),
155
219
  enhanceResponse: (response) => withInstructions(response, [
156
- 'isEnabled=false означает, что расписание заведено, но крон его не берёт. Это нормальное состояние сразу после создания.',
220
+ 'isEnabled=false означает, что расписание заведено, но проход планировщика его не берёт. Это нормальное состояние сразу после создания.',
157
221
  'Смотри на segment.priority: при пересечении сегментов побеждает больший — это и решает, какой текст получит человек, попавший под два правила.',
222
+ 'Расписаний, убранных в архив (удалённых при существующей истории прогонов), в списке нет; их история читается besales_broadcast_schedule_runs по идентификатору.',
158
223
  ]),
159
224
  }),
160
225
  defineTool({
@@ -173,34 +238,31 @@ export const broadcastSchedulerTools = [
173
238
  ],
174
239
  defaultWorkspaceId: true,
175
240
  schema: z.object(scheduleShape).strict().superRefine(assertSingleRepeatAxis),
241
+ // Предпросмотр нового расписания: журнала подач у него нет. Предпросмотр правки существующего с его
242
+ // журналом — отдельный маршрут сервера; отгруженный инструмент его не зовёт.
176
243
  createRequest: (input) => ({
177
244
  method: 'POST',
178
245
  path: `/workspaces/${input.workspace_id}/broadcast-schedules/preview`,
179
246
  body: toScheduleBody(input),
180
247
  }),
181
248
  enhanceResponse: (response) => withInstructions(response, [
182
- 'Ничего не отправляет и не пишет: считает ТЕМ ЖЕ путём, что и крон — тот же источник фактов, тот же компилятор правила, те же исключения.',
183
- '⚠️ Показывай пользователю excludedBy числами. alreadySentSegment — этим сегментом уже слали; alreadyGreeted — человек уже получал «Первое сообщение» (одно на контакт навсегда, любым сегментом; прежнее имя ключа coldWelcomeClaimed приходит от старого бэкенда в окне выкатки); activeEpisode — человека уже ведёт автоприём регистраций (приветствие должны и не отдали, либо подготовлен повторный вход на новый эфир); touchClaimed — это касание уже занято в журнале; journal — контакт подавался этим расписанием и ждёт паузы; cooldown — получал любую нашу рассылку в окне.',
184
- '⚠️⚠️ productNotInList — у расписания задан список продуктов, а у этих людей продукт другой или не указан вовсе. Прежде этот отсев шёл МОЛЧА: строка выбрасывалась без счётчика, и часть пула исчезала без причины. Чинится списком продуктов в расписании.',
185
- '⚠️⚠️ notReachable — ни один из ВЫБРАННЫХ каналов до человека не доходит, и это подтвердил ответ школы. Про невыбранные каналы число не говорит ничего. Чинится составом каналов расписания или рассылки, а не правилом сегмента: вердикт записывается вместе с набором каналов и перестаёт держать паузу, как только набор изменили.',
186
- '⚠️⚠️ factsUnknown — НЕ отсев: эти люди в пуле. Число значит «школу про них ещё не спрашивали», и без него ноль в notReachable читается как «все достижимы». Доставки оно НЕ обещает: тот же прогон опрашивает школу перед подачей (по 300 за раз) и часть из них тогда отсеет — в отчёте прогона эти числа уже пересчитаны.',
187
- '⚠️ Если notReachable равен размеру пула, сперва проверь площадку: когда НИ ОДИН выбранный канал ею не обслуживается, недостижимы будут все до единого, и чинится это в настройках площадки, а не в расписании. Предпросмотр в этом случае показывает отдельное предупреждение, а прогон закрывается своей причиной.', '⚠️ unverifiable — «нечем проверить», а НЕ «не подходит»: нет назначения серии, серия ещё идёт, нет длительности просмотра. Эти люди в рассылку не идут, и это правильно: отсутствие данных о явке не равно отсутствию человека на эфире.',
188
- '⚠️⚠️ truncatedSeriesSkipped — ЕДИНСТВЕННАЯ источниковая причина, которую чинит сам оператор: выборка упёрлась в потолок строк, и запись о явке серии могла остаться за его границей, поэтому судить о ней нечем. Чинится окном расписания за минуту — сузить свежесть, список воронок или продуктов. До 10.09.2026 это число складывалось с unverifiable и читалось как жалоба на данные школы: не путай их снова.',
189
- '⚠️⚠️ claimedByHigherPriority — НЕ отсев по данным, а приоритет: человек подходит и под сегмент с БОЛЬШИМ приоритетом, у которого на этой площадке включено расписание ТОГО ЖЕ механизма, он в его окне и фильтрах, и то расписание его возьмёт — или уже отправило ему свой текст, в том числе волной школы. Исключение одно: старшее пишет только нетронутым, а этому человеку мы уже писали ДРУГИМ сегментом — тогда он остаётся младшему. Большое число — не поломка, а мера пересечения; меняется приоритетами сегментов. ⚠️ Число НЕ обещает, что человеку вообще что-то уйдёт: (1) потолок и день запуска старшего не проверяются — упрётся старшее в потолок, излишек ждёт следующего запуска, а при FRESH_FIRST самые старые из него выходят из окна ни с чем; (2) если текст старшего уже ушёл волной школы, подхватить человека может только расписание ПОВТОРА старшего сегмента, и его надо завести отдельно; (3) человек без живой карточки на этой площадке не получит ничего ни от кого — повтор старшего отсечёт его причиной repeatNoCard; (4) если у расписаний РАЗНЫЕ списки воронок, разведение неполно: воронки отсекаются на стороне источника фактов, и строк человека из чужой воронки в снимке нет вовсе — ставь пересекающимся сегментам одинаковые воронки.',
190
- '⚠️⚠️ repeatNoCard — кандидат на ПОВТОР (прошлое касание годится), но живой карточки на ЭТОЙ площадке у человека нет: чаще всего волну школы записали на эту площадку, а карточка у него на соседней. Без карточки не проверить ответы и дожимы, а импорт карточку здесь не заведёт (обновит соседнюю), поэтому такие люди исключаются. Расписанием это не чинится.',
191
- '⚠️⚠️ cardOnSiblingPlatform — человек живёт на СОСЕДНЕЙ площадке той же школы GetCourse, а на площадке расписания карточки у него нет. Импорт её сюда не перенесёт: он ищет человека по школе и обновляет ту карточку, что уже есть, — поэтому такие люди исключаются, а не уезжают в импорт (иначе каждый месяц они занимали бы слот дневного потолка и закрывались исходом NOT_IMPORTED). Расписанием не чинится: рассылать надо с той площадки, где человек живёт. Большое число у расписания на новой площадке — норма: люди школы живут на старой.',
192
- '⚠️⚠️ noMessengerVerdict — школа УЖЕ отвечала, что привязанных мессенджеров у человека нет, и ответ ещё в силе. Карточки в базе у него нет, импорт её не заведёт, поэтому подача сожгла бы слот дневного потолка и слот импортов воркспейса и закрылась бы исходом NOT_IMPORTED на 30 суток. Срок доверия ответу задаётся у ПЛОЩАДКИ (getcourse_settings.reachability_ttl_days, по умолчанию 7 суток; 0 — спрашивать школу каждый раз); когда он истечёт, человек снова уедет в импорт и школу спросят заново. Чинится почтой, но включать её надо В ДВУХ местах: в allowlist ПЛОЩАДКИ (getcourse_settings.available_transports) и в каналах рассылки/расписания. Гейт сужает каналы по allowlist площадки, поэтому Email только в рассылке ничего не меняет — и это верно: канал вне allowlist школа не отправит никогда.',
193
- '⚠️⚠️ notHandoffCandidate — расписание ПЕРЕДАЧИ (у него включён handoff_from_school_platforms) берёт только людей, чья карточка живёт на ДРУГИХ площадках той же школы и чья серия там уже закончилась (последний шаг календарной цепочки источника позади). Остальные — свои люди площадки, люди без карточек и те, у кого серия ещё идёт, — уходят в это число. У обычного расписания оно всегда ноль.',
194
- '⚠️⚠️ handoffReturn — этого человека передача уже увозила С ЭТОЙ площадки, и в рамках текущего входа в сегмент он сюда не вернётся: иначе два расписания-продолжения возили бы его по кругу, а каждый переезд необратим. Новый вход спустя время запрету не подлежит. Ненулевое число означает, что у вас кольцо из двух расписаний передачи — это настройка, а не сбой данных.',
195
- '⚠️⚠️ handoffBlocked — карточку не перенесли: предохранитель школы (идёт серия, действующая заявка, оплата), контактом занят человек (бан, оператор, мьют), личность занята другим процессом либо на площадке-адресате уже есть карточка с этим внешним id (последнее — навсегда). Такие люди в подачу не идут и получают исход HANDOFF_BLOCKED в журнале подач.',
196
- 'factsAsOf — момент, на который источник считает данные полными. Сильно устаревшее значение означает, что аналитика отстаёт и «не пришедшие» могут быть ложными.',
197
- '⚠️⚠️ overlaps — контакты, которых за последнюю неделю подавали ДРУГИЕ расписания. Непустой список означает, что человек рискует получить несколько сообщений: разведи расписания кулдауном или приоритетом сегментов, прежде чем включать.',
198
- 'byProduct показывает, как отбор разложится по продуктам — крон нарежет подачу этими же блоками.',
249
+ 'Ничего не отправляет и не пишет: считает ТЕМ ЖЕ отбором, что прогон, — тот же источник фактов, те же исключения, тот же предохранитель последним.',
250
+ SCHEDULE_EXCLUSION_HINT,
251
+ '⚠️ Отсев источника фактов — до пула: unverifiable — «нечем проверить» (нет назначения серии, серия идёт, нет длительности), а НЕ «не подходит»; missingIdentifiers — строки без номера ученика или заказа; lowCoverageSkipped — серии воронок с недостоверной явкой (воронки — в lowCoverageFunnels); truncatedSeriesSkipped — выборка упёрлась в потолок строк: чинится сужением окна, воронок или продуктов; productNotInList — продукт строки не из списка продуктов расписания.',
252
+ '⚠️⚠️ guardActiveSeries / guardPaid / guardActiveApplication / guardUnverified — зеркало предохранителя школы (идёт серия; купил после регистрации; живая заявка у менеджера; не смог проверить). Остановленные не занимают дневной потолок — его добирают следующие по очереди. guardUnchecked — НЕ отсев: столько отобранных предохранитель не проверил (cause=SOURCE_UNAVAILABLE — реестр не ответил; OUT_OF_TIME — проверка не уложилась во время запроса); их проверит сама отправка, и часть отсеется. На крупном канале предпросмотр почти всегда отвечает OUT_OF_TIME — полная картина с проверкой в besales_broadcast_pool_today.',
253
+ 'factsUnknown — НЕ отсев: отобранные, про которых школу о мессенджерах ещё не спрашивали, — без этого числа ноль в notReachable читался бы как «все достижимы». needsImport — скольким прогон сначала заведёт карточку или адрес на канале: они уйдут не сразу. resendOpened — скольким повтор сегмента по новой регистрации открыт.',
254
+ '⚠️ allChannelsUnserved=true — ни один выбранный способ отправки канал GetCourse не обслуживает: недостижимы будут все, и чинится это настройкой канала, а не правилом сегмента.',
255
+ 'factsAsOf — момент, на который источник считает данные полными; attendanceIngestAt=null — сигнала о приёме посещаемости нет. truncated=true — снимок упёрся в бюджет строк, показан не весь пул.',
256
+ '⚠️⚠️ overlaps — скольких из отобранных за последние 7 суток уже подавали ДРУГИЕ включённые расписания той же школы. Непустой список — риск нескольких сообщений одному человеку: разведи расписания приоритетом сегментов или тихим периодом, прежде чем включать.',
257
+ '⚠️⚠️ warnings — цена этой настройки, которую получит и сохранение: перескажи человеку каждое message своими словами ДО сохранения. SENIOR_FUNNELS_WIDER — у включённого расписания старшего сегмента того же канала список воронок шире: регистраций человека в его воронках этот отбор не видит, и приоритет пересечение не разведёт — человек может получить текст этого сегмента вместо старшего или оба. Предложи сделать списки funnels у этих расписаний одинаковыми.',
258
+ 'byProduct показывает, как отбор разложится по продуктам: прогон нарежет подачу этими же блоками.',
199
259
  ]),
200
260
  }),
201
261
  defineTool({
202
262
  name: 'besales_broadcast_schedule_upsert',
203
263
  sideEffect: 'write',
264
+ // Без schedule_id вызов СОЗДАЁТ расписание: при неизвестном исходе повтор завёл бы второе.
265
+ verifyWithToolName: 'besales_broadcast_schedule_list',
204
266
  requiredFields: [
205
267
  'segment_id',
206
268
  'mailing_variant_id',
@@ -229,16 +291,21 @@ export const broadcastSchedulerTools = [
229
291
  body: toScheduleBody(input),
230
292
  },
231
293
  enhanceResponse: (response) => withInstructions(response, [
232
- '⚠️ Создавай расписание ПОСЛЕ предпросмотра и без is_enabled=true: включённое расписание начнёт слать живым людям на ближайшем тике крона (каждые 5 минут).',
233
- 'time_local — в таймзоне ПЛОЩАДКИ, не в UTC и не в твоей. Сутки расписания тоже считаются по ней.',
294
+ 'Почему люди отсеялись — зови besales_broadcast_schedule_preview: в его подсказке объяснена каждая причина.',
295
+ '⚠️ Создавай расписание ПОСЛЕ предпросмотра и без is_enabled=true: включённое расписание начнёт слать живым людям на ближайшем проходе планировщика (каждые 5 минут).',
296
+ 'time_local — в часовом поясе КАНАЛА, не в UTC и не в твоём. Сутки расписания тоже считаются по нему.',
234
297
  'daily_cap — потолок ОТОБРАННЫХ за прогон, а не доставленных: часть отсеется гейтами отправки уже внутри пакета. Историческую разницу смотри в besales_broadcast_schedule_runs.',
235
298
  'Пустые funnels/product_codes (или "all") означают «без фильтра», а не «ничего»: это правило пустого списка во всей платформе.',
299
+ 'Поле, которого нет во входе, у существующего расписания остаётся как было; пустой список, false и null у guard_active_series — это значение, а не «не менять».',
300
+ '⚠️ Отказ сохранения — 400 с полем field (какое поле поправить) и объяснением в message: перескажи объяснение человеку своими словами и не повторяй вызов без правки названного поля.',
301
+ '⚠️⚠️ warnings — расписание СОХРАНЕНО, но у настройки есть цена: перескажи человеку каждое message своими словами. SENIOR_FUNNELS_WIDER — у включённого расписания старшего сегмента того же канала список воронок шире: приоритет не разведёт пересечение, человек может получить текст этого сегмента вместо старшего или оба. Предложи выровнять funnels у этих расписаний; повторять сохранение ради предупреждения не нужно.',
236
302
  'Механизм пока только cold_welcome: у волны свой контракт запуска, и планировщик не притворяется, что умеет её ставить в расписание.',
237
303
  ]),
238
304
  }),
239
305
  defineTool({
240
306
  name: 'besales_broadcast_schedule_delete',
241
307
  sideEffect: 'write',
308
+ verifyWithToolName: 'besales_broadcast_schedule_list',
242
309
  requiredFields: ['schedule_id', 'explicit_confirmation'],
243
310
  defaultWorkspaceId: true,
244
311
  schema: z
@@ -253,7 +320,9 @@ export const broadcastSchedulerTools = [
253
320
  path: `/workspaces/${input.workspace_id}/broadcast-schedules/${input.schedule_id}`,
254
321
  }),
255
322
  enhanceResponse: (response) => withInstructions(response, [
256
- 'Удаление уносит и историю прогонов расписания. Журнал подач исчезнет вместе с ней — а он держит информацию «кому мы уже подавали»: после удаления те же контакты снова станут кандидатами для нового расписания с тем же сегментом.',
323
+ 'outcome=DELETED — прогонов у расписания не было, оно удалено. outcome=ARCHIVED — прогоны были: расписание выключено и убрано из списка, история прогонов и журнал подач остались (история читается besales_broadcast_schedule_runs).',
324
+ '⚠️ Журнал подач читается по расписанию: новое расписание того же сегмента его не видит. От второго сообщения человека держат журнал касаний («этим сегментом уже слали») и право первого касания, а не архив.',
325
+ '409 — идёт прогон этого расписания: дождись его конца (besales_broadcast_schedule_runs) и повтори.',
257
326
  'Если нужно просто остановить рассылку — не удаляй, а сохрани расписание с is_enabled=false.',
258
327
  ]),
259
328
  }),
@@ -269,36 +338,42 @@ export const broadcastSchedulerTools = [
269
338
  limit: z.number().int().min(1).max(100).optional(),
270
339
  })
271
340
  .strict(),
272
- // ⚠️⚠️ Параметр клеится в ПУТЬ. Транспорт (`ApiToolRequest`) знает только method/path/body,
273
- // поля `query` в нём нет — оно выбрасывалось МОЛЧА, и `limit` не доезжал до бэкенда никогда:
274
- // сколько бы прогонов агент ни просил, приходило серверных 30, без ошибки и без признака
275
- // усечения. Тот же приём уже применён в `follow-up.ts` и `platform-setup.ts`.
341
+ // ⚠️⚠️ Параметр клеится в ПУТЬ: у транспорта (`ApiToolRequest`) поля `query` нет, и отдельное
342
+ // поле выбрасывалось бы молча — сервер отдавал бы свои 30 без признака усечения.
276
343
  createRequest: (input) => ({
277
344
  method: 'GET',
278
345
  path: `/workspaces/${input.workspace_id}/broadcast-schedules/${input.schedule_id}/runs` +
279
346
  buildListQuery({ limit: input.limit }),
280
347
  }),
281
348
  enhanceResponse: (response) => withInstructions(response, [
282
- 'periodBucket — локальная ДАТА площадки: один прогон на сутки, повтор в тот же день невозможен.',
283
- '⚠️ Статусы различают ПРИЧИНЫ, и чинятся они разным: DONE — пакет завершился; WAITING_RESOURCE — воркспейс был занят другим импортом, период НЕ сгорел и повторится следующим тиком; SKIPPED — правило отбора не дало кандидатов (смотри excludedBy); FAILED — упал запуск пакета; RUNNING — пакет ещё идёт.',
284
- '⚠️ selectedCount — сколько ОТОБРАНО, launchedCount — сколько фактически доставлено. Расхождение нормально: часть отсеивают гейты отправки внутри пакета. Не называй selectedCount «отправлено».',
285
- 'attempts растёт на каждом WAITING_RESOURCE: большое число означает, что воркспейс постоянно занят и расписание стоит передвинуть по времени.',
349
+ 'periodBucket — локальная ДАТА канала: один прогон на сутки, повтор в тот же день невозможен.',
350
+ '⚠️ Статусы различают ПРИЧИНЫ, и чинятся они разным: DONE — пакет завершился; WAITING_RESOURCE — прогон отложен, период НЕ сгорел, waitReason называет чем: WORKSPACE_BUSY_IMPORT — воркспейс занят импортом контактов; WORKSPACE_BUSY_BATCH — пакетом рассылки (ручным или соседнего расписания); FACTS_SOURCE_UNAVAILABLE — не ответил источник фактов; HIGHER_PRIORITY_CLAIMS_UNREADABLE — не прочитались старшие сегменты; CHANNEL_NOT_READY — канал школы не готов: подключение не читается или не отмечен ни один способ отправки (чинится настройкой канала); SEGMENT_RULE_CHANGED — правило сегмента правили, пока шёл отбор (отбор повторится по новому правилу); REPEAT_SEGMENT_PENDING — сегмент-повтор, его шаг отбора ещё не построен; ERASURE_RACE — во время отбора или записи плана стирали данные человека. Прогон повторится ближайшим проходом в тот же день и не раньше времени расписания; не дождался до конца дня — закрывается FAILED с closeReason PERIOD_ENDED_WAITING. SKIPPED и FAILED — период потрачен: closeReason называет причину (RUN_ABANDONED — исполнитель прогона оборвался, а период уже не догнать), closeReasonClass — её род (ANSWER — законный ответ «сегодня некому», FAILURE — отказ, который чинит оператор или настройка, OPERATOR — решение человека, UNKNOWN — перенесённый прогон, причина — текстом прежней системы в error). FAILED без closeReason — отбор отказал по самой настройке (расписания, сегмента или рассылки больше нет) или решение отбора прогона не читается: причина только словами в error. PENDING, RUNNING и IMPORTING — прогон ещё идёт; у IMPORTING тоже бывает waitReason (WORKSPACE_BUSY_IMPORT, WORKSPACE_BUSY_BATCH, CHANNEL_NOT_READY, ERASURE_RACE): карточки заведены, а подача ждёт — не дольше потолка фазы импорта, дальше FAILED с closeReason IMPORT_PHASE_TIMEOUT.',
351
+ SCHEDULE_CLOSE_CLASS_HINT,
352
+ SCHEDULE_GUARD_HINT,
353
+ SCHEDULE_BLOCKS_HINT,
354
+ 'error — та же причина ожидания или закрытия СЛОВАМИ на языке воркспейса: человеку пересказывай её, а не ключ waitReason или closeReason.',
355
+ '⚠️ selectedCount — сколько ОТОБРАНО, launchedCount — сколько ПОПЫТОК отправки: доставленные плюс касания, занятые без доказательства доставки. Это не «доставлено»: не называй «доставлено» ни selectedCount, ни launchedCount. Расхождение с selectedCount нормально: часть отсеивают гейты отправки внутри пакета.',
356
+ SCHEDULE_EXCLUSION_HINT,
357
+ 'attempts растёт на каждом WAITING_RESOURCE: большое число при WORKSPACE_BUSY_* означает, что воркспейс постоянно занят и расписание стоит передвинуть по времени; при FACTS_SOURCE_UNAVAILABLE чинится источник, при CHANNEL_NOT_READY — настройка канала, а не время.',
358
+ 'trigger=RUN_NOW — прогон запущен кнопкой «Запустить сейчас»; actorType и actorId называют, кто её нажал.',
286
359
  ]),
287
360
  }),
288
361
  /**
289
- * «Пул сегодня»: сегменты с правилом на площадке — сколько сейчас в пуле и сколько уйдёт.
362
+ * «Пул сегодня»: сегменты с правилом на канале — сколько сейчас в пуле и сколько уйдёт.
290
363
  *
291
- * ⚠️⚠️ Считает СЕРВЕР фоном, одной выкачкой источника фактов на площадку: живой предпросмотр
292
- * боевого расписания в таймаут MCP не укладывается (замер 09.09.2026). Вызов заказывает расчёт
293
- * и сразу отдаёт последний посчитанный; повторный вызов с теми же параметрами — опрос, новый
294
- * расчёт не заводится. С `local_day` — чтение утреннего расчёта того дня (история).
364
+ * ⚠️⚠️ Считает СЕРВЕР фоном, одной выкачкой источника фактов на канал: живой предпросмотр боевого
365
+ * расписания в таймаут MCP не укладывается. Вызов заказывает расчёт и сразу отдаёт последний
366
+ * посчитанный; повторный вызов с теми же параметрами — опрос, новый расчёт не заводится. С
367
+ * `local_day` — чтение утреннего расчёта того дня (история), без побочного эффекта.
295
368
  *
296
- * ⚠️⚠️ Побочный эффект ЕСТЬ, и инструмент не читающий: вызов без `local_day` — это POST,
297
- * который заводит расчёт (строка живёт до двух недель) и снимает прежние неначатые заказы того
298
- * же ключа. Пометка «только чтение» отдала бы его хосту в профиль, где вызовы одобряются без
299
- * спроса. Идемпотентность объявлена честно: повтор с теми же параметрами переиспользует живой
300
- * и свежий (до 5 минут) расчёт — на этом и держится опрос; исключение — `force`, он и назван
369
+ * ⚠️⚠️ Побочный эффект ЕСТЬ, и инструмент не читающий: вызов без `local_day` — это POST, который
370
+ * заводит расчёт (строка живёт до двух недель) и снимает прежние неначатые заказы того же
371
+ * заказчика. Пометка «только чтение» отдала бы его хосту в профиль, где вызовы одобряются без
372
+ * спроса. Идемпотентность объявлена честно: повтор с теми же параметрами переиспользует живой и
373
+ * свежий (до 5 минут) расчёт — на этом и держится опрос; исключение — `force`, он и назван
301
374
  * «только после своей правки».
375
+ *
376
+ * Схема — отгруженная; тело и строка запроса — в именах сервера v2 (`channelId`, а не `platformId`).
302
377
  */
303
378
  defineTool({
304
379
  name: 'besales_broadcast_pool_today',
@@ -312,11 +387,14 @@ export const broadcastSchedulerTools = [
312
387
  workspace_id: uuidSchema.optional(),
313
388
  platform_id: uuidSchema
314
389
  .optional()
315
- .describe('GetCourse-площадка. Не передан — первая активная площадка с расписанием.'),
390
+ .describe('Канал GetCourse. Не передан — самый старый по дате создания активный канал GetCourse с расписанием, иначе самый старый активный'),
316
391
  window_days: z.number().int().min(1).max(POOL_TODAY_WINDOW_DAYS_MAX).optional(),
317
392
  funnels: filterListSchema(FUNNELS_MAX).optional(),
318
393
  product_codes: filterListSchema(PRODUCTS_MAX).optional(),
319
- channels: z.array(getcourseTransportEnum).max(getcourseTransportEnum.options.length).optional(),
394
+ channels: z
395
+ .array(getcourseTransportEnum)
396
+ .max(getcourseTransportEnum.options.length)
397
+ .optional(),
320
398
  cooldown_days: z.number().int().min(0).max(COOLDOWN_DAYS_MAX).optional(),
321
399
  // ⚠️ Проверяется только ФОРМА: существование даты сверяет сервер.
322
400
  local_day: z.string().regex(LOCAL_DATE_RE, 'local_day: дата ГГГГ-ММ-ДД').optional(),
@@ -324,7 +402,7 @@ export const broadcastSchedulerTools = [
324
402
  })
325
403
  .strict()
326
404
  .superRefine((input, ctx) => {
327
- // ⚠️ Утренний расчёт дня считан с настройками по умолчанию: фильтры рядом с днём сервер
405
+ // ⚠️ Утренний расчёт дня посчитан с настройками по умолчанию: фильтры рядом с днём сервер
328
406
  // отвергает 400-й, и узнавать об этом из ошибки модель не должна.
329
407
  const withSettings = input.window_days !== undefined ||
330
408
  input.funnels !== undefined ||
@@ -336,7 +414,7 @@ export const broadcastSchedulerTools = [
336
414
  ctx.addIssue({
337
415
  code: z.ZodIssueCode.custom,
338
416
  path: ['local_day'],
339
- message: 'local_day читает утренний расчёт дня с настройками по умолчанию — окно, воронки, продукты, каналы, кулдаун и force с ним не передаются',
417
+ message: 'local_day читает утренний расчёт дня с настройками по умолчанию — окно, воронки, продукты, каналы, тихий период и force с ним не передаются',
340
418
  });
341
419
  }
342
420
  }),
@@ -344,32 +422,41 @@ export const broadcastSchedulerTools = [
344
422
  ? {
345
423
  method: 'GET',
346
424
  path: `/workspaces/${input.workspace_id}/broadcast-pool` +
347
- buildListQuery({ platformId: input.platform_id, day: input.local_day }),
425
+ buildListQuery({ channelId: input.platform_id, day: input.local_day }),
348
426
  }
349
427
  : {
350
428
  method: 'POST',
351
429
  path: `/workspaces/${input.workspace_id}/broadcast-pool/refresh`,
352
430
  body: compactObject({
353
- platformId: input.platform_id,
431
+ channelId: input.platform_id,
354
432
  windowDays: input.window_days,
355
433
  funnels: toList(input.funnels),
356
434
  products: toList(input.product_codes),
357
- channels: input.channels,
435
+ transports: input.channels,
358
436
  cooldownDays: input.cooldown_days,
359
437
  force: input.force,
360
438
  }),
361
439
  },
362
440
  enhanceResponse: (response) => withInstructions(response, [
363
- '⚠️⚠️ activeRun не пуст — идёт расчёт этих настроек: вызови инструмент ЕЩЁ РАЗ через минуту с теми же параметрами. Повторный вызов — опрос, новый расчёт он не заказывает. run — последний ПОСЧИТАННЫЙ расчёт этих настроек (run.finishedAt — когда); run=null — ещё не считали.',
364
- '⚠️⚠️ Строка с расписанием (scheduleId не null) считается ТЕМ ЖЕ путём, что его тик: окно, воронки, продукты, каналы и тихий период — самого расписания; параметры вызова на неё не действуют. У выключенного расписания selectedCount значит «уйдёт, если включить». Строка без расписания считается по параметрам вызова (окно window_days, по умолчанию 3 суток; воронки, продукты, каналы, кулдаун), без рассылки и с потолком одного запуска — это верхняя граница того, что дало бы расписание.',
365
- 'Числа строки: poolSize — кандидаты после гейтов источника; selectedCount — отобрано на момент расчёта (не «уедет сегодня»); отсеяно = poolSize − selectedCount, причины — preview.excludedBy тем же реестром, что у besales_broadcast_schedule_preview; preview.unverifiable — «нечем проверить», отсев источника ДО пула, а не причина отбора.',
366
- '⚠️ status: COMPUTED — посчитана; FAILED — не смогли (причина в error, числа null, а не 0); SKIPPED — не считается по правилу, код в skip.reason: REPEAT_NEEDS_SCHEDULE (повторному касанию нужно окно не короче skip.minDays — считается только расписанием), WINDOW_NOT_SHARED (окно регистраций шире общей выборки — только расписанием), PLATFORM_SERVES_NO_CHANNELS (площадка не обслуживает ни одного канала), DEADLINE (расчёт упёрся в потолок skip.deadlineMinutes раньше, чем дошёл до строки).',
367
- '⚠️ preview.truncated=true — «не весь пул»: выборка источника не дошла до начала окна строки, самые давние регистрации не рассмотрены. Совет «сузить свежесть» здесь не годится: выборку обрезал общий расчёт площадки.',
368
- 'failedRun — отказ (status=FAILED, причина в error) или замена новым заказом того же заказчика (CANCELLED), случившиеся ПОСЛЕ показанного расчёта. factsSource ≠ READY — источник фактов не настроен или выключен: расчёта не будет вовсе.',
369
- 'force=true — только после своей правки расписания, правила сегмента или запуска: без него свежий (до 5 минут) расчёт тех же настроек переиспользуется. Опрашивая идущий расчёт, force НЕ передавай — он заказал бы ещё один расчёт следом. local_day — утренний расчёт того дня (история «сегмент × день»), всегда с настройками по умолчанию.',
370
- '⚠️ Вызов с ДРУГИМИ параметрами снимает твой прежний неначатый заказ (расчёт идёт по одному на всю систему). Сравнивая площадки или окна, дождись готовности одной комбинации и только потом заказывай следующую — иначе ни одна не досчитается.',
371
- 'Суммы по строкам не называй «людьми»: человек в двух сегментах посчитан дважды. Поимённых списков в ответе нет — людей смотрят в интерфейсе, под правом на контакты.',
372
- 'Сводка выкачки у run: fetchedRows (строк источника), fetchTruncated (выборка упёрлась в бюджет — у строк она отзывается preview.truncated), registeredFrom/registeredTo (окно регистраций), trigger (MORNING — утренний расчёт истории, ON_DEMAND — заказанный), settings (настройки строк без расписания). Называй их, объясняя расхождение чисел между днями.',
441
+ '⚠️⚠️ activeRun не пуст — идёт расчёт этих настроек: вызови инструмент ЕЩЁ РАЗ через минуту с теми же параметрами. Повторный вызов — опрос, новый расчёт он не заказывает. run — последний ПОСЧИТАННЫЙ расчёт этих настроек (run.finishedAt — когда); run=null — ещё не считали. channelId — канал расчёта; channelId=null — у воркспейса нет активного канала GetCourse, считать нечего.',
442
+ '⚠️ 409 с кодом BROADCAST_POOL_WORKSPACE_ORDER_LIMIT — воркспейс уже держит два живых заказа с другими настройками (расчёт идёт по одному на всю систему): дождись готовности одного из них и повтори, НЕ перебирай параметры. factsSource ≠ READY (MISSING — источник фактов не настроен, DISABLED — выключен) — расчёта не будет вовсе. channelServesNoTransports=true — канал не обслуживает ни одного способа отправки: отправлять некуда, чинится настройкой канала.',
443
+ '⚠️⚠️ Строка с расписанием (scheduleId не null) считается ТЕМ ЖЕ путём, что его прогон, на момент ЗАПУСКА этой авторассылки: окно, воронки, продукты, способы отправки, тихий период, «не писать после» и перекрытие предохранителя серии — самого расписания; параметры вызова на неё не действуют. У выключенного расписания (schedule.isEnabled=false) selectedCount значит «уйдёт, если включить». Строка без расписания считается по параметрам вызова (окно window_days, по умолчанию 3 суток; воронки, продукты, способы отправки, тихий период), без рассылки и с потолком одного запуска — это верхняя граница того, что дало бы расписание. Сегмент без правила строки не получает.',
444
+ 'Числа строки: poolSize — кандидаты после гейтов источника; selectedCount — отобрано на момент запуска авторассылки строки (у строки без расписания — на момент дня) по тому, что известно сейчас, а не «уже уехало»; отсеяно = poolSize − selectedCount, причины — preview.excludedBy тем же реестром, что у besales_broadcast_schedule_preview; unverifiable, missingIdentifiers, lowCoverageSkipped и соседи там — отсев источника ДО пула, а не причина отбора. preview.guardUnchecked не пуст — предохранитель не проверил столько отобранных: «уйдёт» в этот раз завышено.',
445
+ '⚠️ status: COMPUTED — посчитана; FAILED — не смогли: failure.label — причина словами (её и называй человеку), failure.code — для тебя (SCHEDULE_NOT_FOUND, SEGMENT_NOT_FOUND, MAILING_VARIANT_NOT_FOUND, CHANNEL_NOT_READY — отказ по самой настройке; RULE_MISSING, RULE_UNREADABLE, SOURCE_FAILED, ATTENDANCE_INGEST_* и соседи — отказ правила или источника фактов; HIGHER_PRIORITY_CLAIMS_UNREADABLE — не прочитались расписания старших сегментов; UNEXPECTED — непредвиденный сбой; LEGACY_REASON — причина, записанная прежней системой, её текст в label), числа null, а не 0; SKIPPED — не считается по правилу, skip.reason и skip.label: REPEAT_NEEDS_SCHEDULE (повторному касанию нужно окно не короче skip.minDays — считается только расписанием), WINDOW_NOT_SHARED (окно регистраций skip.registrationDays суток шире общей выборки skip.maxDays — только расписанием), CHANNEL_SERVES_NO_TRANSPORTS (канал не обслуживает ни одного способа отправки), DEADLINE (расчёт упёрся в потолок skip.deadlineMinutes минут раньше, чем дошёл до строки).',
446
+ '⚠️ preview.truncated=true — «не весь пул»: выборка источника не дошла до начала окна строки, самые давние регистрации не рассмотрены. Совет «сузить свежесть» здесь не годится: выборку обрезал общий расчёт канала.',
447
+ 'failedRun — отказ (status=FAILED) или замена новым заказом того же заказчика (status=CANCELLED, reason.code=REPLACED_BY_NEW_ORDER), случившиеся ПОСЛЕ показанного расчёта; reason.label — причина словами (HEARTBEAT_LOST — расчёт прервался, DEADLINE — упёрся в потолок времени, FACTS_SOURCE — не ответил источник фактов).',
448
+ 'force=true — только после своей правки расписания, правила сегмента или запуска: без него свежий (до 5 минут) расчёт тех же настроек переиспользуется (запуск дня, отобравший людей или завершившийся после расчёта, переиспользование сбрасывает сам). Опрашивая идущий расчёт, force НЕ передавай — он заказал бы ещё один расчёт следом. local_day — утренний расчёт того дня (история «сегмент × день»), всегда с настройками по умолчанию.',
449
+ '⚠️ Вызов с ДРУГИМИ параметрами снимает твой прежний неначатый заказ (расчёт идёт по одному на всю систему). Сравнивая каналы или окна, дождись готовности одной комбинации и только потом заказывай следующую — иначе ни одна не досчитается.',
450
+ 'Суммы по строкам не называй «людьми»: человек в двух сегментах посчитан дважды. Поимённых списков в ответе нет — людей смотрят в интерфейсе, под правом на контакты (membersAvailable — список строки ещё хранится: только у двух последних расчётов канала и не дольше суток).',
451
+ '⚠️⚠️ «Сколько уйдёт сегодня» — это run.dayBreakdown, а не суммы poolSize/selectedCount: runDay (ГГГГ-ММ-ДД) — сегодня, пока хоть один запуск дня не завершился, потом — день ближайшего запуска; с local_day — сам этот день со всеми его запусками; scheduleIds — авторассылки этого дня; notComputedScheduleIds — авторассылки дня, которые не посчитались: числа дня без них неполные, так и скажи; ⚠️ план дня (registrations, people, excluded, selected, carriedOver, matured) — только по запускам, которые людей ещё не отбирали: ranScheduleIds — авторассылки дня, чей запуск уже прошёл: их итог — только facts.bySchedule, а у их сегментов run.rows[].dayBreakdown = null (списки после запуска планом не являются); с local_day непустой ranScheduleIds значит, что утренний расчёт досчитался уже после этих запусков, — так и скажи; allRan=true — все запуски дня уже прошли, плана нет: числа плана нулевые, не называй их, итог дня — facts; ranDuringComputation=true — запуск прошёл во время расчёта, числа неточны: без local_day повтори вызов с теми же параметрами (прошедший запуск сам сбрасывает переиспользование расчёта), с local_day утро не пересчитывается — оговори неточность; runTimes — время запусков дня по часам канала (прошедший — когда прогон отобрал людей, предстоящий — по правилу); ⚠️⚠️ план считается на момент ЗАПУСКА, а не расчёта: кто созреет к запуску по эфиру, который уже закончился, — уже в числах; явка эфира, который ещё идёт, не известна, и таких людей в числах нет, пока эфир не кончится; matured — registrations плюс outsideSegments (null — не посчитано), outsideSegments — созреют к запуску в окне авторассылок дня, которые их рассматривают (их воронки и продукты, не раньше их свежести, по закончившимся эфирам), но не попали в пул ни одной; outsideSegmentsBy — те же по причинам, только ненулевые ({code, label, count}: paid — купил, application — есть живая заявка, attendanceNoMinutes — был на эфире, минут не записано, paidElsewhere — покупал в другой воронке, seriesRunning — серия ещё идёт, other — прочее); одна причина на регистрацию, сумма равна outsideSegments; старые снимки могли относить paidElsewhere и seriesRunning к other; registrations — регистраций, вошедших в сегменты окнами своих авторассылок (от прошлого отбора до запуска; enteredFrom…enteredTo — общий охват этих окон); people — людей из них, КАЖДЫЙ ОДИН раз при любом числе сегментов; people = selected + сумма excluded; carriedOver — уйдут этим запуском люди с прошлых дней; unrecorded — люди без записанной причины (должно быть 0).',
452
+ SCHEDULE_POOL_GROUPS_HINT,
453
+ SCHEDULE_EXCLUSION_HINT,
454
+ '⚠️⚠️ После запуска экран показывает «Уйдёт» числом «Взято запусками» (facts.taken), когда все авторассылки плана (scheduleIds без ranScheduleIds и notComputedScheduleIds) уже отобрали людей и ни одна отобравшая не лежит вне плана; facts.morningPlanned — «Уйдёт» утреннего плана этого дня (selected + carriedOver утреннего расчёта; null — утреннего плана нет), разница с ним — «добавились к запуску» / «отпали к запуску»: авторассылки отбирают людей по очереди, по приоритету, и за это время у части людей кончаются паузы. Называя «Уйдёт» дня, чьи запуски прошли, называй «взято» и разницу с планом. У каждого сегмента то же в run.rows[].dayBreakdown (без matured; null — сегмент не посчитан или запуск его авторассылки уже прошёл), и его runDay может быть другим днём — у авторассылки не на каждый день; у выключенной авторассылки selected — «уйдёт, если включить», в план дня она не входит.',
455
+ 'facts — исход запусков дня run.dayBreakdown.runDay по людям, по всем авторассылкам канала: taken — взято (люди журнала подач), sent — отправлено, noMessenger — карточку не завели или ни один канал не доходит, pending — подан в ещё идущий прогон, notSent — не ушло или доставка не подтвердилась; notSentByReason — те же notSent по причинам, список {code, label, count}: один исход на человека, сумма равна notSent; code — исход отказа отправки (SKIPPED_BOT_SILENCED — бот выключен у контакта, SKIPPED_ALREADY_IN_DIALOG — разговор уже идёт, SKIPPED_UNRESOLVED_VARS — переменные текста без значения и соседи, как в outcomes у besales_broadcast_launches_list), без следа отправки — исход журнала подач (UNRESOLVED_AFTER_BATCH — причина не записана: прогоны до 16.09.2026, RELEASED_LAUNCH_FAILED — запуск сорвался); человеку называй label, а не коды; bySchedule — то же по авторассылкам (scheduleId).',
456
+ 'facts.scheduleRuns — запуски дня теми же числами, что строка прогона в besales_broadcast_launches_list: headline — «взято · отправлено» (poolMeasured=false — «счётчиков отбора нет», taken=null, и это НЕ ноль), selection — что увидел запуск при отборе (collapsed — несколько регистраций одного человека, excluded — люди ПУЛА по тем же группам, taken — взято, unrecorded — без записанной причины, должно быть 0; selection=null — пул не измерен), delivery — отправка прогона (null — «ещё неизвестно»: не завершён и никого не взял), planned — «Уйдёт» по утреннему плану сегмента на этот день, planDelta — «взято» минус план. Пул запуска — регистрации окна свежести, а не люди дня: суммы пулов разных авторассылок людьми дня не называй. selectedScheduleIds — авторассылки, чей прогон дня уже отобрал людей или завершился (без фильтров): есть там, но нет в run.dayBreakdown.ranScheduleIds — запуск прошёл уже после расчёта, и без local_day план устарел: повтори вызов с теми же параметрами. ⚠️ Под фильтром воронки или продукта план берёт человека с любой регистрацией в них, а факты — по регистрации, с которой ему написали: у людей с регистрациями в разных воронках план и факты расходятся. facts=null — прогонов авторассылок этого дня нет (запуска ещё не было).',
457
+ '⚠️⚠️ «Сколько ушло СЕГОДНЯ» — это todayFacts, а не facts: today — сегодняшний день канала (ГГГГ-ММ-ДД, по его часам), todayFacts — отправка сегодня в том же виде, что facts, какой бы день ни показывал расклад. После всех запусков дня run.dayBreakdown.runDay и facts уже про ЗАВТРА (facts=null — завтра запусков ещё не было), а сегодняшние итоги остаются в todayFacts. Отвечая, разделяй «Сегодня» (todayFacts) и план ближайшего дня (run.dayBreakdown). todayFacts=null — сегодня запусков не было; с local_day today и todayFacts всегда null — у дня истории итог только facts.',
458
+ 'days — до 14 дней канала, свежие первыми (для local_day): дни с утренним расчётом (в 8:00 по часам канала, до запусков дня и впереди заказов в очереди; опоздавший виден по непустому ranScheduleIds) и хотя бы одним запуском, а также дни с прошедшим запуском без утреннего расчёта; у такого дня run — null, итог дня по людям — только facts, а отбор каждого запуска — facts.scheduleRuns. Человеку называй сегменты и авторассылки, а не «строки».',
459
+ 'Сводка выкачки у run: fetchedRows (строк источника), fetchTruncated (выборка упёрлась в бюджет — у строк она отзывается preview.truncated), registeredFrom/registeredTo (окно регистраций), trigger (MORNING — утренний расчёт истории, ON_DEMAND — заказанный), settings (настройки строк без расписания). options — варианты фильтров (воронки и продукты, встреченные в выкачке). Называй их, объясняя расхождение чисел между днями.',
373
460
  ]),
374
461
  }),
375
462
  defineTool({
@@ -383,50 +470,50 @@ export const broadcastSchedulerTools = [
383
470
  segment_id: uuidSchema,
384
471
  facets: z
385
472
  .object({
386
- attended: z.enum(['YES', 'NO', 'ANY']).optional(),
473
+ attended: triStateSchema.optional(),
474
+ /**
475
+ * Был на ПЕРВОМ эфире серии. `NO` — пропустил первый эфир; вопрос отвечается, пока серия
476
+ * ещё идёт, поэтому с `attended` они не взаимозаменяемы.
477
+ */
478
+ attendedFirstSession: triStateSchema.optional(),
387
479
  activeSecondsMin: z.number().int().min(0).optional(),
388
480
  activeSecondsMax: z.number().int().min(0).optional(),
389
481
  webinarDayMin: z.number().int().min(0).optional(),
390
482
  webinarDayMax: z.number().int().min(0).optional(),
391
- bannerClick: z.enum(['YES', 'NO', 'ANY']).optional(),
392
- // Применимость и метки заказа предсписка объявляет ВОРОНКА в источнике фактов, а не
393
- // мы: у нас настройки больше нет. Правила у воронки нет или данные не готовы — строки
394
- // приходят непроверяемыми, и людям не уйдёт ничего.
395
- prelist: z.enum(['YES', 'NO', 'ANY']).optional(),
483
+ bannerClick: triStateSchema.optional(),
484
+ prelist: triStateSchema.optional(),
396
485
  /**
397
- * ⚠️⚠️ ПОВТОРНОЕ касание. Сегмент уходит тем, у кого `key` был ПОСЛЕДНИМ касанием —
398
- * нашей отправкой или волной школы на этой площадке — и кто нам не ответил. Снимает
399
- * вечный одноразовый claim приветствия — единственную структурную защиту от второго
400
- * сообщения живому человеку, — поэтому порог снизу зеркалит серверную константу и здесь:
401
- * ниже 30 суток сервер откажет.
486
+ * ⚠️⚠️ ПОВТОРНОЕ касание: снимает вечный одноразовый claim приветствия — единственную
487
+ * структурную защиту от второго сообщения живому человеку. Имя сегмента сервер
488
+ * переводит в его идентификатор при сохранении: переименование сегмента правило
489
+ * дальше не ломает.
402
490
  */
403
491
  previousSegment: z
404
492
  .object({
405
- key: z.string().min(1).max(64),
406
- minDaysSince: z.number().int().min(30).max(78),
493
+ key: z.string().min(1).max(PREVIOUS_SEGMENT_KEY_MAX_LENGTH),
494
+ minDaysSince: z
495
+ .number()
496
+ .int()
497
+ .min(REPEAT_TOUCH_MIN_DAYS)
498
+ .max(REPEAT_TOUCH_MAX_DAYS),
407
499
  })
408
500
  .strict()
409
501
  .optional(),
410
502
  /**
411
- * Право первого касания: сегмент уходит только тем, кому мы ещё НИКОГДА не писали.
412
- *
413
- * ⚠️⚠️ Молчание значит `true` — это сегодняшнее поведение платформы. Выключение
414
- * осознанное и снимает ровно одну защиту: человеку, уже получившему от нас холодное
415
- * приветствие, уйдёт ВТОРОЕ первое сообщение. Остальные гейты (ответ, оператор,
416
- * тихий период, «этим сегментом уже слали») продолжают работать.
503
+ * Право первого касания: молчание значит `true`. Единственный признак, который
504
+ * меняется и у замороженного сегмента.
417
505
  */
418
506
  untouchedOnly: z.boolean().optional(),
419
- // ⚠️⚠️ ОБЯЗАТЕЛЬНЫ на сервере (`SEGMENT_FACETS_REQUIRED_GUARDS`): необязательное
420
- // поле с дефолтом «неважно» делало законным правило целиком из умолчаний, а такое
421
- // правило матчит КАЖДОГО в окне — рассылка ушла бы и людям с живой заявкой у
422
- // менеджера, и уже заплатившим. `ANY` допустим, но выбирается ЯВНО.
507
+ // ⚠️⚠️ ОБЯЗАТЕЛЬНЫ: правило целиком из умолчаний подошло бы КАЖДОМУ в окне — рассылка
508
+ // ушла бы и людям с живой заявкой у менеджера, и уже заплатившим. `ANY` допустим, но
509
+ // выбирается ЯВНО.
423
510
  application: z.enum(['NONE', 'CANCELLED', 'ANY']),
424
511
  payment: z.enum(['NOT_IN_FUNNEL', 'NOT_ANYWHERE', 'ANY']),
425
512
  })
426
513
  .strict(),
427
514
  freshness_anchor: anchorSchema,
428
515
  wait_after_series_end_hours: z.number().int().min(0).max(WAIT_HOURS_MAX).optional(),
429
- priority: z.number().int().min(-1000).max(1000).optional(),
516
+ priority: z.number().int().min(PRIORITY_MIN).max(PRIORITY_MAX).optional(),
430
517
  })
431
518
  .strict(),
432
519
  createRequest: (input) => ({
@@ -440,13 +527,15 @@ export const broadcastSchedulerTools = [
440
527
  }),
441
528
  }),
442
529
  enhanceResponse: (response) => withInstructions(response, [
443
- '⚠️⚠️ 409 означает, что сегментом УЖЕ отправляли и правило заморожено. Это не ошибка доступа: строки отчётов уже несут это имя, и правка правила задним числом сделала бы разрез несопоставимым сам с собой. Заведи НОВЫЙ сегмент.',
444
- 'attended=NO проходит только достоверный «не пришёл». Человек, про которого данных нет (серия ещё идёт, назначения нет), в сегмент НЕ попадает — и это защита: отсутствие данных о явке не равно отсутствию человека на эфире.',
445
- 'Секунды, а не минуты: «от 120 минут» — это activeSecondsMin=7200. Контакт без известной длительности порог не проходит.',
446
- 'wait_after_series_end_hours — сколько ждать после эфира, прежде чем считать отсутствие явки доказанным. Дефолт 1 час: касание свежее, но явка к этому моменту досчитана у меньшинства (6 ч → 56 %, 24 ч → 74 %, 48 ч → 81 %), и недосчитанный контакт в отбор не попадает. Поэтому при коротком ожидании ставьте freshness_days от 3: при 1 человека смотрят ровно один раз.',
447
- 'application=CANCELLED требует отменённой заявки И отсутствия живой: с человеком, у которого заявка в работе, разговаривает менеджер.',
530
+ '⚠️⚠️ 409 означает, что сегментом УЖЕ отправляли и правило заморожено. Это не ошибка доступа: строки отчётов уже несут это имя, и правка правила задним числом сделала бы разрез несопоставимым сам с собой. Заведи НОВЫЙ сегмент. Исключения — priority и facets.untouchedOnly: их меняют и у замороженного сегмента, передав остальное правило (фасеты, freshness_anchor, wait_after_series_end_hours) ТЕМ ЖЕ, что у сегмента сейчас.',
531
+ 'warnings в ответе — правка принята, но у неё есть цена: перескажи её человеку своими словами (например, смена права первого касания у сегмента, которым уже отправляли, смешивает в его отчёте первые и повторные сообщения).',
532
+ 'attendedFirstSession=NO — «пропустил ПЕРВЫЙ эфир», единственный способ написать человеку, пока его серия идёт. Требует freshness_anchor=FIRST_SESSION_END_PLUS_WAIT (иначе сервер откажет: вход считался бы от конца серии — на 4–5 суток позже), а ни одно расписание сегмента не должно останавливать отправки внутри серии (guard_active_series не BLOCK — иначе сервер откажет, потому что пул отсеивался бы целиком на каждом запуске).',
533
+ 'attended=NO проходит только достоверный «не пришёл» — и одни короткие заходы меньше пяти минут в сумме. Человек, про которого данных нет (серия ещё идёт, назначения нет), в сегмент НЕ попадает: отсутствие данных о явке не равно отсутствию человека на эфире.',
534
+ 'Секунды, а не минуты: «от 120 минут» — это activeSecondsMin=7200. Человек без известной длительности порог не проходит.',
535
+ 'wait_after_series_end_hours — сколько ждать после эфира, прежде чем считать отсутствие явки доказанным. Не передано — 24 часа: к этому моменту явка досчитана примерно у трёх четвертей (6 ч → 56 %, 24 ч → 74 %, 48 ч → 81 %), а недосчитанный человек в отбор не попадает.',
536
+ 'application=CANCELLED требует отменённой заявки и отсутствия любой другой: живая, закрытая или незнакомая заявка бьёт отменённую.',
448
537
  'payment=NOT_ANYWHERE исключает всех, кто когда-либо успешно платил — включая тех, кому потом сделали возврат: он действительно платил.',
449
- 'untouchedOnly не указан = true: сегмент уходит только тем, кому мы никогда не писали. Ставьте false осознанно — это разрешает второе первое сообщение человеку, уже получившему от нас холодное приветствие в другой воронке.',
538
+ 'previousSegment.key — имя сегмента; сервер хранит его идентификатор, и в ответе прошлое касание приходит идентификатором с текущим именем. untouchedOnly не указан = true: сегмент уходит только тем, кому мы никогда не писали. Ставь false осознанно — это разрешает второе первое сообщение человеку, уже получившему от нас холодное приветствие.',
450
539
  ]),
451
540
  }),
452
541
  ];