@rubytech/create-maxy-code 0.1.581 → 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 (453) 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 +181 -9
  30. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +17 -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 +20 -4
  47. package/payload/platform/plugins/docs/references/calendar-booking.md +2 -0
  48. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +15 -0
  49. package/payload/platform/plugins/docs/references/telegram-guide.md +143 -4
  50. package/payload/platform/plugins/filesystem/PLUGIN.md +2 -2
  51. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.d.ts +2 -0
  52. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.d.ts.map +1 -0
  53. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.js +108 -0
  54. package/payload/platform/plugins/filesystem/mcp/dist/lib/__tests__/account-schema.test.js.map +1 -0
  55. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.d.ts +17 -0
  56. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.d.ts.map +1 -0
  57. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.js +162 -0
  58. package/payload/platform/plugins/filesystem/mcp/dist/lib/account-schema.js.map +1 -0
  59. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.d.ts +2 -0
  60. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.d.ts.map +1 -0
  61. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.js +116 -0
  62. package/payload/platform/plugins/filesystem/mcp/dist/tools/__tests__/schema-fence.test.js.map +1 -0
  63. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.d.ts.map +1 -1
  64. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js +9 -0
  65. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-edit.js.map +1 -1
  66. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts.map +1 -1
  67. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js +10 -0
  68. package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js +64 -1
  70. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-origin-gate.test.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts +5 -0
  72. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js +28 -6
  74. package/payload/platform/plugins/memory/mcp/dist/lib/label-origin-gate.js.map +1 -1
  75. package/payload/platform/plugins/memory/references/schema-knowledge-work.md +1 -1
  76. package/payload/platform/plugins/scheduling/PLUGIN.md +19 -3
  77. package/payload/platform/plugins/scheduling/mcp/dist/index.js +7 -2
  78. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  79. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js +53 -6
  80. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate-audits.test.js.map +1 -1
  81. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js +38 -0
  82. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/gate.test.js.map +1 -1
  83. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-uid-stamp.test.d.ts +2 -0
  84. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-uid-stamp.test.d.ts.map +1 -0
  85. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-uid-stamp.test.js +102 -0
  86. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-uid-stamp.test.js.map +1 -0
  87. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js +43 -9
  88. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js.map +1 -1
  89. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +50 -0
  90. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  91. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +47 -1
  92. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  93. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts +5 -3
  94. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.d.ts.map +1 -1
  95. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js +18 -3
  96. package/payload/platform/plugins/scheduling/mcp/dist/lib/gate.js.map +1 -1
  97. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +40 -0
  98. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -1
  99. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +132 -41
  100. package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -1
  101. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +14 -1
  102. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  103. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +61 -1
  104. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  105. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +258 -8
  106. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  107. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-trash-filter.test.d.ts +2 -0
  108. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-trash-filter.test.d.ts.map +1 -0
  109. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-trash-filter.test.js +67 -0
  110. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-trash-filter.test.js.map +1 -0
  111. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +206 -3
  112. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  113. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -1
  114. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +8 -1
  115. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -1
  116. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +29 -21
  117. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  118. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +94 -14
  119. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  120. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.d.ts.map +1 -1
  121. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js +6 -1
  122. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-get.js.map +1 -1
  123. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -1
  124. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +7 -2
  125. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -1
  126. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  127. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +46 -3
  128. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  129. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +30 -0
  130. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  131. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +69 -6
  132. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  133. package/payload/platform/plugins/telegram/PLUGIN.md +49 -1
  134. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-census.test.js +3 -3
  135. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js +28 -2
  136. package/payload/platform/plugins/telegram/mcp/dist/__tests__/api-guard.test.js.map +1 -1
  137. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-business-connection.test.d.ts +2 -0
  138. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-business-connection.test.d.ts.map +1 -0
  139. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-business-connection.test.js +140 -0
  140. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-business-connection.test.js.map +1 -0
  141. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-tool.test.js +93 -5
  142. package/payload/platform/plugins/telegram/mcp/dist/__tests__/ask-tool.test.js.map +1 -1
  143. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.d.ts +2 -0
  144. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.d.ts.map +1 -0
  145. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.js +326 -0
  146. package/payload/platform/plugins/telegram/mcp/dist/__tests__/bot-provision-tool.test.js.map +1 -0
  147. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.d.ts +2 -0
  148. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.d.ts.map +1 -0
  149. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.js +173 -0
  150. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-business.test.js.map +1 -0
  151. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-tool.test.js +79 -0
  152. package/payload/platform/plugins/telegram/mcp/dist/__tests__/card-tool.test.js.map +1 -1
  153. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.d.ts +2 -0
  154. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.d.ts.map +1 -0
  155. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.js +34 -0
  156. package/payload/platform/plugins/telegram/mcp/dist/__tests__/chat-send-policy.test.js.map +1 -0
  157. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.d.ts +2 -0
  158. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.d.ts.map +1 -0
  159. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.js +93 -0
  160. package/payload/platform/plugins/telegram/mcp/dist/__tests__/reach-tool.test.js.map +1 -0
  161. package/payload/platform/plugins/telegram/mcp/dist/__tests__/thread-tool.test.js +15 -1
  162. package/payload/platform/plugins/telegram/mcp/dist/__tests__/thread-tool.test.js.map +1 -1
  163. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js +245 -0
  164. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-register.test.js.map +1 -1
  165. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.js +10 -1
  166. package/payload/platform/plugins/telegram/mcp/dist/__tests__/webhook-verdict.test.js.map +1 -1
  167. package/payload/platform/plugins/telegram/mcp/dist/index.js +26 -4
  168. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  169. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts +19 -0
  170. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.d.ts.map +1 -1
  171. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js +41 -1
  172. package/payload/platform/plugins/telegram/mcp/dist/lib/account-token.js.map +1 -1
  173. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts +15 -20
  174. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.d.ts.map +1 -1
  175. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js +27 -32
  176. package/payload/platform/plugins/telegram/mcp/dist/lib/account-write.js.map +1 -1
  177. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts +13 -0
  178. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.d.ts.map +1 -1
  179. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js +22 -1
  180. package/payload/platform/plugins/telegram/mcp/dist/lib/agent-active.js.map +1 -1
  181. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.d.ts.map +1 -1
  182. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js +6 -0
  183. package/payload/platform/plugins/telegram/mcp/dist/lib/api-census.js.map +1 -1
  184. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts +1 -1
  185. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.d.ts.map +1 -1
  186. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js +22 -2
  187. package/payload/platform/plugins/telegram/mcp/dist/lib/api-guard.js.map +1 -1
  188. package/payload/platform/plugins/telegram/mcp/dist/lib/business-connection-store.d.ts +38 -0
  189. package/payload/platform/plugins/telegram/mcp/dist/lib/business-connection-store.d.ts.map +1 -0
  190. package/payload/platform/plugins/telegram/mcp/dist/lib/business-connection-store.js +56 -0
  191. package/payload/platform/plugins/telegram/mcp/dist/lib/business-connection-store.js.map +1 -0
  192. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.d.ts +55 -0
  193. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.d.ts.map +1 -0
  194. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.js +116 -0
  195. package/payload/platform/plugins/telegram/mcp/dist/lib/business-window-store.js.map +1 -0
  196. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.d.ts +54 -0
  197. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.d.ts.map +1 -0
  198. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.js +90 -0
  199. package/payload/platform/plugins/telegram/mcp/dist/lib/chat-send-policy.js.map +1 -0
  200. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts +2 -28
  201. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.d.ts.map +1 -1
  202. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js +8 -53
  203. package/payload/platform/plugins/telegram/mcp/dist/lib/telegram.js.map +1 -1
  204. package/payload/platform/plugins/telegram/mcp/dist/tools/api.d.ts.map +1 -1
  205. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js +19 -0
  206. package/payload/platform/plugins/telegram/mcp/dist/tools/api.js.map +1 -1
  207. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.d.ts.map +1 -1
  208. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.js +79 -10
  209. package/payload/platform/plugins/telegram/mcp/dist/tools/ask.js.map +1 -1
  210. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.d.ts +64 -0
  211. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.d.ts.map +1 -0
  212. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.js +217 -0
  213. package/payload/platform/plugins/telegram/mcp/dist/tools/bot-provision.js.map +1 -0
  214. package/payload/platform/plugins/telegram/mcp/dist/tools/card.d.ts +7 -0
  215. package/payload/platform/plugins/telegram/mcp/dist/tools/card.d.ts.map +1 -1
  216. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js +56 -0
  217. package/payload/platform/plugins/telegram/mcp/dist/tools/card.js.map +1 -1
  218. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.d.ts.map +1 -1
  219. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js +15 -0
  220. package/payload/platform/plugins/telegram/mcp/dist/tools/invoice.js.map +1 -1
  221. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.d.ts.map +1 -1
  222. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.js +12 -0
  223. package/payload/platform/plugins/telegram/mcp/dist/tools/media-send.js.map +1 -1
  224. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.d.ts +22 -0
  225. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.d.ts.map +1 -0
  226. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.js +67 -0
  227. package/payload/platform/plugins/telegram/mcp/dist/tools/reach.js.map +1 -0
  228. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.d.ts +21 -2
  229. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.d.ts.map +1 -1
  230. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.js +47 -2
  231. package/payload/platform/plugins/telegram/mcp/dist/tools/schemas.js.map +1 -1
  232. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.d.ts.map +1 -1
  233. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.js +12 -0
  234. package/payload/platform/plugins/telegram/mcp/dist/tools/thread.js.map +1 -1
  235. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts +4 -1
  236. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.d.ts.map +1 -1
  237. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js +73 -6
  238. package/payload/platform/plugins/telegram/mcp/dist/tools/webhook-register.js.map +1 -1
  239. package/payload/platform/plugins/telegram/skills/build/SKILL.md +217 -11
  240. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +87 -6
  241. package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +12 -0
  242. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +36 -5
  243. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  244. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +103 -10
  245. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  246. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  247. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +2 -0
  248. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  249. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  250. package/payload/platform/services/claude-session-manager/dist/http-server.js +42 -4
  251. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  252. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  253. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +19 -5
  254. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  255. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +9 -0
  256. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  257. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +5 -1
  258. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  259. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  260. package/payload/platform/services/telegram-channel/dist/instructions.js +13 -0
  261. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  262. package/payload/platform/services/telegram-channel/dist/notification.d.ts +40 -0
  263. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  264. package/payload/platform/services/telegram-channel/dist/notification.js +41 -4
  265. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  266. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +52 -0
  267. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  268. package/payload/platform/services/whatsapp-channel/dist/notification.js +64 -1
  269. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  270. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  271. package/payload/platform/services/whatsapp-channel/dist/server.js +14 -2
  272. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  273. package/payload/platform/templates/account-schema/SCHEMA.md +17 -10
  274. package/payload/platform/templates/agents/admin/IDENTITY.md +5 -0
  275. package/payload/platform/templates/specialists/agents/data-manager.md +2 -2
  276. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  277. package/payload/server/{chunk-BFRB2U75.js → chunk-CV6AEMKH.js} +1 -0
  278. package/payload/server/{manager-CTVTF4YB.js → manager-FQSQSSJ3.js} +1 -1
  279. package/payload/server/public/activity.html +6 -6
  280. package/payload/server/public/agents.html +5 -5
  281. package/payload/server/public/assets/{AdminLoginScreens-B1lu4-_F.js → AdminLoginScreens-BLsDYWVi.js} +1 -1
  282. package/payload/server/public/assets/AdminLoginScreens-BLsDYWVi.js.br +0 -0
  283. package/payload/server/public/assets/AdminLoginScreens-BLsDYWVi.js.gz +0 -0
  284. package/payload/server/public/assets/{AdminShell-BMslfZxN.js → AdminShell-BZS8jzFA.js} +1 -1
  285. package/payload/server/public/assets/AdminShell-BZS8jzFA.js.br +0 -0
  286. package/payload/server/public/assets/AdminShell-BZS8jzFA.js.gz +0 -0
  287. package/payload/server/public/assets/{activity-kQ3w70LJ.js → activity-DOjvoLdd.js} +1 -1
  288. package/payload/server/public/assets/activity-DOjvoLdd.js.br +0 -0
  289. package/payload/server/public/assets/activity-DOjvoLdd.js.gz +0 -0
  290. package/payload/server/public/assets/{admin-CkASgYA7.js → admin-JrSNg29g.js} +1 -1
  291. package/payload/server/public/assets/admin-JrSNg29g.js.br +0 -0
  292. package/payload/server/public/assets/admin-JrSNg29g.js.gz +0 -0
  293. package/payload/server/public/assets/{agents-Cn_bZgnD.js → agents--9WMJVLC.js} +1 -1
  294. package/payload/server/public/assets/agents--9WMJVLC.js.br +0 -0
  295. package/payload/server/public/assets/agents--9WMJVLC.js.gz +0 -0
  296. package/payload/server/public/assets/{browser-BE-GDk5-.js → browser-DiiMmTrM.js} +1 -1
  297. package/payload/server/public/assets/browser-DiiMmTrM.js.br +0 -0
  298. package/payload/server/public/assets/browser-DiiMmTrM.js.gz +0 -0
  299. package/payload/server/public/assets/{calendar-Di7BEHHN.js → calendar-CazmCzgO.js} +1 -1
  300. package/payload/server/public/assets/calendar-CazmCzgO.js.br +0 -0
  301. package/payload/server/public/assets/calendar-CazmCzgO.js.gz +0 -0
  302. package/payload/server/public/assets/chat-CB0TcyXX.js +1 -0
  303. package/payload/server/public/assets/chat-CB0TcyXX.js.br +0 -0
  304. package/payload/server/public/assets/chat-CB0TcyXX.js.gz +0 -0
  305. package/payload/server/public/assets/chevron-left-0tVo23Dh.js +1 -0
  306. package/payload/server/public/assets/chevron-left-0tVo23Dh.js.br +0 -0
  307. package/payload/server/public/assets/clock-D2cZ0sge.js +1 -0
  308. package/payload/server/public/assets/clock-D2cZ0sge.js.br +0 -0
  309. package/payload/server/public/assets/clock-D2cZ0sge.js.gz +0 -0
  310. package/payload/server/public/assets/{copy-nkxspu2J.js → copy-C87QIN_o.js} +1 -1
  311. package/payload/server/public/assets/copy-C87QIN_o.js.br +0 -0
  312. package/payload/server/public/assets/copy-C87QIN_o.js.gz +0 -0
  313. package/payload/server/public/assets/data-CK7AMxPt.js +1 -0
  314. package/payload/server/public/assets/data-CK7AMxPt.js.br +0 -0
  315. package/payload/server/public/assets/data-CK7AMxPt.js.gz +0 -0
  316. package/payload/server/public/assets/{field-2rIXxGMt.js → field-C3kQgGWE.js} +1 -1
  317. package/payload/server/public/assets/field-C3kQgGWE.js.br +0 -0
  318. package/payload/server/public/assets/field-C3kQgGWE.js.gz +0 -0
  319. package/payload/server/public/assets/{file-text-CdSqrscg.js → file-text-rWowl9RN.js} +1 -1
  320. package/payload/server/public/assets/file-text-rWowl9RN.js.br +0 -0
  321. package/payload/server/public/assets/file-text-rWowl9RN.js.gz +0 -0
  322. package/payload/server/public/assets/{graph-BfBHHXaC.js → graph-CVJKz1Uo.js} +1 -1
  323. package/payload/server/public/assets/graph-CVJKz1Uo.js.br +0 -0
  324. package/payload/server/public/assets/graph-CVJKz1Uo.js.gz +0 -0
  325. package/payload/server/public/assets/{graph-labels-CHW2aBke.js → graph-labels-kneGnsHB.js} +1 -1
  326. package/payload/server/public/assets/graph-labels-kneGnsHB.js.br +0 -0
  327. package/payload/server/public/assets/graph-labels-kneGnsHB.js.gz +0 -0
  328. package/payload/server/public/assets/{jsx-runtime-D43jgaWP.css → jsx-runtime-rMaiYjNu.css} +1 -1
  329. package/payload/server/public/assets/jsx-runtime-rMaiYjNu.css.br +0 -0
  330. package/payload/server/public/assets/{jsx-runtime-D43jgaWP.css.gz → jsx-runtime-rMaiYjNu.css.gz} +0 -0
  331. package/payload/server/public/assets/{operator-DRn9LITC.js → operator-BGvB4hu4.js} +1 -1
  332. package/payload/server/public/assets/operator-BGvB4hu4.js.br +0 -0
  333. package/payload/server/public/assets/operator-BGvB4hu4.js.gz +0 -0
  334. package/payload/server/public/assets/{page-D_9mPS6W.js → page-DBQLOasI.js} +1 -1
  335. package/payload/server/public/assets/page-DBQLOasI.js.br +0 -0
  336. package/payload/server/public/assets/page-DBQLOasI.js.gz +0 -0
  337. package/payload/server/public/assets/{page-BwqQsoU8.js → page-DyVAu7tm.js} +1 -1
  338. package/payload/server/public/assets/page-DyVAu7tm.js.br +0 -0
  339. package/payload/server/public/assets/page-DyVAu7tm.js.gz +0 -0
  340. package/payload/server/public/assets/play-DgP7vl0r.js +1 -0
  341. package/payload/server/public/assets/play-DgP7vl0r.js.br +0 -0
  342. package/payload/server/public/assets/play-DgP7vl0r.js.gz +0 -0
  343. package/payload/server/public/assets/{public-CrjZYpdW.js → public-BvUQR3hd.js} +1 -1
  344. package/payload/server/public/assets/public-BvUQR3hd.js.br +0 -0
  345. package/payload/server/public/assets/public-BvUQR3hd.js.gz +0 -0
  346. package/payload/server/public/assets/{rotate-ccw-J9f-GWjm.js → rotate-ccw-Dnn7gbPh.js} +1 -1
  347. package/payload/server/public/assets/rotate-ccw-Dnn7gbPh.js.br +0 -0
  348. package/payload/server/public/assets/rotate-ccw-Dnn7gbPh.js.gz +0 -0
  349. package/payload/server/public/assets/{routines-C1AB-3FR.js → routines-N8NmxPOZ.js} +1 -1
  350. package/payload/server/public/assets/routines-N8NmxPOZ.js.br +0 -0
  351. package/payload/server/public/assets/routines-N8NmxPOZ.js.gz +0 -0
  352. package/payload/server/public/assets/{skills-C4EfNKEx.js → skills-B2CRgO-U.js} +1 -1
  353. package/payload/server/public/assets/skills-B2CRgO-U.js.br +0 -0
  354. package/payload/server/public/assets/skills-B2CRgO-U.js.gz +0 -0
  355. package/payload/server/public/assets/{tasks-DszD3aZl.js → tasks-8QLGtlSj.js} +1 -1
  356. package/payload/server/public/assets/tasks-8QLGtlSj.js.br +0 -0
  357. package/payload/server/public/assets/tasks-8QLGtlSj.js.gz +0 -0
  358. package/payload/server/public/assets/{triangle-alert-B5WAyAkC.js → triangle-alert-el772yUQ.js} +1 -1
  359. package/payload/server/public/assets/triangle-alert-el772yUQ.js.br +0 -0
  360. package/payload/server/public/assets/triangle-alert-el772yUQ.js.gz +0 -0
  361. package/payload/server/public/assets/{useCopyFeedback-B2nuNNJw.js → useCopyFeedback-DfNAu8hQ.js} +1 -1
  362. package/payload/server/public/assets/useCopyFeedback-DfNAu8hQ.js.br +0 -0
  363. package/payload/server/public/assets/useCopyFeedback-DfNAu8hQ.js.gz +0 -0
  364. package/payload/server/public/assets/{useMediaQuery-DUPJPHQt.js → useMediaQuery-B469O83-.js} +1 -1
  365. package/payload/server/public/assets/useMediaQuery-B469O83-.js.br +0 -0
  366. package/payload/server/public/assets/useMediaQuery-B469O83-.js.gz +0 -0
  367. package/payload/server/public/assets/useVoiceRecorder-Bb-51cJv.js +2 -0
  368. package/payload/server/public/assets/useVoiceRecorder-Bb-51cJv.js.br +0 -0
  369. package/payload/server/public/assets/useVoiceRecorder-Bb-51cJv.js.gz +0 -0
  370. package/payload/server/public/assets/{wrench-DZJZm17j.js → wrench-Ck4phkdB.js} +1 -1
  371. package/payload/server/public/assets/wrench-Ck4phkdB.js.br +0 -0
  372. package/payload/server/public/assets/wrench-Ck4phkdB.js.gz +0 -0
  373. package/payload/server/public/browser.html +5 -5
  374. package/payload/server/public/calendar.html +8 -8
  375. package/payload/server/public/chat.html +14 -14
  376. package/payload/server/public/data.html +12 -12
  377. package/payload/server/public/field.html +3 -3
  378. package/payload/server/public/graph.html +10 -10
  379. package/payload/server/public/index.html +14 -14
  380. package/payload/server/public/operator.html +16 -16
  381. package/payload/server/public/public.html +14 -14
  382. package/payload/server/public/routines.html +8 -8
  383. package/payload/server/public/skills.html +6 -6
  384. package/payload/server/public/tasks.html +7 -7
  385. package/payload/server/server.js +3777 -1258
  386. package/payload/server/public/assets/AdminLoginScreens-B1lu4-_F.js.br +0 -0
  387. package/payload/server/public/assets/AdminLoginScreens-B1lu4-_F.js.gz +0 -0
  388. package/payload/server/public/assets/AdminShell-BMslfZxN.js.br +0 -0
  389. package/payload/server/public/assets/AdminShell-BMslfZxN.js.gz +0 -0
  390. package/payload/server/public/assets/activity-kQ3w70LJ.js.br +0 -0
  391. package/payload/server/public/assets/activity-kQ3w70LJ.js.gz +0 -0
  392. package/payload/server/public/assets/admin-CkASgYA7.js.br +0 -0
  393. package/payload/server/public/assets/admin-CkASgYA7.js.gz +0 -0
  394. package/payload/server/public/assets/agents-Cn_bZgnD.js.br +0 -0
  395. package/payload/server/public/assets/agents-Cn_bZgnD.js.gz +0 -0
  396. package/payload/server/public/assets/browser-BE-GDk5-.js.br +0 -0
  397. package/payload/server/public/assets/browser-BE-GDk5-.js.gz +0 -0
  398. package/payload/server/public/assets/calendar-Di7BEHHN.js.br +0 -0
  399. package/payload/server/public/assets/calendar-Di7BEHHN.js.gz +0 -0
  400. package/payload/server/public/assets/chat-CKyMT5Ai.js +0 -1
  401. package/payload/server/public/assets/chat-CKyMT5Ai.js.br +0 -1
  402. package/payload/server/public/assets/chat-CKyMT5Ai.js.gz +0 -0
  403. package/payload/server/public/assets/chevron-left-Id9VAZAz.js +0 -1
  404. package/payload/server/public/assets/chevron-left-Id9VAZAz.js.br +0 -0
  405. package/payload/server/public/assets/clock-DKpWWJbJ.js +0 -1
  406. package/payload/server/public/assets/clock-DKpWWJbJ.js.br +0 -0
  407. package/payload/server/public/assets/copy-nkxspu2J.js.br +0 -0
  408. package/payload/server/public/assets/copy-nkxspu2J.js.gz +0 -0
  409. package/payload/server/public/assets/data-1c33cAHp.js +0 -1
  410. package/payload/server/public/assets/data-1c33cAHp.js.br +0 -2
  411. package/payload/server/public/assets/data-1c33cAHp.js.gz +0 -0
  412. package/payload/server/public/assets/field-2rIXxGMt.js.br +0 -0
  413. package/payload/server/public/assets/field-2rIXxGMt.js.gz +0 -0
  414. package/payload/server/public/assets/file-text-CdSqrscg.js.br +0 -0
  415. package/payload/server/public/assets/file-text-CdSqrscg.js.gz +0 -0
  416. package/payload/server/public/assets/graph-BfBHHXaC.js.br +0 -0
  417. package/payload/server/public/assets/graph-BfBHHXaC.js.gz +0 -0
  418. package/payload/server/public/assets/graph-labels-CHW2aBke.js.br +0 -0
  419. package/payload/server/public/assets/graph-labels-CHW2aBke.js.gz +0 -0
  420. package/payload/server/public/assets/jsx-runtime-D43jgaWP.css.br +0 -0
  421. package/payload/server/public/assets/operator-DRn9LITC.js.br +0 -0
  422. package/payload/server/public/assets/operator-DRn9LITC.js.gz +0 -0
  423. package/payload/server/public/assets/page-BwqQsoU8.js.br +0 -0
  424. package/payload/server/public/assets/page-BwqQsoU8.js.gz +0 -0
  425. package/payload/server/public/assets/page-D_9mPS6W.js.br +0 -0
  426. package/payload/server/public/assets/page-D_9mPS6W.js.gz +0 -0
  427. package/payload/server/public/assets/play-7LTxCuLX.js +0 -1
  428. package/payload/server/public/assets/play-7LTxCuLX.js.br +0 -0
  429. package/payload/server/public/assets/play-7LTxCuLX.js.gz +0 -0
  430. package/payload/server/public/assets/public-CrjZYpdW.js.br +0 -0
  431. package/payload/server/public/assets/public-CrjZYpdW.js.gz +0 -0
  432. package/payload/server/public/assets/rotate-ccw-J9f-GWjm.js.br +0 -0
  433. package/payload/server/public/assets/rotate-ccw-J9f-GWjm.js.gz +0 -0
  434. package/payload/server/public/assets/routines-C1AB-3FR.js.br +0 -0
  435. package/payload/server/public/assets/routines-C1AB-3FR.js.gz +0 -0
  436. package/payload/server/public/assets/skills-C4EfNKEx.js.br +0 -0
  437. package/payload/server/public/assets/skills-C4EfNKEx.js.gz +0 -0
  438. package/payload/server/public/assets/tasks-DszD3aZl.js.br +0 -0
  439. package/payload/server/public/assets/tasks-DszD3aZl.js.gz +0 -0
  440. package/payload/server/public/assets/triangle-alert-B5WAyAkC.js.br +0 -4
  441. package/payload/server/public/assets/triangle-alert-B5WAyAkC.js.gz +0 -0
  442. package/payload/server/public/assets/useCopyFeedback-B2nuNNJw.js.br +0 -0
  443. package/payload/server/public/assets/useCopyFeedback-B2nuNNJw.js.gz +0 -0
  444. package/payload/server/public/assets/useMediaQuery-DUPJPHQt.js.br +0 -0
  445. package/payload/server/public/assets/useMediaQuery-DUPJPHQt.js.gz +0 -0
  446. package/payload/server/public/assets/useVoiceRecorder-CCK60d2i.js +0 -2
  447. package/payload/server/public/assets/useVoiceRecorder-CCK60d2i.js.br +0 -0
  448. package/payload/server/public/assets/useVoiceRecorder-CCK60d2i.js.gz +0 -0
  449. package/payload/server/public/assets/wrench-DZJZm17j.js.br +0 -0
  450. package/payload/server/public/assets/wrench-DZJZm17j.js.gz +0 -0
  451. /package/payload/server/public/assets/{jsx-runtime-Cjug_Cyb.js → jsx-runtime-BmNepPEn.js} +0 -0
  452. /package/payload/server/public/assets/{jsx-runtime-Cjug_Cyb.js.br → jsx-runtime-BmNepPEn.js.br} +0 -0
  453. /package/payload/server/public/assets/{jsx-runtime-Cjug_Cyb.js.gz → jsx-runtime-BmNepPEn.js.gz} +0 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Task 2625 — which Telegram chats a registered bot can already message.
