@rubytech/create-maxy-code 0.1.582 → 0.1.583

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 (409) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/routine-templates/dist/index.d.ts +1 -1
  3. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/routine-templates/dist/index.js +5 -1
  5. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  6. package/payload/platform/lib/routine-templates/src/index.ts +4 -0
  7. package/payload/platform/lib/telegram-managed-bot/dist/index.d.ts +269 -0
  8. package/payload/platform/lib/telegram-managed-bot/dist/index.d.ts.map +1 -0
  9. package/payload/platform/lib/telegram-managed-bot/dist/index.js +351 -0
  10. package/payload/platform/lib/telegram-managed-bot/dist/index.js.map +1 -0
  11. package/payload/platform/lib/telegram-managed-bot/src/__tests__/managed-bot.test.ts +251 -0
  12. package/payload/platform/lib/telegram-managed-bot/src/index.ts +529 -0
  13. package/payload/platform/lib/telegram-managed-bot/tsconfig.json +9 -0
  14. package/payload/platform/lib/telegram-managed-bot/vitest.config.ts +9 -0
  15. package/payload/platform/lib/telegram-reach/dist/index.d.ts +80 -0
  16. package/payload/platform/lib/telegram-reach/dist/index.d.ts.map +1 -0
  17. package/payload/platform/lib/telegram-reach/dist/index.js +211 -0
  18. package/payload/platform/lib/telegram-reach/dist/index.js.map +1 -0
  19. package/payload/platform/lib/telegram-reach/src/__tests__/telegram-reach.test.ts +216 -0
  20. package/payload/platform/lib/telegram-reach/src/index.ts +262 -0
  21. package/payload/platform/lib/telegram-reach/tsconfig.json +9 -0
  22. package/payload/platform/lib/telegram-reach/vitest.config.ts +9 -0
  23. package/payload/platform/package.json +2 -2
  24. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  25. package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +9 -0
  26. package/payload/platform/plugins/admin/hooks/__tests__/preference-consult-directive.test.sh +26 -0
  27. package/payload/platform/plugins/admin/hooks/fs-schema-guard.sh +20 -3
  28. package/payload/platform/plugins/admin/hooks/preference-consult-directive.sh +50 -5
  29. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +142 -8
  30. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
  31. package/payload/platform/plugins/contacts/mcp/dist/__tests__/telegram-user-id.test.d.ts +2 -0
  32. package/payload/platform/plugins/contacts/mcp/dist/__tests__/telegram-user-id.test.d.ts.map +1 -0
  33. package/payload/platform/plugins/contacts/mcp/dist/__tests__/telegram-user-id.test.js +100 -0
  34. package/payload/platform/plugins/contacts/mcp/dist/__tests__/telegram-user-id.test.js.map +1 -0
  35. package/payload/platform/plugins/contacts/mcp/dist/index.js +11 -5
  36. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  37. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts +5 -0
  38. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
  39. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +21 -4
  40. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  41. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +5 -0
  42. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  43. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +29 -1
  44. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  45. package/payload/platform/plugins/contacts/mcp/vitest.config.ts +14 -0
  46. package/payload/platform/plugins/docs/references/admin-ui.md +17 -3
  47. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +15 -0
  48. package/payload/platform/plugins/docs/references/telegram-guide.md +109 -4
  49. package/payload/platform/plugins/filesystem/PLUGIN.md +2 -2
  50. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.d.ts +2 -0
  51. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.d.ts.map +1 -0
  52. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.js +108 -0
  53. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.js.map +1 -0
  54. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.d.ts +17 -0
  55. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.d.ts.map +1 -0
  56. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.js +162 -0
  57. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.js.map +1 -0
  58. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.d.ts +2 -0
  59. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.d.ts.map +1 -0
  60. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.js +116 -0
  61. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.js.map +1 -0
  62. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts.map +1 -1
  63. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js +9 -0
  64. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js.map +1 -1
  65. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts.map +1 -1
  66. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js +10 -0
  67. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js.map +1 -1
  68. package/payload/platform/plugins/scheduling/PLUGIN.md +8 -2
  69. package/payload/platform/plugins/scheduling/mcp/dist/index.js +7 -2
  70. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  71. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js +53 -6
  72. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js.map +1 -1
  73. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +38 -0
  74. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -1
  75. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +5 -3
  76. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -1
  77. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +18 -3
  78. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -1
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +30 -0
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -1
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +112 -39
  82. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -1
  83. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +14 -1
  84. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  85. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +258 -8
  86. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  87. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +206 -3
  88. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  89. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +29 -21
  90. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  91. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +94 -14
  92. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  93. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  94. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +29 -1
  95. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  96. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +30 -0
  97. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  98. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +69 -6
  99. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  100. package/payload/platform/plugins/telegram/PLUGIN.md +44 -3
  101. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +3 -3
  102. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +28 -2
  103. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -1
  104. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-tool.test.js +87 -3
  105. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-tool.test.js.map +1 -1
  106. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.d.ts +2 -0
  107. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.d.ts.map +1 -0
  108. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.js +326 -0
  109. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.js.map +1 -0
  110. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.d.ts +2 -0
  111. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.d.ts.map +1 -0
  112. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.js +173 -0
  113. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.js.map +1 -0
  114. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-tool.test.js +79 -0
  115. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-tool.test.js.map +1 -1
  116. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.d.ts +2 -0
  117. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.d.ts.map +1 -0
  118. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.js +34 -0
  119. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.js.map +1 -0
  120. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.d.ts +2 -0
  121. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.d.ts.map +1 -0
  122. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.js +93 -0
  123. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.js.map +1 -0
  124. package/payload/platform/plugins/telegram/mcp/dist/__tests__/thread-tool.test.js +15 -1
  125. package/payload/platform/plugins/telegram/mcp/dist/__tests__/thread-tool.test.js.map +1 -1
  126. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js +178 -2
  127. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js.map +1 -1
  128. package/payload/platform/plugins/telegram/mcp/dist/index.js +26 -4
  129. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  130. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +19 -0
  131. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -1
  132. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +41 -1
  133. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -1
  134. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +15 -20
  135. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
  136. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +27 -32
  137. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
  138. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts +13 -0
  139. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts.map +1 -1
  140. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js +22 -1
  141. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js.map +1 -1
  142. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -1
  143. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +6 -0
  144. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -1
  145. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +1 -1
  146. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -1
  147. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +22 -2
  148. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -1
  149. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.d.ts +55 -0
  150. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.d.ts.map +1 -0
  151. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.js +116 -0
  152. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.js.map +1 -0
  153. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.d.ts +54 -0
  154. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.d.ts.map +1 -0
  155. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.js +90 -0
  156. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.js.map +1 -0
  157. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +2 -53
  158. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -1
  159. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +8 -94
  160. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -1
  161. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -1
  162. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +19 -0
  163. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -1
  164. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.d.ts.map +1 -1
  165. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.js +31 -1
  166. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.js.map +1 -1
  167. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.d.ts +64 -0
  168. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.d.ts.map +1 -0
  169. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.js +217 -0
  170. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.js.map +1 -0
  171. package/payload/platform/plugins/telegram/mcp/dist/tools/card.d.ts +7 -0
  172. package/payload/platform/plugins/telegram/mcp/dist/tools/card.d.ts.map +1 -1
  173. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js +56 -0
  174. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js.map +1 -1
  175. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts.map +1 -1
  176. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js +15 -0
  177. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js.map +1 -1
  178. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.d.ts.map +1 -1
  179. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.js +12 -0
  180. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.js.map +1 -1
  181. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.d.ts +22 -0
  182. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.d.ts.map +1 -0
  183. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.js +67 -0
  184. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.js.map +1 -0
  185. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.d.ts +19 -0
  186. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.d.ts.map +1 -1
  187. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.js +45 -0
  188. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.js.map +1 -1
  189. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.d.ts.map +1 -1
  190. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.js +12 -0
  191. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.js.map +1 -1
  192. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts +4 -1
  193. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts.map +1 -1
  194. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js +59 -4
  195. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js.map +1 -1
  196. package/payload/platform/plugins/telegram/skills/build/SKILL.md +143 -5
  197. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +87 -6
  198. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +12 -0
  199. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +36 -5
  200. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  201. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +103 -10
  202. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  203. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  204. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  205. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  206. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  207. package/payload/platform/services/claude-session-manager/dist/http-server.js +42 -4
  208. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  209. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  210. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +19 -5
  211. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  212. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +9 -0
  213. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  214. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +5 -1
  215. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  216. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  217. package/payload/platform/services/telegram-channel/dist/instructions.js +13 -0
  218. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  219. package/payload/platform/services/telegram-channel/dist/notification.d.ts +13 -0
  220. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  221. package/payload/platform/services/telegram-channel/dist/notification.js +13 -2
  222. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  223. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +52 -0
  224. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  225. package/payload/platform/services/whatsapp-channel/dist/notification.js +64 -1
  226. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  227. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  228. package/payload/platform/services/whatsapp-channel/dist/server.js +14 -2
  229. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  230. package/payload/platform/templates/account-schema/SCHEMA.md +17 -10
  231. package/payload/platform/templates/agents/admin/IDENTITY.md +5 -0
  232. package/payload/platform/templates/specialists/agents/data-manager.md +2 -2
  233. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  234. package/payload/server/{chunk-BFRB2U75.js → chunk-CV6AEMKH.js} +1 -0
  235. package/payload/server/{manager-CTVTF4YB.js → manager-FQSQSSJ3.js} +1 -1
  236. package/payload/server/public/activity.html +6 -6
  237. package/payload/server/public/agents.html +5 -5
  238. package/payload/server/public/assets/{AdminLoginScreens-AnJZfgFz.js → AdminLoginScreens-BLsDYWVi.js} +1 -1
  239. package/payload/server/public/assets/AdminLoginScreens-BLsDYWVi.js.br +0 -0
  240. package/payload/server/public/assets/AdminLoginScreens-BLsDYWVi.js.gz +0 -0
  241. package/payload/server/public/assets/{AdminShell-CEVqyQqg.js → AdminShell-BZS8jzFA.js} +1 -1
  242. package/payload/server/public/assets/AdminShell-BZS8jzFA.js.br +0 -0
  243. package/payload/server/public/assets/AdminShell-BZS8jzFA.js.gz +0 -0
  244. package/payload/server/public/assets/{activity-czUaUl8I.js → activity-DOjvoLdd.js} +1 -1
  245. package/payload/server/public/assets/activity-DOjvoLdd.js.br +0 -0
  246. package/payload/server/public/assets/activity-DOjvoLdd.js.gz +0 -0
  247. package/payload/server/public/assets/{admin-B0iRcfuk.js → admin-JrSNg29g.js} +1 -1
  248. package/payload/server/public/assets/admin-JrSNg29g.js.br +0 -0
  249. package/payload/server/public/assets/admin-JrSNg29g.js.gz +0 -0
  250. package/payload/server/public/assets/{agents-BocONCeQ.js → agents--9WMJVLC.js} +1 -1
  251. package/payload/server/public/assets/agents--9WMJVLC.js.br +0 -0
  252. package/payload/server/public/assets/agents--9WMJVLC.js.gz +0 -0
  253. package/payload/server/public/assets/{browser-Cy-rJm3t.js → browser-DiiMmTrM.js} +1 -1
  254. package/payload/server/public/assets/browser-DiiMmTrM.js.br +0 -0
  255. package/payload/server/public/assets/browser-DiiMmTrM.js.gz +0 -0
  256. package/payload/server/public/assets/{calendar-DUTKxWtd.js → calendar-CazmCzgO.js} +1 -1
  257. package/payload/server/public/assets/calendar-CazmCzgO.js.br +0 -0
  258. package/payload/server/public/assets/calendar-CazmCzgO.js.gz +0 -0
  259. package/payload/server/public/assets/chat-CB0TcyXX.js +1 -0
  260. package/payload/server/public/assets/chat-CB0TcyXX.js.br +0 -0
  261. package/payload/server/public/assets/chat-CB0TcyXX.js.gz +0 -0
  262. package/payload/server/public/assets/chevron-left-0tVo23Dh.js +1 -0
  263. package/payload/server/public/assets/chevron-left-0tVo23Dh.js.br +0 -0
  264. package/payload/server/public/assets/clock-D2cZ0sge.js +1 -0
  265. package/payload/server/public/assets/clock-D2cZ0sge.js.br +0 -0
  266. package/payload/server/public/assets/clock-D2cZ0sge.js.gz +0 -0
  267. package/payload/server/public/assets/{copy-DE9qmAs5.js → copy-C87QIN_o.js} +1 -1
  268. package/payload/server/public/assets/copy-C87QIN_o.js.br +0 -0
  269. package/payload/server/public/assets/copy-C87QIN_o.js.gz +0 -0
  270. package/payload/server/public/assets/data-CK7AMxPt.js +1 -0
  271. package/payload/server/public/assets/data-CK7AMxPt.js.br +0 -0
  272. package/payload/server/public/assets/data-CK7AMxPt.js.gz +0 -0
  273. package/payload/server/public/assets/{field-DE2tEfTo.js → field-C3kQgGWE.js} +1 -1
  274. package/payload/server/public/assets/field-C3kQgGWE.js.br +0 -0
  275. package/payload/server/public/assets/field-C3kQgGWE.js.gz +0 -0
  276. package/payload/server/public/assets/{file-text-BHfL2gqo.js → file-text-rWowl9RN.js} +1 -1
  277. package/payload/server/public/assets/file-text-rWowl9RN.js.br +0 -0
  278. package/payload/server/public/assets/file-text-rWowl9RN.js.gz +0 -0
  279. package/payload/server/public/assets/{graph-08S_BofN.js → graph-CVJKz1Uo.js} +1 -1
  280. package/payload/server/public/assets/graph-CVJKz1Uo.js.br +0 -0
  281. package/payload/server/public/assets/graph-CVJKz1Uo.js.gz +0 -0
  282. package/payload/server/public/assets/{graph-labels-C640Er5R.js → graph-labels-kneGnsHB.js} +1 -1
  283. package/payload/server/public/assets/graph-labels-kneGnsHB.js.br +0 -0
  284. package/payload/server/public/assets/graph-labels-kneGnsHB.js.gz +0 -0
  285. package/payload/server/public/assets/{jsx-runtime-FxjwOUbA.css → jsx-runtime-rMaiYjNu.css} +1 -1
  286. package/payload/server/public/assets/jsx-runtime-rMaiYjNu.css.br +0 -0
  287. package/payload/server/public/assets/{jsx-runtime-FxjwOUbA.css.gz → jsx-runtime-rMaiYjNu.css.gz} +0 -0
  288. package/payload/server/public/assets/{operator-DH5Kw3fL.js → operator-BGvB4hu4.js} +1 -1
  289. package/payload/server/public/assets/operator-BGvB4hu4.js.br +0 -0
  290. package/payload/server/public/assets/operator-BGvB4hu4.js.gz +0 -0
  291. package/payload/server/public/assets/{page-BDpa4HWT.js → page-DBQLOasI.js} +1 -1
  292. package/payload/server/public/assets/page-DBQLOasI.js.br +0 -0
  293. package/payload/server/public/assets/page-DBQLOasI.js.gz +0 -0
  294. package/payload/server/public/assets/{page-yn7ivq8O.js → page-DyVAu7tm.js} +1 -1
  295. package/payload/server/public/assets/page-DyVAu7tm.js.br +0 -0
  296. package/payload/server/public/assets/page-DyVAu7tm.js.gz +0 -0
  297. package/payload/server/public/assets/play-DgP7vl0r.js +1 -0
  298. package/payload/server/public/assets/play-DgP7vl0r.js.br +0 -0
  299. package/payload/server/public/assets/play-DgP7vl0r.js.gz +0 -0
  300. package/payload/server/public/assets/{public-D_qjqet7.js → public-BvUQR3hd.js} +1 -1
  301. package/payload/server/public/assets/public-BvUQR3hd.js.br +0 -0
  302. package/payload/server/public/assets/public-BvUQR3hd.js.gz +0 -0
  303. package/payload/server/public/assets/{rotate-ccw-Ixkq1iUA.js → rotate-ccw-Dnn7gbPh.js} +1 -1
  304. package/payload/server/public/assets/rotate-ccw-Dnn7gbPh.js.br +0 -0
  305. package/payload/server/public/assets/rotate-ccw-Dnn7gbPh.js.gz +0 -0
  306. package/payload/server/public/assets/{routines-Cxjlmcp7.js → routines-N8NmxPOZ.js} +1 -1
  307. package/payload/server/public/assets/routines-N8NmxPOZ.js.br +0 -0
  308. package/payload/server/public/assets/routines-N8NmxPOZ.js.gz +0 -0
  309. package/payload/server/public/assets/{skills-DEo9hSVX.js → skills-B2CRgO-U.js} +1 -1
  310. package/payload/server/public/assets/skills-B2CRgO-U.js.br +0 -0
  311. package/payload/server/public/assets/skills-B2CRgO-U.js.gz +0 -0
  312. package/payload/server/public/assets/{tasks-BxApOIXI.js → tasks-8QLGtlSj.js} +1 -1
  313. package/payload/server/public/assets/tasks-8QLGtlSj.js.br +0 -0
  314. package/payload/server/public/assets/tasks-8QLGtlSj.js.gz +0 -0
  315. package/payload/server/public/assets/{triangle-alert-BYzMcXv8.js → triangle-alert-el772yUQ.js} +1 -1
  316. package/payload/server/public/assets/triangle-alert-el772yUQ.js.br +0 -0
  317. package/payload/server/public/assets/triangle-alert-el772yUQ.js.gz +0 -0
  318. package/payload/server/public/assets/{useCopyFeedback-Bk2HVcjy.js → useCopyFeedback-DfNAu8hQ.js} +1 -1
  319. package/payload/server/public/assets/useCopyFeedback-DfNAu8hQ.js.br +0 -0
  320. package/payload/server/public/assets/useCopyFeedback-DfNAu8hQ.js.gz +0 -0
  321. package/payload/server/public/assets/{useMediaQuery-DEti-vAx.js → useMediaQuery-B469O83-.js} +1 -1
  322. package/payload/server/public/assets/useMediaQuery-B469O83-.js.br +0 -0
  323. package/payload/server/public/assets/{useMediaQuery-DEti-vAx.js.gz → useMediaQuery-B469O83-.js.gz} +0 -0
  324. package/payload/server/public/assets/{useVoiceRecorder-kQ92rL-f.js → useVoiceRecorder-Bb-51cJv.js} +1 -1
  325. package/payload/server/public/assets/useVoiceRecorder-Bb-51cJv.js.br +0 -0
  326. package/payload/server/public/assets/useVoiceRecorder-Bb-51cJv.js.gz +0 -0
  327. package/payload/server/public/assets/{wrench-BhLkHffv.js → wrench-Ck4phkdB.js} +1 -1
  328. package/payload/server/public/assets/wrench-Ck4phkdB.js.br +0 -0
  329. package/payload/server/public/assets/wrench-Ck4phkdB.js.gz +0 -0
  330. package/payload/server/public/browser.html +5 -5
  331. package/payload/server/public/calendar.html +8 -8
  332. package/payload/server/public/chat.html +14 -14
  333. package/payload/server/public/data.html +12 -12
  334. package/payload/server/public/field.html +3 -3
  335. package/payload/server/public/graph.html +10 -10
  336. package/payload/server/public/index.html +14 -14
  337. package/payload/server/public/operator.html +15 -15
  338. package/payload/server/public/public.html +14 -14
  339. package/payload/server/public/routines.html +8 -8
  340. package/payload/server/public/skills.html +6 -6
  341. package/payload/server/public/tasks.html +7 -7
  342. package/payload/server/server.js +3020 -1267
  343. package/payload/server/public/assets/AdminLoginScreens-AnJZfgFz.js.br +0 -0
  344. package/payload/server/public/assets/AdminLoginScreens-AnJZfgFz.js.gz +0 -0
  345. package/payload/server/public/assets/AdminShell-CEVqyQqg.js.br +0 -0
  346. package/payload/server/public/assets/AdminShell-CEVqyQqg.js.gz +0 -0
  347. package/payload/server/public/assets/activity-czUaUl8I.js.br +0 -0
  348. package/payload/server/public/assets/activity-czUaUl8I.js.gz +0 -0
  349. package/payload/server/public/assets/admin-B0iRcfuk.js.br +0 -0
  350. package/payload/server/public/assets/admin-B0iRcfuk.js.gz +0 -0
  351. package/payload/server/public/assets/agents-BocONCeQ.js.br +0 -0
  352. package/payload/server/public/assets/agents-BocONCeQ.js.gz +0 -0
  353. package/payload/server/public/assets/browser-Cy-rJm3t.js.br +0 -0
  354. package/payload/server/public/assets/browser-Cy-rJm3t.js.gz +0 -0
  355. package/payload/server/public/assets/calendar-DUTKxWtd.js.br +0 -0
  356. package/payload/server/public/assets/calendar-DUTKxWtd.js.gz +0 -0
  357. package/payload/server/public/assets/chat-Cis7sHsZ.js +0 -1
  358. package/payload/server/public/assets/chat-Cis7sHsZ.js.br +0 -0
  359. package/payload/server/public/assets/chat-Cis7sHsZ.js.gz +0 -0
  360. package/payload/server/public/assets/chevron-left-Bhhx8j2r.js +0 -1
  361. package/payload/server/public/assets/chevron-left-Bhhx8j2r.js.br +0 -0
  362. package/payload/server/public/assets/clock-Ca3nx7Cc.js +0 -1
  363. package/payload/server/public/assets/clock-Ca3nx7Cc.js.br +0 -0
  364. package/payload/server/public/assets/clock-Ca3nx7Cc.js.gz +0 -0
  365. package/payload/server/public/assets/copy-DE9qmAs5.js.br +0 -0
  366. package/payload/server/public/assets/copy-DE9qmAs5.js.gz +0 -0
  367. package/payload/server/public/assets/data-CkaAEeP1.js +0 -1
  368. package/payload/server/public/assets/data-CkaAEeP1.js.br +0 -0
  369. package/payload/server/public/assets/data-CkaAEeP1.js.gz +0 -0
  370. package/payload/server/public/assets/field-DE2tEfTo.js.br +0 -0
  371. package/payload/server/public/assets/field-DE2tEfTo.js.gz +0 -0
  372. package/payload/server/public/assets/file-text-BHfL2gqo.js.br +0 -0
  373. package/payload/server/public/assets/file-text-BHfL2gqo.js.gz +0 -0
  374. package/payload/server/public/assets/graph-08S_BofN.js.br +0 -0
  375. package/payload/server/public/assets/graph-08S_BofN.js.gz +0 -0
  376. package/payload/server/public/assets/graph-labels-C640Er5R.js.br +0 -0
  377. package/payload/server/public/assets/graph-labels-C640Er5R.js.gz +0 -0
  378. package/payload/server/public/assets/jsx-runtime-FxjwOUbA.css.br +0 -0
  379. package/payload/server/public/assets/operator-DH5Kw3fL.js.br +0 -0
  380. package/payload/server/public/assets/operator-DH5Kw3fL.js.gz +0 -0
  381. package/payload/server/public/assets/page-BDpa4HWT.js.br +0 -0
  382. package/payload/server/public/assets/page-BDpa4HWT.js.gz +0 -0
  383. package/payload/server/public/assets/page-yn7ivq8O.js.br +0 -0
  384. package/payload/server/public/assets/page-yn7ivq8O.js.gz +0 -0
  385. package/payload/server/public/assets/play-CMN7ht-S.js +0 -1
  386. package/payload/server/public/assets/play-CMN7ht-S.js.br +0 -0
  387. package/payload/server/public/assets/play-CMN7ht-S.js.gz +0 -0
  388. package/payload/server/public/assets/public-D_qjqet7.js.br +0 -0
  389. package/payload/server/public/assets/public-D_qjqet7.js.gz +0 -0
  390. package/payload/server/public/assets/rotate-ccw-Ixkq1iUA.js.br +0 -0
  391. package/payload/server/public/assets/rotate-ccw-Ixkq1iUA.js.gz +0 -0
  392. package/payload/server/public/assets/routines-Cxjlmcp7.js.br +0 -0
  393. package/payload/server/public/assets/routines-Cxjlmcp7.js.gz +0 -0
  394. package/payload/server/public/assets/skills-DEo9hSVX.js.br +0 -0
  395. package/payload/server/public/assets/skills-DEo9hSVX.js.gz +0 -0
  396. package/payload/server/public/assets/tasks-BxApOIXI.js.br +0 -0
  397. package/payload/server/public/assets/tasks-BxApOIXI.js.gz +0 -0
  398. package/payload/server/public/assets/triangle-alert-BYzMcXv8.js.br +0 -0
  399. package/payload/server/public/assets/triangle-alert-BYzMcXv8.js.gz +0 -0
  400. package/payload/server/public/assets/useCopyFeedback-Bk2HVcjy.js.br +0 -0
  401. package/payload/server/public/assets/useCopyFeedback-Bk2HVcjy.js.gz +0 -0
  402. package/payload/server/public/assets/useMediaQuery-DEti-vAx.js.br +0 -0
  403. package/payload/server/public/assets/useVoiceRecorder-kQ92rL-f.js.br +0 -0
  404. package/payload/server/public/assets/useVoiceRecorder-kQ92rL-f.js.gz +0 -0
  405. package/payload/server/public/assets/wrench-BhLkHffv.js.br +0 -0
  406. package/payload/server/public/assets/wrench-BhLkHffv.js.gz +0 -0
  407. /package/payload/server/public/assets/{jsx-runtime-BL3pljPe.js → jsx-runtime-BmNepPEn.js} +0 -0
  408. /package/payload/server/public/assets/{jsx-runtime-BL3pljPe.js.br → jsx-runtime-BmNepPEn.js.br} +0 -0
  409. /package/payload/server/public/assets/{jsx-runtime-BL3pljPe.js.gz → jsx-runtime-BmNepPEn.js.gz} +0 -0