3
+ *
4
+ * Telegram does not let a bot open a conversation: the person must message it
5
+ * first (core.telegram.org/bots). So "can I reach X" is answerable only from
6
+ * what has already arrived, and the record of that is the message store the
7
+ * webhook writes. Before this module nothing enumerated it, and the agent
8
+ * refused a send that one telegram-card call would have made.
9
+ *
10
+ * Shared rather than duplicated plugin-side, unlike the live-location and card
11
+ * stores: BOTH callers need this same walk — the MCP tool answers one account on
12
+ * demand, the hourly census counts every account — so duplicating it would mean
13
+ * writing the walk twice and holding it together with a boundary test. The
14
+ * filename rule lives here too, and `ui/app/lib/telegram/message-store.ts`
15
+ * re-exports it, so the store that writes a filename and the walk that reads one
16
+ * cannot disagree.
17
+ *
18
+ * Path-free by construction: the caller passes `storeDir`. The plugin derives it
19
+ * from PLATFORM_ROOT and the server from the brand config dir, and neither
20
+ * derivation belongs in a library that must not know what a brand is.
21
+ */
22
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+
25
+ /**
26
+ * Is this a channel key that can safely become a path segment?
27
+ *
28
+ * Both halves of an admin key are Telegram numeric ids (a bot id is positive; a
29
+ * chat or sender id is an integer and is negative for groups), so requiring
30
+ * exactly that is both the truthful shape check and a complete traversal guard:
31
+ * no `.`, `/` or `\` can survive it.
32
+ *
33
+ * Task 2615 — the business shape `<botId>:biz:<senderId>` joins the guard rather
34
+ * than bypassing it. Both alternatives are anchored and admit only digits, an
35
+ * optional leading minus and the literal `biz`, so neither can carry a `.`, `/`
36
+ * or `\` either.
37
+ */
38
+ export function isStorableChannelKey(channelKey: string): boolean {
39
+ return /^-?\d+:-?\d+$/.test(channelKey) || /^-?\d+:biz:-?\d+$/.test(channelKey);
40
+ }
41
+
42
+ /** Task 2615 — is this the three-part business shape?
43
+ *
44
+ * Its own predicate rather than a `.includes(':biz:')` at each caller: a
45
+ * business conversation is reached through the business-connection send route
46
+ * and its 24-hour window, not by addressing a chat id, so anything that answers
47
+ * "can we message this person now" has to be able to tell the two apart. */
48
+ export function isBusinessChannelKey(channelKey: string): boolean {
49
+ return /^-?\d+:biz:-?\d+$/.test(channelKey);
50
+ }
51
+
52
+ /** The filename stem for a channel key.
53
+ *
54
+ * Throws on a key that is neither two numeric ids nor the business shape.
55
+ * Refusing loudly is correct here: every caller reaches this with a key it has
56
+ * already validated, so an unstorable key means a caller skipped its guard, and
57
+ * silently sanitising it would build a path nobody asked for. */
58
+ export function channelKeyToFileStem(channelKey: string): string {
59
+ if (!isStorableChannelKey(channelKey)) {
60
+ throw new Error(
61
+ `telegram-message-store: refusing unstorable channelKey ${JSON.stringify(channelKey)}`,
62
+ );
63
+ }
64
+ // Task 2615 — every colon, not only the first: a business key has two.
65
+ return channelKey.replaceAll(":", "_");
66
+ }
67
+
68
+ /** The inverse of `channelKeyToFileStem`.
69
+ *
70
+ * Task 2615 — a stem whose second segment is exactly `biz` is a three-part
71
+ * business key. Otherwise only the FIRST `_` is a separator, because both halves
72
+ * of an admin key are numeric Telegram ids and nothing later in that stem is
73
+ * one. */
74
+ export function fileStemToChannelKey(stem: string): string {
75
+ const i = stem.indexOf("_");
76
+ if (i <= 0) return stem;
77
+ const rest = stem.slice(i + 1);
78
+ if (rest.startsWith("biz_")) {
79
+ return `${stem.slice(0, i)}:biz:${rest.slice("biz_".length)}`;
80
+ }
81
+ return `${stem.slice(0, i)}:${rest}`;
82
+ }
83
+
84
+ /** One registered bot, as much of it as reachability needs. `adminUsers` is
85
+ * present on an admin entry only; absent reads as none. */
86
+ export interface ReachBotInput {
87
+ id: string;
88
+ role: string;
89
+ adminUsers?: number[];
90
+ }
91
+
92
+ export interface ReachChat {
93
+ /** Null for an adminUsers entry that has never messaged: there is no chat. */
94
+ chatId: string | null;
95
+ senderId: string;
96
+ /** From the newest inbound row. Null when that row carried no name, and null
97
+ * for a sender who has never messaged. */
98
+ displayName: string | null;
99
+ /** ISO 8601 of the newest inbound row; null means never messaged. */
100
+ lastInbound: string | null;
101
+ /** This sender is an adminUsers entry on this bot. Allowlisted is a DIFFERENT
102
+ * fact from having started the chat, which is why both are carried. */
103
+ admin: boolean;
104
+ /** True exactly when `lastInbound` is non-null. */
105
+ reachable: boolean;
106
+ }
107
+
108
+ export interface ReachBot {
109
+ botId: string;
110
+ role: string;
111
+ adminUsers: number[];
112
+ /** Reachable chats newest first, then never-messaged admins in listed order. */
113
+ chats: ReachChat[];
114
+ }
115
+
116
+ export interface ReachResult {
117
+ bots: ReachBot[];
118
+ /** Message rows this call actually read, across EVERY conversation file in the
119
+ * directory — including files belonging to a bot the caller did not name.
120
+ * That is deliberate: `chats=0` with `storeRows>0` is then the signature of a
121
+ * wrong bot list or a wrong account scope, which is the failure this number
122
+ * exists to expose. */
123
+ storeRows: number;
124
+ }
125
+
126
+ /** The fields of a store row this walk reads. Declared structurally rather than
127
+ * imported from the store: importing would invert the dependency, since the
128
+ * store imports the key helpers from here. */
129
+ interface StoreRowLike {
130
+ kind?: unknown;
131
+ origin?: unknown;
132
+ chatId?: unknown;
133
+ senderId?: unknown;
134
+ senderName?: unknown;
135
+ dateSent?: unknown;
136
+ }
137
+
138
+ function parseLines(raw: string): StoreRowLike[] {
139
+ const out: StoreRowLike[] = [];
140
+ for (const line of raw.split("\n")) {
141
+ if (!line.trim()) continue;
142
+ try {
143
+ out.push(JSON.parse(line) as StoreRowLike);
144
+ } catch {
145
+ // A partial write from a crash mid-append is skipped, never fatal: one
146
+ // corrupt line must not hide every other chat in the account.
147
+ }
148
+ }
149
+ return out;
150
+ }
151
+
152
+ /** Every conversation file's newest inbound PRIVATE row, keyed
153
+ * `<botId>:<senderId>`, plus the message rows read getting there. */
154
+ function scanStore(storeDir: string): {
155
+ newest: Map<
156
+ string,
157
+ { chatId: string; senderId: string; senderName: string | null; dateSent: string }
158
+ >;
159
+ storeRows: number;
160
+ } {
161
+ const newest = new Map<
162
+ string,
163
+ { chatId: string; senderId: string; senderName: string | null; dateSent: string }
164
+ >();
165
+ let storeRows = 0;
166
+ if (!storeDir || !existsSync(storeDir)) return { newest, storeRows };
167
+ let entries: string[];
168
+ try {
169
+ entries = readdirSync(storeDir);
170
+ } catch {
171
+ return { newest, storeRows };
172
+ }
173
+ for (const entry of entries) {
174
+ if (!entry.endsWith(".jsonl")) continue;
175
+ const stem = entry.slice(0, -".jsonl".length);
176
+ const channelKey = fileStemToChannelKey(stem);
177
+ if (!isStorableChannelKey(channelKey)) continue;
178
+ // Task 2615's business conversations are skipped entirely, rows included.
179
+ // A business chat is NOT reached by addressing a chat id: it needs the
180
+ // business-connection send route and its own 24-hour window, which Task 2625
181
+ // put out of scope. Listing one here would report it as sendable with
182
+ // telegram-card, which is the same wrong answer in the opposite direction to
183
+ // the refusal this tool exists to prevent.
184
+ if (isBusinessChannelKey(channelKey)) continue;
185
+ const botId = channelKey.slice(0, channelKey.indexOf(":"));
186
+ let raw: string;
187
+ try {
188
+ raw = readFileSync(join(storeDir, entry), "utf8");
189
+ } catch {
190
+ continue;
191
+ }
192
+ for (const row of parseLines(raw)) {
193
+ // An update row is a patch, not a message: counting it would inflate
194
+ // storeRows and it carries no sender at all.
195
+ if (row.kind === "update") continue;
196
+ storeRows += 1;
197
+ if (row.origin !== "inbound") continue;
198
+ const chatId = typeof row.chatId === "string" ? row.chatId : null;
199
+ const senderId = typeof row.senderId === "string" ? row.senderId : null;
200
+ const dateSent = typeof row.dateSent === "string" ? row.dateSent : null;
201
+ if (chatId === null || senderId === null || dateSent === null) continue;
202
+ // Private chats only. A DM chat id equals the sender id; a group's differs
203
+ // (telegram-gateway.ts:66-68). A group is not a route to a person.
204
+ if (chatId !== senderId) continue;
205
+ const key = `${botId}:${senderId}`;
206
+ const prev = newest.get(key);
207
+ if (prev !== undefined && prev.dateSent.localeCompare(dateSent) >= 0) continue;
208
+ newest.set(key, {
209
+ chatId,
210
+ senderId,
211
+ senderName: typeof row.senderName === "string" ? row.senderName : null,
212
+ dateSent,
213
+ });
214
+ }
215
+ }
216
+ return { newest, storeRows };
217
+ }
218
+
219
+ /** Reachability for one account: every registered bot, the private chats that
220
+ * have messaged it, and the allowlisted people who have not. */
221
+ export function collectTelegramReach(input: {
222
+ storeDir: string;
223
+ bots: ReachBotInput[];
224
+ }): ReachResult {
225
+ const { newest, storeRows } = scanStore(input.storeDir);
226
+ const bots: ReachBot[] = input.bots.map((bot) => {
227
+ const adminUsers = bot.adminUsers ?? [];
228
+ const adminSet = new Set(adminUsers.map((u) => String(u)));
229
+ const chats: ReachChat[] = [];
230
+ const seen = new Set<string>();
231
+ for (const [key, row] of newest) {
232
+ if (key.slice(0, key.indexOf(":")) !== bot.id) continue;
233
+ seen.add(row.senderId);
234
+ chats.push({
235
+ chatId: row.chatId,
236
+ senderId: row.senderId,
237
+ displayName: row.senderName,
238
+ lastInbound: row.dateSent,
239
+ admin: adminSet.has(row.senderId),
240
+ reachable: true,
241
+ });
242
+ }
243
+ chats.sort((a, b) => (b.lastInbound ?? "").localeCompare(a.lastInbound ?? ""));
244
+ for (const user of adminUsers) {
245
+ const senderId = String(user);
246
+ if (seen.has(senderId)) continue;
247
+ // Listed, never messaged. Reported rather than omitted, because "we are
248
+ // allowed to talk to them" and "they have opened the chat" are different
249
+ // facts and only the second one makes a send possible.
250
+ chats.push({
251
+ chatId: null,
252
+ senderId,
253
+ displayName: null,
254
+ lastInbound: null,
255
+ admin: true,
256
+ reachable: false,
257
+ });
258
+ }
259
+ return { botId: bot.id, role: bot.role, adminUsers, chats };
260
+ });
261
+ return { bots, storeRows };
262
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/__tests__"]
9
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ globals: false,
7
+ include: ["src/__tests__/**/*.test.ts"],
8
+ },
9
+ });
@@ -10,8 +10,8 @@
10
10
  "pdf-lib": "^1.17.1"