@@ -17,10 +17,11 @@ and this skill says how to call it.
17
17
 
18
18
  ## What you have
19
19
 
20
- Ten named tools for the common paths, and one passthrough for everything else.
20
+ Eleven named tools for the common paths, and one passthrough for everything else.
21
21
 
22
22
  | Tool | Use it for |
23
23
  |---|---|
24
+ | `mcp__telegram__telegram-reach` | Which chats each registered bot can message now, and which allowlisted admins have never messaged it |
24
25
  | `mcp__telegram__telegram-card` | Posting a card, or editing the one your key already names |
25
26
  | `mcp__telegram__telegram-ask` | Polls, checklists, locations, venues, contacts, dice |
26
27
  | `mcp__telegram__telegram-media-send` | Sending a photo, file, video, audio or album, and editing media or captions |
@@ -36,7 +37,20 @@ Ten named tools for the common paths, and one passthrough for everything else.
36
37
  `telegram-api` takes `botId` (never a token), `method`, `params`, and optionally `files` and
37
38
  `timeoutMs`. Field names inside `params` are Telegram's own snake_case.
38
39
 
39
- ## The five methods it will not call
40
+ ## Who you can send to
41
+
42
+ Telegram does not let a bot open a conversation. Somebody who has messaged one of this account's
43
+ bots can be messaged back at any time with `telegram-card` and their chat id; somebody who never has
44
+ must write to the bot first, and no tool here can shortcut that.
45
+
46
+ `telegram-reach` is what tells the two apart. It takes no parameters and lists, per registered bot,
47
+ every private chat that has messaged it — chat id, name, and when they last wrote — plus any admin
48
+ user allowlisted on that bot who has never messaged. Being on the allowlist is not the same fact as
49
+ having opened the chat, and only the second one makes a send possible.
50
+
51
+ Read it before telling anybody a Telegram send is impossible.
52
+
53
+ ## The seven methods it will not call
40
54
 
41
55
  Each refusal names the tool that owns the capability instead.
42
56
 
@@ -45,6 +59,41 @@ Each refusal names the tool that owns the capability instead.
45
59
  | `setWebhook`, `deleteWebhook` | Webhook state is registered on the one path the maxy edge admits, stamped with the bot id. A raw call silently repoints or stops every inbound message | `telegram-webhook-register` |
46
60
  | `getUpdates` | Telegram's two ways of receiving updates are mutually exclusive, and this one does not work while a webhook is set | The webhook already delivers inbound |
47
61
  | `logOut`, `close` | Both move the bot off the cloud Bot API server | Nothing. Do not do this |
62
+ | `getManagedBotToken`, `replaceManagedBotToken` | The result IS a live bot token. Relaying it puts a credential in a tool result, and therefore in the transcript | `telegram-bot-provision`, which persists tokens and returns only bot ids |
63
+
64
+ `getManagedBotAccessSettings` and `setManagedBotAccessSettings` are NOT refused: they return no
65
+ credential, so refusing them would remove a capability while protecting nothing. Prefer
66
+ `telegram-bot-provision action=access` anyway, which logs the change.
67
+
68
+ ## Creating a bot without BotFather
69
+
70
+ `telegram-bot-provision` has four actions.
71
+
72
+ | Action | What it does |
73
+ |---|---|
74
+ | `offer` | Sends a one-tap button into a private chat. The person taps it, names the bot, and this install receives it, stores its token, restricts it to its owner and registers its webhook |
75
+ | `rotate` | Revokes a managed bot's token, saves the replacement, and re-registers the webhook at the URL it was already on |
76
+ | `access` | Reads the access settings when `isAccessRestricted` is omitted; writes them when it is given. Up to ten `addedUserIds` besides the owner |
77
+ | `list` | This account's bots by id and role. Never a token |
78
+
79
+ ```
80
+ telegram-bot-provision botId=<managing-bot> action=offer chatId=<private-chat>
81
+ suggestedName="Front Desk" suggestedUsername="frontdesk_bot"
82
+ ```
83
+
84
+ Three things to know before using it:
85
+
86
+ - The managing bot must have management of other bots enabled **once**, in the @BotFather Mini App.
87
+ No API call turns it on. `offer` checks `getMe().can_manage_bots` and refuses with the Mini App
88
+ named, so a button that would do nothing is never sent.
89
+ - The button is a REPLY keyboard and Telegram makes it **private chats only**. A group or channel
90
+ id will not work.
91
+ - A provisioned bot lands as `role:'unbound'` and answers nobody until an agent is bound to it. It
92
+ is restricted to its owner on Telegram's side at the same time.
93
+
94
+ Telegram sends no `request_id` back and no event discriminator, so a creation and a token change
95
+ are told apart only by whether the account already holds the bot id, and the log correlates on
96
+ `newBotId` rather than on the request id.
48
97
 