11
11
  },
12
12
  "scripts": {
13
- "build": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/learning-constants/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
14
- "build:lib": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json",
13
+ "build": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/learning-constants/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json && tsc -p services/claude-session-manager/tsconfig.json && tsc -p services/whatsapp-channel/tsconfig.json && tsc -p services/webchat-channel/tsconfig.json && tsc -p services/telegram-channel/tsconfig.json && NODE_OPTIONS='--max-old-space-size=8192' tsc -b plugins/*/mcp/tsconfig.json",
14
+ "build:lib": "tsc -p lib/account-schema-regions/tsconfig.json && tsc -p lib/account-ingest-token/tsconfig.json && tsc -p lib/models/tsconfig.json && tsc -p lib/agent-slug/tsconfig.json && tsc -p lib/mcp-spawn-tee/tsconfig.json && tsc -p lib/mcp-lifeline/tsconfig.json && tsc -p lib/mcp-eager/tsconfig.json && tsc -p lib/account-enumeration/tsconfig.json && tsc -p lib/account-scope/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/graph-write/tsconfig.json && tsc -p lib/ledger-core/tsconfig.json && tsc -p lib/embed-client/tsconfig.json && tsc -p lib/graph-mcp/tsconfig.json && tsc -p lib/graph-trash/tsconfig.json && tsc -p lib/active-rules/tsconfig.json && tsc -p lib/admin-conversation-purge/tsconfig.json && tsc -p lib/graph-search/tsconfig.json && tsc -p lib/graph-style/tsconfig.json && tsc -p lib/device-url/tsconfig.json && tsc -p lib/brand-templating/tsconfig.json && tsc -p lib/entitlement/tsconfig.json && tsc -p lib/task-secrets/tsconfig.json && tsc -p lib/admins-write/tsconfig.json && tsc -p lib/admin-access-password/tsconfig.json && tsc -p lib/persistent-components/tsconfig.json && tsc -p lib/require-port-env/tsconfig.json && tsc -p lib/aeo-llms-txt-writer/tsconfig.json && tsc -p lib/obsidian-parser/tsconfig.json && tsc -p lib/storage-broker/tsconfig.json && tsc -p lib/routine-templates/tsconfig.json && tsc -p lib/agent-dispatch-rule/tsconfig.json && tsc -p lib/shared-folder/tsconfig.json && tsc -p lib/canonical-instant/tsconfig.json && tsc -p lib/dispatch-read/tsconfig.json && tsc -p lib/dispatch-write/tsconfig.json && tsc -p lib/telegram-media/tsconfig.json && tsc -p lib/telegram-managed-bot/tsconfig.json && tsc -p lib/telegram-reach/tsconfig.json",
15
15
  "gen:canonical-tools": "node scripts/generate-canonical-tool-names.mjs",
16
16
  "build:memory": "tsc -p plugins/memory/mcp/tsconfig.json",
17
17
  "build:contacts": "tsc -p plugins/contacts/mcp/tsconfig.json",
@@ -290,7 +290,7 @@ Tools are available via the `admin` MCP server.
290
290
  - `hooks/post-tool-use-agent.sh` — **PostToolUse hook on `Agent`.** Drains any subagent hook-decision buffers under `~/.maxy-code/logs/hook-decisions/` modified since this parent's previous PostToolUse-Agent fire (cursor file keyed by parent session id), prints one `[hook-propagate]` line per record to stdout — Claude Code attaches the stdout as a `hook_success` attachment on the parent JSONL, making the records grep-queryable from the parent session alone. Rotates consumed buffers to `consumed/`. Emits one `[hook-propagate-census] parentSession=<…> subagentHooksObserved=<N> attachmentsEmitted=<M>` line per fire to stdout and server.log; `N != M` is the propagation regression signal. The companion emitter library `hooks/lib/hook-emit.sh` is sourced by `post-tool-use-agent.sh` and any other hook that records a block decision (4 KB stderr truncation, `truncated=true` set on the record).
291
291
  - `hooks/admin-authoring-observer.sh` — **PostToolUse hook on Write and Edit.** Observation only — never blocks; exits 0 on every path. Fires when the admin agent (not a specialist subagent — gated by `MAXY_SPECIALIST` env) writes or edits a file under `<accountDir>/output/`. Walks the session transcript from the latest real-user turn forward to detect any prior `Task` `tool_use` whose `subagent_type` starts with `specialists:`. Emits one stderr line `[admin-authoring] inline-write path=<rel> priorSpecialistSpawnInTurn=<true|false|unknown>`. A `false` value on a long-form prose file is the regression signal this hook was designed to make visible — the BioSymm proposal session (admin authored a customer-facing proposal inline despite content-producer being installed) is the failure mode this surfaces mechanically. Mechanical enforcement (refuse the write, force a re-spawn) is deferred per the task spec.
292
292
  - `hooks/prompt-optimiser-directive.sh` — **UserPromptSubmit hook.** Injects the standing prompt-optimiser restatement directive plus the per-turn four-rung routing ladder as `additionalContext`: **(1)** delegate to the specialist that owns the deliverable via the Agent tool, where the brief states the outcome plus binding constraints, never lines/anchors/literal text; **(2)** only if none fits, load an admin-usable skill with `skill-load`; **(3)** only if the deliverable recurs and no specialist or skill owns it, author its owning skill via `skill-builder` (or `agent-builder` for a worker agent) and produce through it; **(4)** only if it is a genuine one-off, author inline as the named last resort. Re-emits the full agent roster (`agents/admin/AGENTS.md`) and the full admin-usable skills list (`agents/admin/ADMIN-SKILLS.md`) every turn by reading the two generated files from the account dir (the hook fires with the account dir as cwd); it never walks the plugins tree per turn. Fail-open is **visible**: a missing list logs `[prompt-optimiser] missing=<AGENTS.md\|ADMIN-SKILLS.md> emitting-partial` to stderr and the ladder still injects. The trivial-turn skip (one-word confirmation, slash-command, direct continuation) is unchanged. **Staleness:** `ADMIN-SKILLS.md` is regenerated only by `setup-account.sh`; a plugin add/remove since the last setup leaves the list stale — compare `ADMIN-SKILLS.md` mtime against the newest `SKILL.md` mtime and re-run setup to refresh. The list generator is `platform/scripts/lib/admin-skills-bootstrap.sh`; it logs `[admin-skills] scanned=<N> admin-usable=<M> no-declaration=<K>` (failure signature: `admin-usable=0` while `scanned>0`, or any `missing-declaration` line). The directive also carries a standing CAPABILITY-QUESTIONS-ARE-OWNED-WORK clause (how-to / "do you have instructions for X" / config questions about platform features are answered from the owning specialist or plugin tool/reference, never from training memory), and the hook appends a durable `<ts> [prompt-optimiser-directive] injected len=<n> session=<id>` breadcrumb to `$LOG_DIR/prompt-optimiser-directive.log` so per-turn injection is greppable, not stderr-only. The directive is also **suppressed on native channel turns** — when the parsed `.prompt` starts with the `<channel source=` event marker, the hook logs `[prompt-optimiser-directive] skipped reason=channel-turn session=<id>` to stderr and exits without injecting, because the channel service already reframes the inbound into a select-and-dispatch turn (`composeAdminContent`, see `.docs/whatsapp-inbound-lifeline.md`). Marker-matched at start-of-prompt only, so an admin/Terminal prompt that merely mentions "channel" still gets the directive; fail-open injects if the prompt cannot be parsed.
293
- - `hooks/preference-consult-directive.sh` — **UserPromptSubmit hook.** Injects a standing `<preference-adherence>` block naming the two-layer preference architecture (layer 1 the fixed block already in front of the agent every turn; layer 2 the account's own saved `Preference` records, read with `profile-read`, holding signature policy, header, naming and styling) and two directives: consult the relevant layer-2 preferences with `profile-read` before any customer-facing document deliverable, and store finished deliverables under `documents/` or the project folder because `output/` is scratch of last resort. Prompt-independent and always exit 0 (fail-open). Breadcrumb (stderr): `[pref-wrapper] op=inject`. This is the advisory companion to the `preference-consult-gate` hard gate.
293
+ - `hooks/preference-consult-directive.sh` — **UserPromptSubmit hook.** Injects a standing `<preference-adherence>` block naming the two-layer preference architecture (layer 1 the fixed block already in front of the agent every turn; layer 2 the account's own saved `Preference` records, read with `profile-read`, holding signature policy, header, naming and styling) and two directives: consult the relevant layer-2 preferences with `profile-read` before any customer-facing document deliverable, and store finished deliverables under `documents/` or the project folder because `output/` is scratch of last resort. It also carries the standing filing rules: records and files kept in step unprompted, client or engagement material in that project's folder with the matching project record created in the same pass, a person's own folder under `contacts/` with the record pointing at that copy, `documents/` as account-wide reference against `uploads/` as intake and never a home, an entity folder holding its current version at the top with superseded versions moved into `archive/` beneath rather than deleted, and any create, move or rename finished in the same session with every reference repointed and named briefly in the reply. Because it is a `UserPromptSubmit` hook it reaches all four doors, which is why the filing rules are not duplicated in `IDENTITY.md` or in the four channel reframe literals. Prompt-independent and always exit 0 (fail-open). Breadcrumb, stderr **and** `$LOG_DIR/preference-consult-directive.log`: `<ts> [pref-wrapper] op=inject len=<n> session=<id>`. The log file not growing while sessions run is the failure signature, because a broken directive literal fails the heredoc silently at exit 0 and stderr alone cannot answer the question after the fact. Registration is audited by the standing reconcile as `UserPromptSubmit:preference-consult-directive.sh`. This is the advisory companion to the `preference-consult-gate` hard gate.
294
294
  - `hooks/datetime-inject.sh` — **UserPromptSubmit hook.** Injects a fresh `<datetime>` block as `additionalContext` on every turn (`refresh=per-turn`), so the agent always has the current instant — date, time, timezone, weekday, and the raw UTC ISO — without hand-computing it and without depending on Claude Code's spawn-time "Today's date" line going stale. The instant is computed fresh each turn; the timezone is **not** computed here — it is the operator's own zone, resolved from their `UserProfile.timezone` on the graph once per admin spawn and stamped into `MAXY_ACCOUNT_TZ` (see `platform/services/claude-session-manager/src/account-timezone.ts`). When `MAXY_ACCOUNT_TZ` is empty (a non-admin turn, an unset profile timezone, or a neo4j hiccup at spawn) the block falls back to the server's own zone and labels the source, so a reader can always tell which clock the agent saw. This is the current-time surface the `datetime` skill points at. **Fail-open** on every path (missing `python3` or `node`, any formatting error → exit 0, empty stdout — the turn is never blocked). The hook appends `<ts> [datetime-inject] injected tz=<zone> source=<graph|server-fallback> refresh=per-turn session=<id>` to `$LOG_DIR/datetime-inject.log` so "did the agent have the right time this turn, and from which clock?" is answerable from logs without reproduction.
295
295
  - `hooks/mailbox-inject.sh` — **UserPromptSubmit hook.** Injects a `<mailboxes>` block as `additionalContext` on every turn (`refresh=per-turn`), naming every Outlook mailbox attached to this account and the plugin that holds it, so a session never learns attachment state by polling one mail plugin and reading that plugin's miss as an account-wide fact (the incident it exists for: a WhatsApp session called `email-status`, got the IMAP plugin's miss, and told a customer the account had no access to a mailbox the outlook plugin had read that morning). Reads `$ACCOUNT_DIR/secrets/outlook/` directly as a **read-only observer** — it never writes there, and never performs the rename `MailboxRegistry.migrateLegacy()` does. Signed-in mailboxes come from `mailboxes/<key>/tokens.enc` plus the pre-migration top-level `tokens.enc`, keeping only directories that actually contain a blob (the registry's own gate — the revocation path unlinks the blob and leaves the directory). Delegated mailboxes come from the plaintext `delegated.json`, keyed by normalised address so two keys differing only in case or space collapse to one line exactly as `DelegatedStore.read()` collapses them, with the delegator resolved against the **readable** set exactly as `MailboxRegistry.resolve()` does, so a delegated line never promises a mailbox every `outlook-*` call would refuse. A legacy blob whose `graphUserId` already has a `mailboxes/` directory is skipped rather than listed twice, mirroring the branch of `migrateLegacy()` that unlinks it. Every rendered subject is stripped of control characters and angle brackets and length-bounded, so the block's frame holds by construction rather than by the upstream invariants that currently keep them out. Three fields leave the decrypt — `mail`, `graphUserId`, `refreshTokenExpiry` — and **no token value is ever emitted, logged or written**; the `node` reader carries `2>/dev/null` and try/catches every read and parse, because node prints the entire parsed source text on an uncaught exception. **Fail-open** on every path. **Env asymmetry:** `ACCOUNT_DIR` is stamped unconditionally on the rc-spawn path (`buildRcChildEnv`) but only for admin and specialist spawns on the pty-spawner path, and `LOG_DIR` exists on the rc path only — so the hook resolves the account dir from `$PWD` when the stamp is absent (the spawn cwd is the account dir on both paths) and walks `$LOG_DIR` → `$CLAUDE_SESSION_MANAGER_PERSIST_DIR/logs` → `<accountDir>/logs` for its breadcrumb. **Reach on upgrade:** the provision-time writer re-runs for the house account only, so `reconcile_all_accounts_settings` converges every account against `platform/templates/account-settings.json` — which carries this `UserPromptSubmit` entry — from `setup-account.sh` on every install, one `[settings-reconcile] account=<id> status=…` line per account. (The four per-hook backfill libs this converge replaced have been retired.) The breadcrumb is `<ts> [mailbox-inject] injected account=<id> outlook=<n> delegated=<n> usable=<n> expired=<n> unreadable=<n> degraded=<yes|no> imap=not-read source=disk refresh=per-turn session=<id>` in `<logDir>/mailbox-inject.log`, with `op=skip reason=<no-account-dir|unreadable-source|reader-failed>` and `op=degrade reason=delegated-unreadable` on the degraded paths. `imap=not-read`, never `imap=0`: IMAP mailboxes are not surveyed at all by this hook, and a literal zero in a field shaped like the two measured counts would be read in a later incident review as a census. No address is written to the log, because `/api/admin/logs` serves every account's log dir to any LAN caller without auth. A missing `node` or `python3` exits 0 before the breadcrumb block, so a PATH regression produces **no line at all** rather than a `reason=no-node` line.
296
296
  - `hooks/prompt-optimiser-compliance.sh` — **Stop hook.** After each admin turn, reads the just-finished turn from `transcript_path` and appends `<ts> [prompt-optimiser-compliance] directive-fired no-route-taken session=<id8> prompt="<clip>"` to `$LOG_DIR/prompt-optimiser-directive.log` (and stderr) when the routing directive fired, the prompt was non-trivial (not a slash-command, not a one-word confirmation), and the turn took **no route** — no `Agent` dispatch, no `Skill` load, no `ToolSearch`, no `mcp__*` tool call. This is the standing compliance signal that surfaces the session-`da0b12d4` failure class (agent answers a capability question from memory) as a visible event instead of a silent stale answer. Directive-fired is detected by the marker `PROMPT-OPTIMISER DIRECTIVE` in the turn slice, so it is robust to the CC-version difference in how `UserPromptSubmit` `additionalContext` is recorded (`attachment`/`hook_success` vs `hook_additional_context`). **Known limitation:** "direct continuation of the prior turn" is not detectable from the transcript, so a continuation turn that legitimately needs no route can be flagged; treat the log as a review signal, not a gate. **Fail-open:** no python3, no `transcript_path`, or an unreadable transcript → exit 0, no output. Lives in the same log as the directive breadcrumb, so a single `grep` interleaves "fired" and "no-route" into one per-session timeline; cross-check via `platform/scripts/logs-read.sh <sessionKey>`. This is a lightweight transcript read, not a per-turn spawn (contrast the turn recorder below).
@@ -19,6 +19,9 @@
19
19
  # 15. Write into a sibling dir with no account.json -> ALLOW (not an account)
20
20
  # 16. Symlink under this account resolving into a sibling -> BLOCK cross-account
21
21
  # 17. MultiEdit reaches every fence and the refusal ledger -> BLOCK / record
22
+ # 18. Write to projects/acme/archive/x.pdf (one archive) -> ALLOW
23
+ # 19. Write to contacts/jane-doe/archive/x.pdf -> ALLOW
24
+ # 20. Write to projects/acme/archive/old/x.pdf (deeper) -> BLOCK over-deep
22
25
  set -u
23
26
 
24
27
  HOOK="$(cd "$(dirname "$0")/.." && pwd)/fs-schema-guard.sh"
@@ -107,6 +110,12 @@ run_case "loose document allow" "$(mkenv Write file_path 'documents/a.pdf')"
107
110
  run_case "tool-owned deep allow" "$(mkenv Write file_path 'output/deep/nested/x.png')" 0 ""
108
111
  run_case "over-deep block" "$(mkenv Write file_path 'projects/acme/deploy/sources/a.txt')" 2 "fs-guard. blocked path=projects/acme/deploy/sources/a.txt reason=over-deep"
109
112
  run_case "over-deep one block" "$(mkenv Write file_path 'projects/acme/deploy/a.txt')" 2 "reason=over-deep"
113
+ # archive/ is the ONE permitted child of an entity folder: it holds superseded
114
+ # versions, which is what the per-turn filing directive tells the agent to do.
115
+ # Any other name at depth 4, and anything at depth 5, is still refused.
116
+ run_case "archive project allow" "$(mkenv Write file_path 'projects/acme/archive/quote-v1.pdf')" 0 ""
117
+ run_case "archive contact allow" "$(mkenv Write file_path 'contacts/jane-doe/archive/profile.pdf')" 0 ""
118
+ run_case "archive nested block" "$(mkenv Write file_path 'projects/acme/archive/old/quote.pdf')" 2 "reason=over-deep"
110
119
  run_case "top-level block" "$(mkenv Write file_path 'whim/x.txt')" 2 "fs-guard. blocked path=whim/x.txt reason=top-level"
111
120
  run_case "outside acct allow" "$(mkenv Write file_path '/etc/passwd')" 0 ""
112
121
  run_case "notebook over-deep" "$(mkenv NotebookEdit notebook_path 'projects/acme/deploy/n.ipynb')" 2 "reason=over-deep"
@@ -16,6 +16,32 @@ check '! printf "%s" "$OUT" | grep -q "—"' "no em-dash"
16
16
  OUT_PUB=$(printf '{"session_id":"x","prompt":"hi"}' | MAXY_PUBLIC_ALLOWED_TOOLS='mcp__channel__reply' bash "$HOOK" 2>/dev/null); RC_PUB=$?
17
17
  check '[ "$RC_PUB" = "0" ]' "public turn exits 0"
18
18
  check '[ -z "$OUT_PUB" ]' "public turn emits nothing on stdout"
19
+
20
+ # The six filing clauses. The hook is the only surface that reaches all four
21
+ # doors, so a clause dropped here is a clause no admin turn carries.
22
+ check 'printf "%s" "$OUT" | grep -q "in step"' "clause a: records and files in step"
23
+ check 'printf "%s" "$OUT" | grep -q "project.s folder"' "clause b: engagement material in the project folder"
24
+ check 'printf "%s" "$OUT" | grep -q "contacts/"' "clause c: a person gets a contacts folder"
25
+ check 'printf "%s" "$OUT" | grep -q "intake tray"' "clause d: uploads is intake, never a home"
26
+ check 'printf "%s" "$OUT" | grep -q "archive/"' "clause e: superseded versions move to archive"
27
+ check 'printf "%s" "$OUT" | grep -q "indistinguishable"' "clause f: finish and name the move"
28
+ check 'printf "%s" "$OUT" | python3 -c "import json,sys; json.load(sys.stdin)"' "valid JSON envelope"
29
+
30
+ # Durable breadcrumb: the stderr line alone cannot answer "did the directive fire
31
+ # on this turn" after the fact, and a broken literal fails the heredoc silently at
32
+ # exit 0. The log file not growing while sessions run is that failure's signature.
33
+ LOGD=$(mktemp -d)
34
+ printf '{"session_id":"sess-abc","prompt":"hi"}' | LOG_DIR="$LOGD" bash "$HOOK" >/dev/null 2>&1
35
+ check '[ -s "$LOGD/preference-consult-directive.log" ]' "breadcrumb file written"
36
+ check 'grep -q "op=inject len=" "$LOGD/preference-consult-directive.log"' "breadcrumb carries len"
37
+ check 'grep -q "session=sess-abc" "$LOGD/preference-consult-directive.log"' "breadcrumb carries session"
38
+ rm -rf "$LOGD"
39
+
40
+ # An unwritable LOG_DIR never blocks the turn.
41
+ OUT_RO=$(printf '{"session_id":"x","prompt":"hi"}' | LOG_DIR=/nonexistent-dir-2620 bash "$HOOK" 2>/dev/null); RC_RO=$?
42
+ check '[ "$RC_RO" = "0" ]' "unwritable LOG_DIR still exits 0"
43
+ check 'printf "%s" "$OUT_RO" | grep -q "hookSpecificOutput"' "unwritable LOG_DIR still injects"
44
+
19
45
  echo "----- $PASS passed, $FAIL failed -----"
20
46
  for f in "${FAILED[@]:-}"; do [ -n "$f" ] && echo " $f"; done
21
47
  [ "$FAIL" -eq 0 ]
@@ -16,8 +16,13 @@
16
16
  # though that name is in the allowed set: the allowed set is a layout list,
17
17
  # and a declared file is written whole by its owning plugin;
18
18
  # - a target under an operator-data bucket (projects/ contacts/) may be at
19
- # most <bucket>/<entity>/<file> deep; documents/ may be at most
19
+ # most <bucket>/<entity>/<file> deep, with one exception:
20
+ # <bucket>/<entity>/archive/<file>, which holds superseded versions and is
21
+ # the ONLY permitted child of an entity folder; documents/ may be at most
20
22
  # <bucket>/<folder>/<file> deep. Tool-owned dirs pass at any depth.
23
+ # The same five rules run on the file-write/file-edit MCP route, in
24
+ # plugins/filesystem/mcp/src/lib/account-schema.ts — an MCP tool name matches
25
+ # none of the four matchers below, so that route needs its own copy.
21
26
  #
22
27
  # The hook governs this account's dir, and refuses any target inside a SIBLING
23
28
  # account under the same accounts root (reason=cross-account) for every session,
@@ -202,9 +207,21 @@ depth=$(printf '%s' "$REL" | awk -F/ '{print NF}')
202
207
  case "$SEG0" in
203
208
  projects|contacts)
204
209
  # Allowed: <bucket>/<entity>/<file> = 3 segments max; <bucket>/<file> = 2 ok.
205
- if [ "$depth" -gt 3 ]; then
210
+ # One exception: <bucket>/<entity>/archive/<file> = 4 segments. An entity
211
+ # folder holds its current version at the top and its superseded versions in
212
+ # archive/ beneath, which is the standing filing rule the per-turn directive
213
+ # states and SCHEMA.md's version rule now points at. It is the ONLY permitted
214
+ # child of an entity folder: any other name at depth 4, and anything at depth
215
+ # 5, still has no basis in the graph ontology and is refused. `archive`
216
+ # already satisfies the folder name rule below, so that check needs no
217
+ # exception of its own.
218
+ ARCHIVE_OK=0
219
+ if [ "$depth" -eq 4 ] && [ "$(printf '%s' "$REL" | cut -d/ -f3)" = "archive" ]; then
220
+ ARCHIVE_OK=1
221
+ fi
222
+ if [ "$depth" -gt 3 ] && [ "$ARCHIVE_OK" -eq 0 ]; then
206
223
  echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
207
- echo "Blocked: $SEG0/ is flat one folder per entity, then files. '$REL' nests a subtree under an entity folder, which has no basis in the graph ontology. Keep files directly under $SEG0/<name>/, or route a reorganisation through the data-manager specialist." >&2
224
+ echo "Blocked: $SEG0/ is flat: one folder per entity, then files, with archive/ the one permitted subfolder for superseded versions. '$REL' nests a subtree that has no basis in the graph ontology. Keep files directly under $SEG0/<name>/ or in $SEG0/<name>/archive/, or route a reorganisation through the data-manager specialist." >&2
208
225
  exit 2
209
226
  fi
210
227
  ;;
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env bash
2
2
  # preference-consult-directive — UserPromptSubmit hook. Injects a standing line
3
- # naming the two-layer preference architecture and two adherence directives:
4
- # consult layer-2 preferences (profile-read) before any customer document, and
5
- # promote finished deliverables out of output/. Prompt-independent; fail-open.
3
+ # naming the two-layer preference architecture, two adherence directives (consult
4
+ # layer-2 preferences with profile-read before any customer document, and promote
5
+ # finished deliverables out of output/), and the standing filing rules: records
6
+ # and files kept in step, engagement material in its project folder with the
7
+ # matching record, a person's own folder under contacts/, documents/ as lasting
8
+ # reference against uploads/ as intake, a superseded version moved into archive/
9
+ # rather than deleted, and any move finished and named in the same session.
10
+ # Because this is a UserPromptSubmit hook it reaches all four doors, which is why
11
+ # the filing rules live here and not in IDENTITY.md or the channel reframe
12
+ # literals. Prompt-independent; fail-open.
6
13
  set -uo pipefail
7
14
  HOOK_INPUT=$(cat 2>/dev/null || true)
8
15
  # Task 2424 — a public visitor's turn carries only its agent's authored
@@ -15,7 +22,10 @@ if [ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]; then
15
22
  fi
16
23
  echo "[public-prompt-gate] op=gate hook=preference-consult-directive.sh public=0" >&2
17
24
  command -v python3 >/dev/null 2>&1 || exit 0
18
- python3 - <<'PY' 2>/dev/null || exit 0
25
+ # The envelope is captured rather than printed straight through, so its
26
+ # additionalContext can be measured for the breadcrumb below. Nothing else about
27
+ # the emission changed: the same JSON reaches stdout, unmodified.
28
+ ENVELOPE=$(python3 - <<'PY' 2>/dev/null || true
19
29
  import json
20
30
  ctx = (
21
31
  "<preference-adherence>\n"
@@ -26,9 +36,44 @@ ctx = (
26
36
  "attachment), read the relevant layer-2 preferences with profile-read and apply them. "
27
37
  "Store finished deliverables under documents/ or the project folder. output/ is scratch of last resort, "
28
38
  "rebuilt on the next tool write, and never the home a graph reference points at.\n"
39
+ "Keep the account's records and its files in step, and keep both tidy, without being asked. "
40
+ "Client or engagement material goes in that project's folder, and the matching project record is created in the same pass. "
41
+ "A person gets a folder under contacts/ named after them, and the record points at that copy. "
42
+ "documents/ is account-wide reference that outlives an engagement; uploads/ is an intake tray and never a home. "
43
+ "An entity folder holds the current version at its top level, and a superseded version moves into archive/ beneath it rather than being deleted. "
44
+ "Finish any create, move or rename in the same session, filed and with every reference repointed, and name it briefly in your reply, because doing it silently is indistinguishable from not doing it.\n"
29
45
  "</preference-adherence>"
30
46
  )
31
47
  print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": ctx}}))
32
48
  PY
33
- echo "[pref-wrapper] op=inject" >&2
49
+ )
50
+ # A broken literal makes the heredoc fail with no stdout. Exit 0 in silence, as
51
+ # before: the hook never blocks a turn. The absence of a breadcrumb line while
52
+ # sessions run is what makes that state visible, which is the whole reason the
53
+ # log below exists.
54
+ [ -z "$ENVELOPE" ] && exit 0
55
+ printf '%s\n' "$ENVELOPE"
56
+
57
+ CTXLEN=$(printf '%s' "$ENVELOPE" | python3 -c 'import json,sys
58
+ try:
59
+ print(len(json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"]))
60
+ except Exception:
61
+ print(0)' 2>/dev/null || echo 0)
62
+ SID=$(printf '%s' "$HOOK_INPUT" | python3 -c 'import json,sys
63
+ try:
64
+ print(json.load(sys.stdin).get("session_id","") or "")
65
+ except Exception:
66
+ print("")' 2>/dev/null)
67
+ SID=${SID:-?}
68
+ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
69
+
70
+ # Durable breadcrumb. stderr alone cannot answer "did the directive fire on this
71
+ # turn" after the fact, which is the gap prompt-optimiser-directive.sh closed with
72
+ # its own log file. Best-effort: an unwritable path never blocks the turn.
73
+ if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then
74
+ printf '%s [pref-wrapper] op=inject len=%s session=%s\n' "$TS" "$CTXLEN" "$SID" \
75
+ >> "$LOG_DIR/preference-consult-directive.log" 2>/dev/null || true
76
+ fi
77
+
78
+ echo "[pref-wrapper] op=inject len=$CTXLEN session=$SID" >&2
34
79
  exit 0