49
98
  ## Five things people actually build
50
99
 
@@ -166,6 +215,21 @@ form needs forum topic mode enabled on the bot: `sendMessage`'s `message_thread_
166
215
  working "for forum supergroups and private chats of bots with forum topic mode enabled only", so
167
216
  without it the topic exists and nothing can be sent into it.
168
217
 
218
+ **A message from a topic is answered in that topic, and you do not arrange it.** The route reads the
219
+ topic off the inbound and threads it through the reply, so your turn's final text lands where the
220
+ question was asked. You only pass `messageThreadId` yourself when you are sending into a topic
221
+ nobody just wrote in.
222
+
223
+ **A checklist cannot go into a topic.** `sendChecklist` is the one send method with no
224
+ `message_thread_id` in its table. `telegram-ask` refuses that pair before the network call rather
225
+ than dropping the field and letting the checklist land in the group's General thread, which used to
226
+ read as the call having worked. Send it without `messageThreadId`, or use a poll or a plain message
227
+ for the topic itself.
228
+
229
+ **Only a real topic counts.** Telegram also stamps `message_thread_id` on an ordinary reply in a
230
+ supergroup, so the route keys on `is_topic_message` instead. A plain reply is answered in the
231
+ conversation, not pulled into a thread.
232
+
169
233
  ### 8. Asking a question, or sending a place
170
234
 
171
235
  `telegram-ask` covers the structured sends. A poll takes its answers as plain strings:
@@ -200,6 +264,29 @@ connection. Pass one explicitly only to override that. With nothing stored the t
200
264
  the operator step, which means the answer is to ask the operator to connect the bot, not to hunt for
201
265
  a parameter.
202
266
 
267
+ **A customer messaging the connected business account reaches you in their own conversation, not the
268
+ operator's.** Once the operator connects the bot under Telegram, Settings, Business, Chatbots, every
269
+ message in that account's private chats arrives here as a turn of its own. Four things follow.
270
+
271
+ The turn is a **stranger's**, so it runs as the entry's public agent and carries none of the
272
+ operator's standing rules, whatever role the bot itself holds. The two conversations never share a
273
+ session even when the same person is in both.
274
+
275
+ Every reply **quotes the connection**, and you do not supply it: `telegram-card` and the turn's own
276
+ reply fill it in from the stored connection. Telegram routes the message as the business account
277
+ rather than as the bot, and there is no variant without it.
278
+
279
+ The **24-hour window is a hard limit**. `BusinessBotRights.can_reply` grants the right to answer only
280
+ "in the private chats that had incoming messages in the last 24 hours". A send past that is refused
281
+ here before it is attempted, and the refusal names the window. Nothing is emitted when a window
282
+ closes, so a customer you leave unanswered simply becomes unreachable with no error anywhere. Answer
283
+ while you can.
284
+
285
+ An edit from the customer **changes the message you already have** rather than arriving as a new one.
286
+ It still raises a turn, because a customer who rewrites their question has asked something new; what
287
+ it does not do is leave you two messages to reconcile. A deletion marks the message deleted rather
288
+ than removing it, so what was withdrawn stays readable, and it raises no turn at all.
289
+
203
290
  **A business connection and Telegram Premium are two separate gates, and they govern different
204
291
  things.** Telegram states them apart, so do not treat one as the other. Bot API 9.1 (3 July 2025)
205
292
  added `sendChecklist` and `editMessageChecklist` "allowing bots to send a checklist on behalf of a
@@ -266,7 +353,8 @@ sends group messages visible to a single user.
266
353
 
267
354
  ## What arrives from Telegram
268
355
 
269
- Telegram defines twenty-six update classes. This install registers fourteen and handles ten of them.
356
+ Telegram defines twenty-six update classes. This install registers twenty-two and handles seventeen
357
+ of them.
270
358
 
271
359
  | Class | Registered | What happens |
272
360
  |---|---|---|
@@ -280,9 +368,24 @@ Telegram defines twenty-six update classes. This install registers fourteen and
280
368
  | `subscription` | yes | a turn naming the state (`canceled`, `active`, `failed`) |
281
369
  | `my_chat_member` | yes | logged only, no turn: in a DM this is a block or an unblock |
282
370
  | `business_connection` | yes | recorded so `business_connection_id` can be supplied |
283
- | `business_message`, `edited_business_message`, `deleted_business_messages` | yes | registered and stored; no turn is raised from them yet |
371
+ | `business_message` | yes | a turn, in the customer's own session |
372
+ | `edited_business_message` | yes | a turn, and it patches the message rather than adding one |
373
+ | `deleted_business_messages` | yes | marks the stored rows deleted, no turn |
374
+ | `chat_member` | yes | logged only, no turn: another member joined, left or was removed |
375
+ | `chat_join_request` | yes | logged only, no turn |
376
+ | `message_reaction_count` | yes | logged against the message, no turn: the anonymous tally, as against `message_reaction` which names the reactor |
377
+ | `chat_boost`, `removed_chat_boost` | yes | logged only, no turn |
378
+ | `channel_post`, `edited_channel_post` | yes | a turn, attributed to the channel, when the channel is allowlisted |
284
379
  | everything else | no | does not arrive |
285
380
 
381
+ None of `chat_member`, `chat_join_request`, `message_reaction_count` or the two boost classes raises
382
+ a turn, and none takes an access decision: they are things that happened, with nobody addressing the
383
+ bot, so there is nobody to admit or refuse.
384
+
385
+ **A channel post has no author.** Telegram documents `Message.from` as "may be empty for messages
386
+ sent to channels", so the turn names the channel by its title and there is no person in it. Do not
387
+ address a channel post as if somebody wrote it.
388
+
286
389
  **A reaction may never arrive in a DM.** Telegram documents `message_reaction` as requiring the bot
287
390
  to be an administrator in the chat, and a private chat has no administrator. The class is registered
288
391
  and the handler exists; if nothing ever arrives from a DM, reactions are group-only here.
@@ -293,8 +396,13 @@ Naming **any** class in `allowed_updates` replaces Telegram's default set. Teleg
293
396
  every class except `chat_member`, `message_reaction` and `message_reaction_count` — so a class that
294
397
  works today and is missing from the list stops arriving, with no error in any log.
295
398
 
399
+ Those same three are the ones outside the default, so a bot registered before they were added
400
+ receives none of them until it re-registers. That is not a failure anything reports: the bot simply
401
+ never sees a reaction or a membership change.
402
+
296
403
  The list lives in exactly one place, `TELEGRAM_ALLOWED_UPDATES` in
297
- `plugins/telegram/mcp/src/lib/telegram.ts`. Add a class there and nowhere else, and re-register the
404
+ `platform/lib/telegram-managed-bot/` (re-exported from
405
+ `plugins/telegram/mcp/src/lib/telegram.ts`). Add a class there and nowhere else, and re-register the
298
406
  webhook: `allowed_updates` is set at registration, so an existing bot keeps the list it was
299
407
  registered with. `telegram-webhook-register` prints
300
408
  `op=webhook-allowed … match=yes|no` against what `getWebhookInfo` reports back; `match=no` means
@@ -303,6 +411,36 @@ Telegram registered something narrower than was asked for.
303
411
  The registration test asserts the whole array, not that a particular name is in it, because the
304
412
  failure it guards against is a name silently dropped.
305
413
 
414
+ ## Sending to a channel or a group
415
+
416
+ Every send whose chat id you choose is checked before it reaches Telegram. A chat id beginning
417
+ `-100` is a channel or a supergroup — Telegram gives the two the same id shape and nothing local can
418
+ tell them apart — and it is refused unless the operator listed it in that bot's `allowChannels` or
419
+ `allowGroups`. The refusal names the id and both fields, and nothing is sent.
420
+
421
+ This is a refusal, not a retry. A channel post reaches every subscriber and cannot be unsent, so the
422
+ check happens before the network call rather than after Telegram's answer. If you are refused, say
423
+ so plainly and name the id; do not try another tool, and do not reach for `telegram-api` to route
424
+ around it, because the same check applies there on `params.chat_id`.
425
+
426
+ Both lists live on public bot entries only. An admin, specialist or unbound bot reaches no channel
427
+ and no group, in either direction.
428
+
429
+ A private chat id (positive) and a basic group id (negative, no `-100`) are unaffected.
430
+
431
+ ## Answering a comment on a channel post
432
+
433
+ Telegram builds a comment thread by auto-forwarding each channel post into the channel's linked
434
+ discussion group. That forwarded copy is the thread root, and it is not a message anybody sent you:
435
+ it raises no turn, because the post itself already reached you through the channel.
436
+
437
+ A comment arrives as an ordinary group message whose turn carries the root's message id. Answer by
438
+ replying beneath that root, not at the bottom of the group, or the answer reads to everyone in the
439
+ thread as no answer at all.
440
+
441
+ A commenter who posted anonymously has no personal identity. The turn names the group they commented
442
+ as, which is the only identity Telegram supplies.
443
+
306
444
  ## Reading the method list
307
445
 
308
446
  `references/methods.md` carries all 185 names grouped as Telegram documents them: Getting updates,
@@ -18,12 +18,30 @@ A finished Telegram setup has all of these, and the skill is not done until each
18
18
  - The bot is stored as one entry in `account.json`'s `telegram.bots` array (under the brand install, never `~/.claude/`). The `telegram-webhook-register` tool writes it there and logs `[telegram-setup] op=token-write dir=… brand=…`.
19
19
  - The webhook is registered against a URL whose path is `/api/telegram/` (the only path the maxy edge admits). The register tool refuses any other path and logs `op=webhook-register path=… botId=… account=… admitted=false` when it does.
20
20
  - The registered URL carries `bot=<botId>`, the bot's own Telegram id. The register tool derives it from the token and appends it, so you never type it. The receiver requires it and rejects `reason=unknown-bot`; the bot id is what determines the account, so no `account=` is carried.
21
- - The bot's `role` is stated, not inferred: `admin` or `public`. It selects the spawn path, so it is a privilege decision.
22
- - Access policy is set **on that entry**: an admin bot carries the operator's numeric Telegram user IDs (`adminUsers`); a public bot carries a DM policy (and an allowlist when the policy is `allowlist`). Nothing is shared with the account's other bots.
23
- - A public bot names the `agent` it answers as, and that agent is active. An admin bot names none: it reads `agents/admin` by construction.
21
+ - The bot's `role` is stated, not inferred: `admin`, `public` or `specialist`. It selects the spawn path, so it is a privilege decision. (A bot that arrived through the one-tap path lands as `unbound`, which answers nobody; giving it a role is what finishes it.)
22
+ - Access policy is set **on that entry**: an admin bot carries the operator's numeric Telegram user IDs (`adminUsers`); a public bot carries a DM policy (and an allowlist when the policy is `allowlist`); a specialist bot carries `allowFrom` and nothing else, because there is no open policy for that role. Nothing is shared with the account's other bots.
23
+ - A public bot names the `agent` it answers as, and that agent is active. A specialist bot names the `specialist` card it runs as, and the account has that card. An admin bot names neither: it reads `agents/admin` by construction.
24
+ - If the bot is to work in a **group**, it is a public bot (never an admin or specialist bot), its group privacy mode is on in BotFather before registration, and every group it answers in is listed by chat id on that entry (`groupPolicy: 'allowlist'`, `allowGroups`). An admin bot cannot be used in a group at all.
25
+ - If the bot is to work in a **channel**, it is a public bot for the same reason, it is a channel administrator with post rights, and every channel it reads or posts to is listed by chat id on that entry (`channelPolicy: 'allowlist'`, `allowChannels`). The list gates both directions: a channel not on it is neither read nor posted to. An admin bot cannot be used in a channel at all, and it can no longer post to any group or channel either, in either direction.
24
26
 
25
27
  An account may hold any number of bots. Registering, re-pointing or removing one never disturbs another, in any account.
26
28
 
29
+ ## Two ways in, and the first one is preferred
30
+
31
+ **The one-tap path.** If this account already has a working admin bot, a new bot needs no BotFather
32
+ visit and no token typed anywhere. Call `telegram-bot-provision action=offer` with that admin bot's
33
+ id and the operator's private chat id. They tap the button, name the bot, and this install receives
34
+ it, stores its token, restricts it to its owner and registers its webhook by itself. What arrives is
35
+ a `role:'unbound'` entry that answers nobody; binding it to an agent is the only step left.
36
+
37
+ That path needs the managing bot to have management of other bots enabled **once**, in the
38
+ @BotFather Mini App. No API call turns it on. `offer` checks first and refuses with the Mini App
39
+ named rather than sending a button that would do nothing.
40
+
41
+ **The BotFather path**, below, is still the way in for the first bot on an install, and for a bot
42
+ the operator already owns. Everything after Step 1 applies to it alone: a provisioned bot has
43
+ already had Steps 1, 3, 4 and 5 done for it.
44
+
27
45
  ## Flow
28
46
 
29
47
  Read `references/setup-guide.md` (cited throughout this flow) with the `plugin-read` tool (`file="references/setup-guide.md"`), which resolves it at the plugin root. A plain `Read` against this skill's base directory misses, because the file lives one level up at the plugin root, not under the skill.
@@ -32,12 +50,17 @@ Read `references/setup-guide.md` (cited throughout this flow) with the `plugin-r
32
50
 
33
51
  Follow `references/setup-guide.md` "Creating a Bot": the operator creates the bot via @BotFather and gives you the token (e.g. `123456:ABC-DEF…`). Confirm you have it before continuing.
34
52
 
53
+ Skip this step entirely when the account already has a working admin bot: use `telegram-bot-provision action=offer` instead, and no token is ever handled by anyone.
54
+
35
55
  ### Step 2 — Decide the role, and for a public bot its agent
36
56
 
37
57
  Ask which role this bot serves (see `references/setup-guide.md` "Admin Bot vs Public Bot"). The role is stated by the operator, never worked out from anything else: it decides which spawn path the bot's messages take.
38
58
 
39
59
  - **Admin bot** — only the numeric Telegram user IDs the operator names for THIS bot may message it; everything else is denied. Collect those IDs (`references/setup-guide.md` "Finding Your Telegram User ID"). It answers as the admin agent.
40
60
  - **Public bot** — customer-facing; choose a DM policy (`open`, `allowlist`, or `disabled`), for `allowlist` the allowed numeric IDs, and **which of this account's agents answers on it**. Call `account-manage` to list the account's agents and let the operator pick one. The agent must be active; the register tool refuses an inactive or unknown slug.
61
+ - **Specialist bot** — a bot bound to one of the account's own specialist cards, so messaging it runs that specialist with its own prompt and its own tools instead of the admin seat. Collect two things: **which card** (the file name under the account's `specialists/agents/`, without `.md` — that is the name the spawn resolves, not the card's title) and **who may message it** (numeric IDs). Both are required: there is no open policy for this role, because the session carries the card's own tool surface rather than the public zero-tool ringfence.
62
+
63
+ If the operator wants this bot in a **group**, say plainly that groups are a public-bot feature. An admin bot added to a group answers nobody: it would otherwise put the operator's own agent, with the operator's own standing rules and tools, into a room other people read, and every member's messages into its context. There is no setting that changes this.
41
64
 
42
65
  The account may already hold other bots. This one is added alongside them and shares nothing with them.
43
66
 
@@ -54,15 +77,62 @@ The register tool aborts on any other path, so do not invent `/channels/telegram
54
77
  Call `telegram-webhook-register` with:
55
78
  - `botToken` — the BotFather token
56
79
  - `webhookUrl` — the URL from Step 3
57
- - `role` — `admin` or `public`
80
+ - `role` — `admin`, `public` or `specialist`
58
81
  - admin bot: `adminUsers` — the numeric IDs from Step 2
59
82
  - public bot: `agent` — the slug from Step 2, plus `dmPolicy` and, for `allowlist`, `allowFrom`
83
+ - specialist bot: `specialist` — the card file name from Step 2, plus `allowFrom` (both required)
84
+
85
+ On success the tool has, in this order: verified the token, derived the bot id, set the webhook with a generated secret (written to `~/.<brand>/telegram-secrets/<botId>`), probed Telegram and confirmed no delivery error since the registration, and only then persisted the entry into `account.json`'s `telegram.bots` array. It logs `op=webhook-register admitted=true` + `op=webhook-verify reachable=true` + `op=token-write`. It replaces only the entry with this bot's id, so the account's other bots are untouched. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`), a webhook Telegram cannot reach (`op=webhook-verify reachable=false`), a public bot with no or an inactive `agent` (`op=token-write-rejected reason=no-agent|agent-inactive`), a specialist bot with no card, an unknown card or an empty allowlist (`op=token-write-rejected reason=no-specialist|specialist-unknown|no-allowlist`), or a token write blocked outside the brand install.
86
+
87
+ ### Step 4a — Groups, when the bot is to work in one
88
+
89
+ Only for a public bot, and only if the operator asked for it. Skip this step entirely otherwise.
90
+
91
+ The outcome is that an allowlisted group can address the bot and gets an answer in that group, and that the bot reads nothing else said in the room.
92
+
93
+ Three things have to be true, and the register tool has already checked the first:
94
+
95
+ - **Group privacy mode is ON in BotFather.** With it off the bot receives every message anyone types in every group it joins. `telegram-webhook-register` refuses such a bot outright and logs `[telegram-webhook] op=group-preflight … canReadAllGroupMessages=true verdict=refused`, naming the BotFather step. With it on, Telegram delivers only commands, @mentions and replies to the bot's own messages, which is why no further "was I being addressed" rule exists anywhere in the code.
96
+ - **The bot is in the group.** A group admin adds it. The install cannot do this and does not try.
97
+ - **The group's chat id is on the entry.** Set `groupPolicy: 'allowlist'` and put the numeric chat id in `allowGroups`. A supergroup id is negative and long, e.g. `-1001234567890`. Until the id is listed, the bot answers nobody in that room and logs `op=group-refused … reason=not-in-allowGroups`.
98
+
99
+ The chat is what is admitted, not its members: once a group is allowlisted, anyone in it can address the bot, including people on no `allowFrom` list. Say that to the operator in those words before they allowlist a room. `dmPolicy` and `allowFrom` govern one-to-one chats only and are never consulted for a group.
100
+
101
+ A group is one conversation, not one per member: everyone in the room shares a single thread and a single session, and each turn names who spoke.
102
+
103
+ ## Setting a bot up in a channel
104
+
105
+ The outcome is that a post in an allowlisted channel reaches the agent, that the agent can post to
106
+ that channel and no other, and that comments on a post can be attributed and answered.
107
+
108
+ Three things have to be true:
109
+
110
+ - **The bot is a channel administrator, with post rights.** A channel admin adds it and grants
111
+ "Post Messages". The install cannot do this and does not try. Without the post right the bot still
112
+ reads posts but every send fails, which from the agent's side is indistinguishable from a channel
113
+ nobody reads.
114
+ - **The channel's chat id is on the entry.** Set `channelPolicy: 'allowlist'` and put the numeric
115
+ chat id in `allowChannels`. A channel id is negative and long, e.g. `-1001234567890`. Until the id
116
+ is listed, no post from that channel reaches the agent (`op=channel-refused …
117
+ reason=not-in-allowChannels`) and no send to it leaves the install.
118
+ - **For comments, the channel has a linked discussion group,** and that group is allowlisted too
119
+ (`groupPolicy`, `allowGroups`). Telegram builds comment threads by auto-forwarding each post into
120
+ the linked group; without the link there is nothing to comment on and nothing to answer in.
121
+
122
+ The list gates **both directions**. That is the difference from a group, and it is deliberate: a
123
+ channel post reaches every subscriber and cannot be unsent, so a send to a channel outside
124
+ `allowChannels` is refused before it reaches Telegram, and the refusal names both list fields.
125
+
126
+ Say this to the operator in these words before they allowlist a channel: the agent will be able to
127
+ post to it, and a post cannot be taken back.
60
128
 
61
- On success the tool has, in this order: verified the token, derived the bot id, set the webhook with a generated secret (written to `~/.<brand>/telegram-secrets/<botId>`), probed Telegram and confirmed no delivery error since the registration, and only then persisted the entry into `account.json`'s `telegram.bots` array. It logs `op=webhook-register admitted=true` + `op=webhook-verify reachable=true` + `op=token-write`. It replaces only the entry with this bot's id, so the account's other bots are untouched. If it returns an error, read it back to the operator verbatim — the common ones are a wrong path (`admitted=false`), a webhook Telegram cannot reach (`op=webhook-verify reachable=false`), a public bot with no or an inactive `agent` (`op=token-write-rejected reason=no-agent|agent-inactive`), or a token write blocked outside the brand install.
129
+ A channel post has no author. Telegram sends no sender for it, so the turn names the channel by its
130
+ title. A comment does have an author, unless the person commented anonymously, in which case the
131
+ turn names the group they commented as.
62
132
 
63
133
  ### Step 5 — Confirm
64
134
 
65
- Tell the operator the bot is connected, which role it has, and that messaging the bot now reaches the agent and a reply returns. For a public bot, name the agent that will respond. If the account holds other bots, say that this one is separate from them: its own people, its own agent, its own conversations.
135
+ Tell the operator the bot is connected, which role it has, and that messaging the bot now reaches the agent and a reply returns. For a public bot, name the agent that will respond; for a specialist bot, name the card and say the conversation stays the same one across days. If the account holds other bots, say that this one is separate from them: its own people, its own agent, its own conversations.
66
136
 
67
137
  ## Failure modes (state these loudly, do not paper over)
68
138
 
@@ -76,6 +146,17 @@ Tell the operator the bot is connected, which role it has, and that messaging th
76
146
  - **One bot id claimed by two accounts** — inbound is refused `[telegram-inbound] op=reject reason=duplicate-bot-id`, and the standing `[telegram-audit] op=duplicate-bot-id` names it every tick. Two accounts hold the same token. Remove it from the one that should not have it; nothing picks between them.
77
147
  - **Chat id claimed by two bots** — a scheduled dispatch logs `[schedule-inject] op=telegram-destination-ambiguous … dispatched=no`. Two bots, in one account or two, list the same numeric ID in their `adminUsers`. Remove it from the one that should not have it; the dispatch refuses rather than guessing which bot the person should hear from.
78
148
  - **A secret file with no bot, or a bot with no secret file** — `[telegram-audit] op=secret-orphan botId=… side=file|entry`. Neither shows up any other way. `side=entry` means that bot cannot receive anything until it re-registers; `side=file` is left-over state from a bot that has been removed.
149
+ - **Group privacy mode off at registration** — register returns an error naming BotFather's Group Privacy setting and logs `op=group-preflight … verdict=refused`. Nothing was registered and nothing written. Turn privacy on, then register again.
150
+ - **The bot cannot be added to groups** — the same refusal, naming BotFather's "Allow Groups?" setting instead.
151
+ - **Admin bot in a group** — `[telegram-inbound] op=group-refused … reason=admin-entry-no-groups`, and no turn runs. This is not a misconfiguration to fix; the admin persona never enters a group. Use a public bot for the room.
152
+ - **Group not allowlisted** — `op=group-refused … reason=not-in-allowGroups` (the id is missing from `allowGroups`) or `reason=group-policy-disabled` (the entry has no `groupPolicy: 'allowlist'` at all). Both are silent to everyone in the room, so check the log rather than assuming the bot is broken.
153
+ - **Privacy mode turned off AFTER registration** — nothing fails and no update says so; the bot simply starts reading every message in the room. The hourly `[telegram-audit] op=group-census … privacyOff=<n>` is the only thing that reports it. `privacyOff>0` needs the BotFather setting turned back on.
154
+ - **Bot removed from a group** — every later send fails and no inbound arrives again. `op=chat-member-group … status=kicked` catches it live; if the install was down when it happened, the hourly `op=group-census … absent=<n>` is what surfaces it. Read `bots=` and `groups=` on that line before trusting `absent=0`: zero absent out of zero groups is an install with no groups, not a healthy one.
155
+ - **Admin or specialist bot in a channel** — `[telegram-inbound] op=channel-refused … reason=admin-entry-no-channels` (or `specialist-entry-no-channels`, or `unbound-entry-no-channels`), and no turn runs. Not a misconfiguration to fix; those personas never enter a channel. Use a public bot.
156
+ - **Channel not allowlisted** — `op=channel-refused … reason=not-in-allowChannels` (the id is missing from `allowChannels`) or `reason=channel-policy-disabled` (the entry has no `channelPolicy: 'allowlist'` at all). Both are silent to every subscriber, so read the log rather than assuming the bot is broken. The same two states also refuse every send, logged as `[telegram-outbound] op=channel-send … allowed=no reason=not-in-allowChannels`.
157
+ - **Agent says it cannot post to a channel or group** — the id is on neither `allowChannels` nor `allowGroups`. Both lists exist on public entries only, so an admin, specialist or unbound bot can never post to any channel or group. The refusal names the id and both fields.
158
+ - **Bot loses its post rights, or is demoted in a channel** — every later send fails one at a time and nothing announces it. The hourly `[telegram-audit] op=channel-census … admin=<n> notAdmin=<n> noPostRight=<n>` is the only thing that reports it. `noPostRight>0` means the bot is still an administrator but cannot post; `notAdmin>0` means it is no longer an administrator at all. Read `bots=` and `channels=` before trusting either zero: zero out of zero channels is an install with no channels, not a healthy one.
159
+ - **Comments arrive but answers land in the wrong place** — the reply must sit beneath the auto-forwarded root post, whose id the turn carries. `op=channel-comment-root … rootMessageId=<id>` is the record of that id; a thread with no root line is a thread whose comments point nowhere, which means the discussion group is not linked or not allowlisted.
79
160
 
80
161
  ## Language
81
162
 
@@ -12,6 +12,18 @@ The agent has **no tool to initiate a WhatsApp message to an arbitrary number.**
12
12
 
13
13
  What the agent **can** still do: reply within an existing chat. **The `reply` and `reply-document` tools are retired**; the agent no longer chooses to deliver. Its answer is the text its turn ends with, read from the session record and sent by the gateway, split into separate messages on any line that is exactly `[[next]]`. A scheduled turn delivers the same way, so a scheduled turn with nothing to report must end with no text at all. Files go through `SendUserFile`, caught by a read-only JSONL follower on the session and funnelled through `sendWhatsAppDocument`, the one send core that owns path validation, the 100 MB ceiling, the canonical log, and per-file reconciliation. Delivery stays reply-only by construction: the follower runs only for an admin session and its recipient is fixed when the session spawns, so there is no recipient the agent can name. (The removed `whatsapp-send-document` route tool and the bridge path `SendUserFile` → `file-delivery-bridge.ts` → `POST /api/whatsapp/send-document` are the legacy webchat/email bridge surface, not the native WhatsApp channel.) Replying within your chats is what WhatsApp permits; starting new chats from a linked device is what it penalises.
14
14
 
15
+ The operator-facing half of this rule now also lives in the admin identity, under its `## Channel reality` section. This page loads only with the WhatsApp skill, and on 2026-08-09 an admin turn that had not loaded it filled the gap from model priors and offered a cold send three times. The identity states which channels can start a conversation and with whom; this page stays the authority on why WhatsApp cannot. A change to either belongs in both.
16
+
17
+ ## The channel-suggestion clause
18
+
19
+ The platform arm of this door's turn wrapper — `REFRAME_INSTRUCTION` in `platform/services/whatsapp-channel/src/notification.ts`, the body of a turn the operator authored from their own handset or the scheduler — ends with a clause telling the agent that when the turn wanted something this door cannot do (a button, an in-place card, a poll or checklist, a map pin, a photo or album, an invoice, a reply in a group, an edit to a message already sent, or a message to someone not already registered) it names the missing thing, says Telegram has it, and offers to set it up. It is conditioned on the turn: when this door served the request, the agent never raises another door, which is what stops it becoming a nag without needing a per-sender counter.
20
+
21
+ It exists because Telegram's own wrapper gained a paragraph naming its surfaces and no counterpart was possible here, so on the weaker door the agent answered in prose and never said a better door existed.
22
+
23
+ **WhatsApp-only, and it offers no counterweight.** A public visitor's turn takes the account arm and never reads this constant. The two twins' tests assert the clause's absence, so copying it across fails there. Three counterweights were drafted and each was struck on evidence: there is no outbound reach on this door at all and the paired number is a dedicated line no contact already holds; Telegram runs the same whisper wrapper, so the voice-note transcript is not this door's alone; and both doors gate inbound on the same `dmPolicy`. A clause that invents an advantage here is the defect it exists to close, and its test asserts all three are absent.
24
+
25
+ **How to see it.** The `[reframe] op=applied` line carries `arm=<platform|account|webchat>` and `suggestClause=<bool>`, both read back off the composed body rather than off the branch the composer meant to take. `arm=platform suggestClause=false` means the constant shipped without the clause; `arm=account suggestClause=true`, or any `suggestClause=true` on a webchat line, is a leak. Whether it ever converts anyone has no event of its own, so the hourly `[channel-suggest-census]` counts the state instead: `accountsWaOnly` flat over weeks while `waOperatorTurns` climbs is the signal. It is a proxy and never proof of causation — an operator who reads the clause and decides against Telegram is a correct outcome that moves the number not at all.
26
+
15
27
  ## Per-account observe socket (account manager)
16
28
 
17
29
  Each sub-account can pair its own WhatsApp number as a linked device, separate from the house socket. This is intended for an account manager's phone: the agent reads that WhatsApp, but the phone's inbound is never treated as instructions to the agent.
@@ -43,15 +43,46 @@ export interface ReconcileDeps {
43
43
  * account has settings reports hooks-missing=0 and reads as fenced.
44
44
  *
45
45
  * The matcher is compared for equality, which is the shape
46
- * provision-account-dir.sh writes for all five. A settings file that reaches the
47
- * same effect another way — a matcher-less catch-all entry, or a regex
48
- * alternation — reads as missing here. That is intended: what this audits is
49
- * whether the account carries the provisioned fence, and drift from the template
50
- * is worth a line even when it happens to be equivalent.
46
+ * provision-account-dir.sh writes for the five tool rows. A settings file that
47
+ * reaches the same effect another way — a matcher-less catch-all entry, or a
48
+ * regex alternation — reads as missing here. That is intended: what this audits
49
+ * is whether the account carries the provisioned fence, and drift from the
50
+ * template is worth a line even when it happens to be equivalent.
51
+ *
52
+ * A row with a null matcher inverts that test to "no matcher key at all", which
53
+ * is the shape account-settings.json writes for every UserPromptSubmit hook. The
54
+ * rule is the same in both directions: match the template exactly.
51
55
  */
52
56
  export declare function hookRegistration(accountDir: string): {
53
57
  missing: string[];
54
58
  } | null;
59
+ /**
60
+ * Per-account count of the entity folders and their child folders.
61
+ *
62
+ * The archive exception widened the guard's depth rule, and a rule that is too
63
+ * wide produces NO event: a successful write emits nothing, so an entity folder
64
+ * quietly growing a second subtree on some route is invisible. `stray` above zero
65
+ * is that signature.
66
+ *
67
+ * `entities` rides the same line because `stray=0` alone cannot tell a clean
68
+ * account from a walker that read nothing: `entities=0` on an account that holds
69
+ * entity folders is the not-measuring reading, and the two are indistinguishable
70
+ * without the denominator.
71
+ *
72
+ * Directories only; files are not counted. Dot-prefixed entries are skipped at
73
+ * both levels, because SCHEMA.md's name rule exempts them at every level and the
74
+ * write guard skips them too: counting one as a stray would raise a standing
75
+ * alarm about an entry the rules permit.
76
+ *
77
+ * A bucket that does not exist, or a child that cannot be stat'd, contributes
78
+ * nothing rather than throwing: the caller already swallows per-account errors,
79
+ * and a partial count is worth more than a dropped line.
80
+ */
81
+ export declare function entitySubdirs(accountDir: string): {
82
+ entities: number;
83
+ archive: number;
84
+ stray: number;
85
+ };
55
86
  /** One reconcile pass over every account under the accounts root: one op=strays
56
87
  * line naming the unaccounted-for entries of each account that has any, then the
57
88
  * single summary line. An account is any child dir with an account.json (the
@@ -1 +1 @@
1
- {"version":3,"file":"account-dir-schema-reconcile.d.ts","sourceRoot":"","sources":["../src/account-dir-schema-reconcile.ts"],"names":[],"mappings":"AA8EA,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD;;;;;0CAKsC;IACtC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;+DAE+D;AAC/D,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,eAAe,EAClB,WAAW,SAAI,EACf,aAAa,SAAI,EACjB,UAAU,SAAI,EACd,YAAY,SAAI,EAChB,cAAc,SAAI,GACjB,MAAM,CAIR;AAED;;8EAE8E;AAC9E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAcpE;AA+ED;;;8EAG8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAoCpE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAqED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CA6BjF;AAED;;;;;;;;;;;;;iCAaiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CA6EtD;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;oDACoD;AACpD,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3C,yBAAyB,CAiB3B"}
1
+ {"version":3,"file":"account-dir-schema-reconcile.d.ts","sourceRoot":"","sources":["../src/account-dir-schema-reconcile.ts"],"names":[],"mappings":"AA8EA,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD;;;;;0CAKsC;IACtC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;+DAE+D;AAC/D,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,eAAe,EAClB,WAAW,SAAI,EACf,aAAa,SAAI,EACjB,UAAU,SAAI,EACd,YAAY,SAAI,EAChB,cAAc,SAAI,GACjB,MAAM,CAIR;AAED;;8EAE8E;AAC9E,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAcpE;AA+ED;;;8EAG8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,CAoCpE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AA8ED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAgCjF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAuCtG;AAED;;;;;;;;;;;;;iCAaiC;AACjC,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAsFtD;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED;oDACoD;AACpD,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,aAAa,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3C,yBAAyB,CAiB3B"}
@@ -284,15 +284,22 @@ function agentParity(accountDir) {
284
284
  const stillOnDisk = disabled.filter((n) => existsSync(join(accountDir, 'specialists', 'agents', n)));
285
285
  return { disabled: disabled.length, stillOnDisk };
286
286
  }
287
- /** The hook entries that fence an account's file writes: the path guard on each
288
- * path-bearing tool, and the Bash snapshot pair. Each row is
289
- * [settings event, matcher, hook filename]. */
287
+ /** The hook entries that fence an account's file writes and carry its standing
288
+ * filing directive: the path guard on each path-bearing tool, the Bash snapshot
289
+ * pair, and the per-turn directive. Each row is
290
+ * [settings event, matcher, hook filename].
291
+ *
292
+ * A null matcher means the template writes the entry with NO matcher key, which
293
+ * is how account-settings.json registers every UserPromptSubmit hook. An entry
294
+ * that carries one is drift from the template and is reported, on the same rule
295
+ * the five tool rows follow. */
290
296
  const REQUIRED_HOOKS = [
291
297
  ['PreToolUse', 'Write', 'fs-schema-guard.sh'],
292
298
  ['PreToolUse', 'Edit', 'fs-schema-guard.sh'],
293
299
  ['PreToolUse', 'NotebookEdit', 'fs-schema-guard.sh'],
294
300
  ['PreToolUse', 'Bash', 'fs-schema-guard-bash-pre.sh'],
295
301
  ['PostToolUse', 'Bash', 'fs-schema-guard-bash-post.sh'],
302
+ ['UserPromptSubmit', null, 'preference-consult-directive.sh'],
296
303
  ];
297
304
  /**
298
305
  * Task 2196 — which of the required file-write fences this account's settings do
@@ -308,11 +315,15 @@ const REQUIRED_HOOKS = [
308
315
  * account has settings reports hooks-missing=0 and reads as fenced.
309
316
  *
310
317
  * The matcher is compared for equality, which is the shape
311
- * provision-account-dir.sh writes for all five. A settings file that reaches the
312
- * same effect another way — a matcher-less catch-all entry, or a regex
313
- * alternation — reads as missing here. That is intended: what this audits is
314
- * whether the account carries the provisioned fence, and drift from the template
315
- * is worth a line even when it happens to be equivalent.
318
+ * provision-account-dir.sh writes for the five tool rows. A settings file that
319
+ * reaches the same effect another way — a matcher-less catch-all entry, or a
320
+ * regex alternation — reads as missing here. That is intended: what this audits
321
+ * is whether the account carries the provisioned fence, and drift from the
322
+ * template is worth a line even when it happens to be equivalent.
323
+ *
324
+ * A row with a null matcher inverts that test to "no matcher key at all", which
325
+ * is the shape account-settings.json writes for every UserPromptSubmit hook. The
326
+ * rule is the same in both directions: match the template exactly.
316
327
  */
317
328
  export function hookRegistration(accountDir) {
318
329
  let raw;
@@ -336,18 +347,93 @@ export function hookRegistration(accountDir) {
336
347
  : undefined;
337
348
  const found = Array.isArray(entries) && entries.some((e) => {
338
349
  const entry = e;
339
- if (entry.matcher !== matcher)
350
+ // A null matcher requires the ABSENCE of the key, not any value.
351
+ if (matcher === null ? entry.matcher !== undefined : entry.matcher !== matcher)
340
352
  return false;
341
353
  return Array.isArray(entry.hooks) && entry.hooks.some((h) => {
342
354
  const cmd = h.command;
343
355
  return typeof cmd === 'string' && cmd.includes(hookFile);
344
356
  });
345
357
  });
358
+ // A matcher-less row is named by its event, which is the only handle the
359
+ // operator has for it.
346
360
  if (!found)
347
- missing.push(`${matcher}:${hookFile}`);
361
+ missing.push(`${matcher ?? event}:${hookFile}`);
348
362
  }
349
363
  return { missing };
350
364
  }
365
+ /**
366
+ * Per-account count of the entity folders and their child folders.
367
+ *
368
+ * The archive exception widened the guard's depth rule, and a rule that is too
369
+ * wide produces NO event: a successful write emits nothing, so an entity folder
370
+ * quietly growing a second subtree on some route is invisible. `stray` above zero
371
+ * is that signature.
372
+ *
373
+ * `entities` rides the same line because `stray=0` alone cannot tell a clean
374
+ * account from a walker that read nothing: `entities=0` on an account that holds
375
+ * entity folders is the not-measuring reading, and the two are indistinguishable
376
+ * without the denominator.
377
+ *
378
+ * Directories only; files are not counted. Dot-prefixed entries are skipped at
379
+ * both levels, because SCHEMA.md's name rule exempts them at every level and the
380
+ * write guard skips them too: counting one as a stray would raise a standing
381
+ * alarm about an entry the rules permit.
382
+ *
383
+ * A bucket that does not exist, or a child that cannot be stat'd, contributes
384
+ * nothing rather than throwing: the caller already swallows per-account errors,
385
+ * and a partial count is worth more than a dropped line.
386
+ */
387
+ export function entitySubdirs(accountDir) {
388
+ let entities = 0;
389
+ let archive = 0;
390
+ let stray = 0;
391
+ for (const bucket of ['projects', 'contacts']) {
392
+ let names;
393
+ try {
394
+ names = readdirSync(join(accountDir, bucket));
395
+ }
396
+ catch {
397
+ continue;
398
+ }
399
+ for (const name of names) {
400
+ if (name.startsWith('.'))
401
+ continue;
402
+ const entityDir = join(accountDir, bucket, name);
403
+ try {
404
+ if (!statSync(entityDir).isDirectory())
405
+ continue;
406
+ }
407
+ catch {
408
+ continue;
409
+ }
410
+ entities++;
411
+ let children;
412
+ try {
413
+ children = readdirSync(entityDir);
414
+ }
415
+ catch {
416
+ continue;
417
+ }
418
+ for (const child of children) {
419
+ if (child.startsWith('.'))
420
+ continue;
421
+ try {
422
+ if (!statSync(join(entityDir, child)).isDirectory())
423
+ continue;
424
+ }
425
+ catch {
426
+ continue;
427
+ }
428
+ if (child === 'archive')
429
+ archive++;
430
+ else
431
+ stray++;
432
+ }
433
+ }
434
+ }
435
+ return { entities, archive, stray };
436
+ }
351
437
  /** One reconcile pass over every account under the accounts root: one op=strays
352
438
  * line naming the unaccounted-for entries of each account that has any, then the
353
439
  * single summary line. An account is any child dir with an account.json (the
@@ -431,6 +517,13 @@ export function runReconcile(deps) {
431
517
  deps.logger(`${TAG} op=hook-registration account=${entry} missing=${reg.missing.join(',')}`);
432
518
  hooksMissing += reg.missing.length;
433
519
  }
520
+ // Emitted for EVERY account on every pass, not only on drift, unlike the
521
+ // four lines above. Those report a fault; this one reports a population,
522
+ // and a population line that appears only when it is non-zero cannot be
523
+ // told from a check that stopped running.
524
+ const subdirs = entitySubdirs(accountDir);
525
+ deps.logger(`${TAG} op=entity-subdirs account=${entry} entities=${subdirs.entities} ` +
526
+ `archive=${subdirs.archive} stray=${subdirs.stray}`);
434
527
  total.strayTopLevel += c.strayTopLevel;
435
528
  total.overDeep += c.overDeep;
436
529
  total.badName += c.badName;