@rubytech/create-realagent-code 0.1.572 → 0.1.574

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 (373) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +2 -1
  3. package/payload/platform/lib/admins-write/__tests__/index.test.ts +97 -0
  4. package/payload/platform/lib/admins-write/dist/index.d.ts +12 -0
  5. package/payload/platform/lib/admins-write/dist/index.d.ts.map +1 -1
  6. package/payload/platform/lib/admins-write/dist/index.js +27 -5
  7. package/payload/platform/lib/admins-write/dist/index.js.map +1 -1
  8. package/payload/platform/lib/admins-write/src/index.ts +35 -5
  9. package/payload/platform/lib/admins-write/vitest.config.ts +14 -0
  10. package/payload/platform/lib/dispatch-read/dist/session.d.ts.map +1 -1
  11. package/payload/platform/lib/dispatch-read/dist/session.js +19 -4
  12. package/payload/platform/lib/dispatch-read/dist/session.js.map +1 -1
  13. package/payload/platform/lib/dispatch-read/src/__tests__/session.test.ts +91 -0
  14. package/payload/platform/lib/dispatch-read/src/session.ts +25 -6
  15. package/payload/platform/lib/routine-templates/dist/index.d.ts +2 -2
  16. package/payload/platform/lib/routine-templates/dist/index.d.ts.map +1 -1
  17. package/payload/platform/lib/routine-templates/dist/index.js +9 -1
  18. package/payload/platform/lib/routine-templates/dist/index.js.map +1 -1
  19. package/payload/platform/lib/routine-templates/src/index.ts +14 -2
  20. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  21. package/payload/platform/plugins/admin/PLUGIN.md +8 -3
  22. package/payload/platform/plugins/admin/hooks/__tests__/html-structure-gate.test.sh +264 -0
  23. package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +122 -24
  24. package/payload/platform/plugins/admin/hooks/__tests__/specialist-dispatch-gate.test.sh +158 -0
  25. package/payload/platform/plugins/admin/hooks/html-structure-gate.sh +165 -0
  26. package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +111 -19
  27. package/payload/platform/plugins/admin/hooks/specialist-dispatch-gate.sh +159 -0
  28. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts +27 -0
  29. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.d.ts.map +1 -0
  30. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js +29 -0
  31. package/payload/platform/plugins/admin/mcp/dist/admin-bind-classify.js.map +1 -0
  32. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts +11 -0
  33. package/payload/platform/plugins/admin/mcp/dist/binding-validate.d.ts.map +1 -0
  34. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js +20 -0
  35. package/payload/platform/plugins/admin/mcp/dist/binding-validate.js.map +1 -0
  36. package/payload/platform/plugins/admin/mcp/dist/index.js +137 -2
  37. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +32 -2
  39. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +192 -90
  40. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +18 -0
  41. package/payload/platform/plugins/docs/references/admin-ui.md +71 -31
  42. package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +50 -57
  43. package/payload/platform/plugins/docs/references/internals.md +6 -0
  44. package/payload/platform/plugins/docs/references/outlook-guide.md +31 -1
  45. package/payload/platform/plugins/docs/references/telegram-guide.md +23 -0
  46. package/payload/platform/plugins/docs/references/troubleshooting.md +10 -0
  47. package/payload/platform/plugins/email/PLUGIN.md +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js +36 -5
  49. package/payload/platform/plugins/email/mcp/dist/__tests__/email-draft-send.test.js.map +1 -1
  50. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +10 -5
  51. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  52. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +12 -6
  53. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  54. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +18 -8
  55. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  56. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.d.ts.map +1 -1
  57. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js +41 -10
  58. package/payload/platform/plugins/email/mcp/dist/tools/email-draft-send.js.map +1 -1
  59. package/payload/platform/plugins/email/references/email-reference.md +12 -0
  60. package/payload/platform/plugins/outlook/PLUGIN.md +3 -1
  61. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts +2 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.d.ts.map +1 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js +177 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger-write-sites.test.js.map +1 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts +2 -0
  66. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.d.ts.map +1 -0
  67. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js +130 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-ledger.test.js.map +1 -0
  69. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts +2 -0
  70. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.d.ts.map +1 -0
  71. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js +162 -0
  72. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-reconcile.test.js.map +1 -0
  73. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts +2 -0
  74. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.d.ts.map +1 -0
  75. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js +168 -0
  76. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-state-fields.test.js.map +1 -0
  77. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts +64 -0
  78. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.d.ts.map +1 -0
  79. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js +110 -0
  80. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-ledger.js.map +1 -0
  81. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts +119 -0
  82. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.d.ts.map +1 -0
  83. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js +0 -0
  84. package/payload/platform/plugins/outlook/mcp/dist/lib/draft-reconcile.js.map +1 -0
  85. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts +8 -0
  86. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.d.ts.map +1 -0
  87. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js +247 -0
  88. package/payload/platform/plugins/outlook/mcp/dist/scripts/draft-reconcile.js.map +1 -0
  89. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.d.ts.map +1 -1
  90. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js +10 -0
  91. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-send.js.map +1 -1
  92. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
  93. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +21 -0
  94. package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js.map +1 -1
  95. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +16 -0
  96. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
  97. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +5 -1
  98. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
  99. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +4 -0
  100. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -1
  101. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +2 -1
  102. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -1
  103. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -1
  104. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +24 -0
  105. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
  106. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -1
  107. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +2 -1
  108. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -1
  109. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
  110. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +29 -0
  111. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
  112. package/payload/platform/plugins/scheduling/PLUGIN.md +4 -0
  113. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts +2 -0
  114. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.d.ts.map +1 -0
  115. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js +21 -0
  116. package/payload/platform/plugins/scheduling/mcp/dist/__tests__/draft-reconcile-throttle.test.js.map +1 -0
  117. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts +13 -0
  118. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.d.ts.map +1 -0
  119. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js +10 -0
  120. package/payload/platform/plugins/scheduling/mcp/dist/lib/draft-reconcile-throttle.js.map +1 -0
  121. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +54 -0
  122. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  123. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +78 -5
  124. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  125. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +133 -2
  126. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  127. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts +11 -0
  128. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  129. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +30 -1
  130. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  131. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.d.ts.map +1 -1
  132. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js +8 -0
  133. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-update.js.map +1 -1
  134. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  135. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +37 -6
  136. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  137. package/payload/platform/scripts/check-no-esm-require.mjs +4 -0
  138. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts +32 -0
  139. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.d.ts.map +1 -0
  140. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js +144 -0
  141. package/payload/platform/services/claude-session-manager/dist/authored-specialist-tools.js.map +1 -0
  142. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  143. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +3 -0
  144. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  145. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts +44 -0
  146. package/payload/platform/services/claude-session-manager/dist/html-structure-census.d.ts.map +1 -0
  147. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js +185 -0
  148. package/payload/platform/services/claude-session-manager/dist/html-structure-census.js.map +1 -0
  149. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  150. package/payload/platform/services/claude-session-manager/dist/http-server.js +32 -5
  151. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  152. package/payload/platform/services/claude-session-manager/dist/index.js +125 -0
  153. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  154. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +12 -1
  155. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  156. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +13 -1
  157. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  158. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -1
  159. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +7 -0
  160. package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -1
  161. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts +42 -0
  162. package/payload/platform/services/claude-session-manager/dist/quote-print-census.d.ts.map +1 -0
  163. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js +254 -0
  164. package/payload/platform/services/claude-session-manager/dist/quote-print-census.js.map +1 -0
  165. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts +92 -0
  166. package/payload/platform/services/claude-session-manager/dist/specialist-census.d.ts.map +1 -0
  167. package/payload/platform/services/claude-session-manager/dist/specialist-census.js +227 -0
  168. package/payload/platform/services/claude-session-manager/dist/specialist-census.js.map +1 -0
  169. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts +8 -0
  170. package/payload/platform/services/telegram-channel/dist/jsonl.d.ts.map +1 -0
  171. package/payload/platform/services/telegram-channel/dist/jsonl.js +42 -0
  172. package/payload/platform/services/telegram-channel/dist/jsonl.js.map +1 -0
  173. package/payload/platform/services/telegram-channel/dist/notification.d.ts +0 -31
  174. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  175. package/payload/platform/services/telegram-channel/dist/notification.js +4 -33
  176. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  177. package/payload/platform/services/telegram-channel/dist/server.d.ts +0 -5
  178. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  179. package/payload/platform/services/telegram-channel/dist/server.js +7 -101
  180. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  181. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +0 -66
  182. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  183. package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -64
  184. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  185. package/payload/platform/services/whatsapp-channel/dist/server.d.ts +0 -12
  186. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  187. package/payload/platform/services/whatsapp-channel/dist/server.js +15 -268
  188. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  189. package/payload/platform/templates/account-settings.json +8 -4
  190. package/payload/server/public/activity.html +6 -6
  191. package/payload/server/public/agents.html +5 -5
  192. package/payload/server/public/assets/{AdminLoginScreens-CMdj1W5p.js → AdminLoginScreens-Dp6k1XgD.js} +1 -1
  193. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.br +0 -0
  194. package/payload/server/public/assets/AdminLoginScreens-Dp6k1XgD.js.gz +0 -0
  195. package/payload/server/public/assets/{AdminShell-CZcaW3BQ.js → AdminShell-D-wB90c3.js} +1 -1
  196. package/payload/server/public/assets/AdminShell-D-wB90c3.js.br +0 -0
  197. package/payload/server/public/assets/AdminShell-D-wB90c3.js.gz +0 -0
  198. package/payload/server/public/assets/{activity-CAhibgb-.js → activity-BJxhblz-.js} +1 -1
  199. package/payload/server/public/assets/activity-BJxhblz-.js.br +0 -0
  200. package/payload/server/public/assets/activity-BJxhblz-.js.gz +0 -0
  201. package/payload/server/public/assets/{admin-xEXv3w9e.js → admin-DzEi8shs.js} +1 -1
  202. package/payload/server/public/assets/admin-DzEi8shs.js.br +0 -0
  203. package/payload/server/public/assets/admin-DzEi8shs.js.gz +0 -0
  204. package/payload/server/public/assets/{agents-CiAmzTaT.js → agents-DTL8kjfD.js} +1 -1
  205. package/payload/server/public/assets/agents-DTL8kjfD.js.br +0 -0
  206. package/payload/server/public/assets/agents-DTL8kjfD.js.gz +0 -0
  207. package/payload/server/public/assets/{browser-D36BiYhS.js → browser-UZp75zhx.js} +1 -1
  208. package/payload/server/public/assets/browser-UZp75zhx.js.br +0 -0
  209. package/payload/server/public/assets/browser-UZp75zhx.js.gz +0 -0
  210. package/payload/server/public/assets/{calendar-CJAM-iXn.js → calendar-Dfo0O2K_.js} +1 -1
  211. package/payload/server/public/assets/calendar-Dfo0O2K_.js.br +0 -0
  212. package/payload/server/public/assets/calendar-Dfo0O2K_.js.gz +0 -0
  213. package/payload/server/public/assets/chat-ByJAvEWZ.js +1 -0
  214. package/payload/server/public/assets/chat-ByJAvEWZ.js.br +0 -0
  215. package/payload/server/public/assets/chat-ByJAvEWZ.js.gz +0 -0
  216. package/payload/server/public/assets/chevron-left-iG7sYNm7.js +1 -0
  217. package/payload/server/public/assets/chevron-left-iG7sYNm7.js.br +0 -0
  218. package/payload/server/public/assets/clock-xSs0UF10.js +1 -0
  219. package/payload/server/public/assets/clock-xSs0UF10.js.br +0 -0
  220. package/payload/server/public/assets/clock-xSs0UF10.js.gz +0 -0
  221. package/payload/server/public/assets/{copy-axBe92iY.js → copy-CGI2jioK.js} +1 -1
  222. package/payload/server/public/assets/copy-CGI2jioK.js.br +0 -0
  223. package/payload/server/public/assets/copy-CGI2jioK.js.gz +0 -0
  224. package/payload/server/public/assets/data-dvwK4BcP.js +1 -0
  225. package/payload/server/public/assets/data-dvwK4BcP.js.br +0 -0
  226. package/payload/server/public/assets/data-dvwK4BcP.js.gz +0 -0
  227. package/payload/server/public/assets/{dispatch-D_i2ko7K.js → dispatch-Dx9-ty1K.js} +1 -1
  228. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.br +0 -0
  229. package/payload/server/public/assets/dispatch-Dx9-ty1K.js.gz +0 -0
  230. package/payload/server/public/assets/{field-DFgkqq1P.js → field-D-P6CsWA.js} +1 -1
  231. package/payload/server/public/assets/field-D-P6CsWA.js.br +0 -0
  232. package/payload/server/public/assets/field-D-P6CsWA.js.gz +0 -0
  233. package/payload/server/public/assets/{file-text-CEFoq0Je.js → file-text-RWintp9q.js} +1 -1
  234. package/payload/server/public/assets/file-text-RWintp9q.js.br +0 -0
  235. package/payload/server/public/assets/file-text-RWintp9q.js.gz +0 -0
  236. package/payload/server/public/assets/{graph-BrpbilNr.js → graph-CpBlfYrJ.js} +1 -1
  237. package/payload/server/public/assets/graph-CpBlfYrJ.js.br +0 -0
  238. package/payload/server/public/assets/graph-CpBlfYrJ.js.gz +0 -0
  239. package/payload/server/public/assets/{graph-labels-BtmcWzo7.js → graph-labels-BOMyPppX.js} +1 -1
  240. package/payload/server/public/assets/graph-labels-BOMyPppX.js.br +0 -0
  241. package/payload/server/public/assets/graph-labels-BOMyPppX.js.gz +0 -0
  242. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css +1 -0
  243. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.br +0 -0
  244. package/payload/server/public/assets/jsx-runtime-DxsQ5Y-z.css.gz +0 -0
  245. package/payload/server/public/assets/{operator-Dl7dJt1G.js → operator-DFgW6GiH.js} +1 -1
  246. package/payload/server/public/assets/operator-DFgW6GiH.js.br +0 -0
  247. package/payload/server/public/assets/operator-DFgW6GiH.js.gz +0 -0
  248. package/payload/server/public/assets/{page-nx31Ij0T.js → page-BRShHSH0.js} +1 -1
  249. package/payload/server/public/assets/page-BRShHSH0.js.br +0 -0
  250. package/payload/server/public/assets/page-BRShHSH0.js.gz +0 -0
  251. package/payload/server/public/assets/{page-zC66xzNT.js → page-BbBCu9iP.js} +5 -5
  252. package/payload/server/public/assets/page-BbBCu9iP.js.br +0 -0
  253. package/payload/server/public/assets/page-BbBCu9iP.js.gz +0 -0
  254. package/payload/server/public/assets/play-BJxUaf6-.js +1 -0
  255. package/payload/server/public/assets/play-BJxUaf6-.js.br +0 -0
  256. package/payload/server/public/assets/play-BJxUaf6-.js.gz +0 -0
  257. package/payload/server/public/assets/{public-DEKnH5Q0.js → public-DkObqkzL.js} +1 -1
  258. package/payload/server/public/assets/public-DkObqkzL.js.br +0 -0
  259. package/payload/server/public/assets/public-DkObqkzL.js.gz +0 -0
  260. package/payload/server/public/assets/{rotate-ccw-CBex8gon.js → rotate-ccw-DgMO69nh.js} +1 -1
  261. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.br +0 -0
  262. package/payload/server/public/assets/rotate-ccw-DgMO69nh.js.gz +0 -0
  263. package/payload/server/public/assets/routines-D9kvg_Xu.js +2 -0
  264. package/payload/server/public/assets/routines-D9kvg_Xu.js.br +0 -0
  265. package/payload/server/public/assets/routines-D9kvg_Xu.js.gz +0 -0
  266. package/payload/server/public/assets/{skills-BmxjPOmq.js → skills-l5I9HZ3D.js} +1 -1
  267. package/payload/server/public/assets/skills-l5I9HZ3D.js.br +0 -0
  268. package/payload/server/public/assets/skills-l5I9HZ3D.js.gz +0 -0
  269. package/payload/server/public/assets/{tasks-BTFYO9vT.js → tasks-C2qJMv6i.js} +1 -1
  270. package/payload/server/public/assets/tasks-C2qJMv6i.js.br +0 -0
  271. package/payload/server/public/assets/tasks-C2qJMv6i.js.gz +0 -0
  272. package/payload/server/public/assets/{triangle-alert-px1YRJJ7.js → triangle-alert-BnC67uSs.js} +1 -1
  273. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.br +0 -0
  274. package/payload/server/public/assets/triangle-alert-BnC67uSs.js.gz +0 -0
  275. package/payload/server/public/assets/{useCopyFeedback-nMXwRcDX.js → useCopyFeedback-1RmgkTFh.js} +1 -1
  276. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.br +0 -0
  277. package/payload/server/public/assets/useCopyFeedback-1RmgkTFh.js.gz +0 -0
  278. package/payload/server/public/assets/{useMediaQuery-DjbOTk2X.js → useMediaQuery-BQslddHQ.js} +1 -1
  279. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.br +0 -0
  280. package/payload/server/public/assets/useMediaQuery-BQslddHQ.js.gz +0 -0
  281. package/payload/server/public/assets/{useVoiceRecorder-Cx7AOmib.js → useVoiceRecorder-BeZNQEC4.js} +1 -1
  282. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.br +0 -0
  283. package/payload/server/public/assets/useVoiceRecorder-BeZNQEC4.js.gz +0 -0
  284. package/payload/server/public/assets/{wrench-Cexvlx6J.js → wrench-D4ufQG6C.js} +1 -1
  285. package/payload/server/public/assets/wrench-D4ufQG6C.js.br +0 -0
  286. package/payload/server/public/assets/wrench-D4ufQG6C.js.gz +0 -0
  287. package/payload/server/public/browser.html +5 -5
  288. package/payload/server/public/calendar.html +8 -8
  289. package/payload/server/public/chat.html +14 -14
  290. package/payload/server/public/data.html +12 -12
  291. package/payload/server/public/dispatch.html +5 -5
  292. package/payload/server/public/field.html +3 -3
  293. package/payload/server/public/graph.html +10 -10
  294. package/payload/server/public/index.html +14 -14
  295. package/payload/server/public/operator.html +16 -16
  296. package/payload/server/public/public.html +14 -14
  297. package/payload/server/public/routines.html +8 -8
  298. package/payload/server/public/skills.html +6 -6
  299. package/payload/server/public/tasks.html +7 -7
  300. package/payload/server/server.js +1088 -694
  301. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.br +0 -0
  302. package/payload/server/public/assets/AdminLoginScreens-CMdj1W5p.js.gz +0 -0
  303. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.br +0 -0
  304. package/payload/server/public/assets/AdminShell-CZcaW3BQ.js.gz +0 -0
  305. package/payload/server/public/assets/activity-CAhibgb-.js.br +0 -0
  306. package/payload/server/public/assets/activity-CAhibgb-.js.gz +0 -0
  307. package/payload/server/public/assets/admin-xEXv3w9e.js.br +0 -0
  308. package/payload/server/public/assets/admin-xEXv3w9e.js.gz +0 -0
  309. package/payload/server/public/assets/agents-CiAmzTaT.js.br +0 -0
  310. package/payload/server/public/assets/agents-CiAmzTaT.js.gz +0 -0
  311. package/payload/server/public/assets/browser-D36BiYhS.js.br +0 -0
  312. package/payload/server/public/assets/browser-D36BiYhS.js.gz +0 -0
  313. package/payload/server/public/assets/calendar-CJAM-iXn.js.br +0 -0
  314. package/payload/server/public/assets/calendar-CJAM-iXn.js.gz +0 -0
  315. package/payload/server/public/assets/chat-DhZSvtZK.js +0 -1
  316. package/payload/server/public/assets/chat-DhZSvtZK.js.br +0 -0
  317. package/payload/server/public/assets/chat-DhZSvtZK.js.gz +0 -0
  318. package/payload/server/public/assets/chevron-left-CXEcNlc3.js +0 -1
  319. package/payload/server/public/assets/chevron-left-CXEcNlc3.js.br +0 -0
  320. package/payload/server/public/assets/clock-DdxIJwTr.js +0 -1
  321. package/payload/server/public/assets/clock-DdxIJwTr.js.br +0 -0
  322. package/payload/server/public/assets/clock-DdxIJwTr.js.gz +0 -0
  323. package/payload/server/public/assets/copy-axBe92iY.js.br +0 -0
  324. package/payload/server/public/assets/copy-axBe92iY.js.gz +0 -0
  325. package/payload/server/public/assets/data-DUvkI-zq.js +0 -1
  326. package/payload/server/public/assets/data-DUvkI-zq.js.br +0 -0
  327. package/payload/server/public/assets/data-DUvkI-zq.js.gz +0 -0
  328. package/payload/server/public/assets/dispatch-D_i2ko7K.js.br +0 -0
  329. package/payload/server/public/assets/dispatch-D_i2ko7K.js.gz +0 -0
  330. package/payload/server/public/assets/field-DFgkqq1P.js.br +0 -0
  331. package/payload/server/public/assets/field-DFgkqq1P.js.gz +0 -0
  332. package/payload/server/public/assets/file-text-CEFoq0Je.js.br +0 -0
  333. package/payload/server/public/assets/file-text-CEFoq0Je.js.gz +0 -0
  334. package/payload/server/public/assets/graph-BrpbilNr.js.br +0 -0
  335. package/payload/server/public/assets/graph-BrpbilNr.js.gz +0 -0
  336. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.br +0 -0
  337. package/payload/server/public/assets/graph-labels-BtmcWzo7.js.gz +0 -0
  338. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css +0 -1
  339. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.br +0 -0
  340. package/payload/server/public/assets/jsx-runtime-XuYKjgVb.css.gz +0 -0
  341. package/payload/server/public/assets/operator-Dl7dJt1G.js.br +0 -3
  342. package/payload/server/public/assets/operator-Dl7dJt1G.js.gz +0 -0
  343. package/payload/server/public/assets/page-nx31Ij0T.js.br +0 -0
  344. package/payload/server/public/assets/page-nx31Ij0T.js.gz +0 -0
  345. package/payload/server/public/assets/page-zC66xzNT.js.br +0 -0
  346. package/payload/server/public/assets/page-zC66xzNT.js.gz +0 -0
  347. package/payload/server/public/assets/play-MbU8O2iC.js +0 -1
  348. package/payload/server/public/assets/play-MbU8O2iC.js.br +0 -0
  349. package/payload/server/public/assets/play-MbU8O2iC.js.gz +0 -0
  350. package/payload/server/public/assets/public-DEKnH5Q0.js.br +0 -0
  351. package/payload/server/public/assets/public-DEKnH5Q0.js.gz +0 -0
  352. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.br +0 -0
  353. package/payload/server/public/assets/rotate-ccw-CBex8gon.js.gz +0 -0
  354. package/payload/server/public/assets/routines-BqCmbvFJ.js +0 -2
  355. package/payload/server/public/assets/routines-BqCmbvFJ.js.br +0 -0
  356. package/payload/server/public/assets/routines-BqCmbvFJ.js.gz +0 -0
  357. package/payload/server/public/assets/skills-BmxjPOmq.js.br +0 -0
  358. package/payload/server/public/assets/skills-BmxjPOmq.js.gz +0 -0
  359. package/payload/server/public/assets/tasks-BTFYO9vT.js.br +0 -0
  360. package/payload/server/public/assets/tasks-BTFYO9vT.js.gz +0 -0
  361. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.br +0 -0
  362. package/payload/server/public/assets/triangle-alert-px1YRJJ7.js.gz +0 -0
  363. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.br +0 -0
  364. package/payload/server/public/assets/useCopyFeedback-nMXwRcDX.js.gz +0 -0
  365. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.br +0 -0
  366. package/payload/server/public/assets/useMediaQuery-DjbOTk2X.js.gz +0 -0
  367. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.br +0 -0
  368. package/payload/server/public/assets/useVoiceRecorder-Cx7AOmib.js.gz +0 -0
  369. package/payload/server/public/assets/wrench-Cexvlx6J.js.br +0 -0
  370. package/payload/server/public/assets/wrench-Cexvlx6J.js.gz +0 -0
  371. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js → jsx-runtime-5lmxFBIw.js} +0 -0
  372. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.br → jsx-runtime-5lmxFBIw.js.br} +0 -0
  373. /package/payload/server/public/assets/{jsx-runtime-C5Hcfj84.js.gz → jsx-runtime-5lmxFBIw.js.gz} +0 -0
@@ -13,7 +13,8 @@ This skill manages who has admin access to this account. It wraps six MCP tools
13
13
 
14
14
  | Tool | Purpose |
15
15
  |---|---|
16
- | `admin-add` | Add a new admin. Requires a name. PIN is optional: omit it and a unique 4-digit PIN is generated. PIN must be at least 4 digits and unique across all users on the device. |
16
+ | `admin-add` | Add a new admin. Requires a name. PIN is optional: omit it and a unique 4-digit PIN is generated. PIN must be at least 4 digits and unique across all users on the device. `phone` and `telegram` are optional channel bindings — pass them and the new admin is born reachable on those channels. |
17
+ | `admin-bind` | Set the `phone` and/or `telegram` chat id on an admin. Defaults to the calling admin; pass `userId` to bind someone else and that also needs `confirm: true`, because a binding decides whose identity an inbound resolves to. Sets only what you pass; an omitted field is left alone. |
17
18
  | `admin-remove` | Remove an admin by `userId` (find IDs via `admin-list`). The last admin on the account cannot be removed. The user's device-level entry is retained so they can still administer other accounts. |
18
19
  | `admin-list` | List all admins on this account with names and roles. |
19
20
  | `admin-update-pin` | Update an admin's PIN. Defaults to the calling admin if no `userId` is given. PIN must be at least 4 digits and unique across all users on the device. |
@@ -24,6 +25,35 @@ This skill manages who has admin access to this account. It wraps six MCP tools
24
25
 
25
26
  The remote-access password is the *outer* gate — the password the Cloudflare tunnel asks for before the PIN screen — and it is independent of the PIN. Each admin has their own; setting or revoking one never touches another admin's password or anyone's PIN. The PIN still does identity (it names the admin in session rows); the access password just decides who can reach a tunnel-exposed install at all. On an install upgraded from the old single shared password, the owner's existing password migrates onto their own record automatically on first boot, so nobody is locked out.
26
27
 
28
+ ## Channel bindings decide whether an answer reaches a second channel
29
+
30
+ An admin's `users.json` entry carries two optional bindings, and they are what
31
+ lets one answer reach every channel that person holds rather than only the one
32
+ the message arrived on.
33
+
34
+ `phone` is the E.164 number. It identifies a WhatsApp sender as this person, and
35
+ it is also the address used to reach them on WhatsApp when a turn arrives
36
+ somewhere else. `telegram` is the chat id, matched exactly, doing the same two
37
+ jobs for Telegram. They are not interchangeable, and two-way reach needs both.
38
+
39
+ `account.json` `whatsapp.adminPhones` is a different thing: an allowlist saying
40
+ a number may reach the admin agent. It grants access; it does not say whose
41
+ number it is, which is why adding a phone there never binds anyone.
42
+
43
+ When a binding is missing, the failure is silent to the person messaging. These
44
+ lines in `server.log` name it:
45
+
46
+ | Line | Means |
47
+ |---|---|
48
+ | `[whatsapp-native] op=admin-identity senderId=… userId=unbound-empty` | this sender resolves to no admin, so no second door is looked for |
49
+ | `[channel-fanout] op=no-target origin=… reason=no-admin-user` | the same fact, at the fan-out |
50
+ | `[channel-fanout] op=fan-out reached=… notReached=…` | which doors actually received the answer |
51
+
52
+ A Telegram bot owned by a `role:'client'` sub-account can never resolve an
53
+ admin: both resolvers return null on a client account before reading any chat
54
+ id. Setting `telegram` will not help there; the bot has to live on the house
55
+ account.
56
+
27
57
  ## The three-store invariant
28
58
 
29
59
  Admin identity lives in three places that must stay in lockstep:
@@ -32,7 +62,7 @@ Admin identity lives in three places that must stay in lockstep:
32
62
  2. `users.json`: device-level PIN authentication. This is the source of truth at login.
33
63
  3. The Neo4j graph: `:AdminUser` + `:Person` + `OWNS` + `ADMIN_OF` edges. Display and graph identity.
34
64
 
35
- `admin-add` writes all three. `admin-update-pin` writes `users.json` only (the other stores carry no PIN). If any leg fails, the tool returns `is_error: true` and `server.log` carries a `[admin-auth-store] action=… userId=… result=fail store=…` line naming which leg failed and what was already written. When you see that line, tell the owner the record is half-written and may need manual reconciliation.
65
+ `admin-add` writes all three. `admin-update-pin` and `admin-bind` write `users.json` only (the other stores carry neither the PIN nor the channel bindings). If any leg fails, the tool returns `is_error: true` and `server.log` carries a `[admin-auth-store] action=… userId=… result=fail store=…` line naming which leg failed and what was already written. When you see that line, tell the owner the record is half-written and may need manual reconciliation.
36
66
 
37
67
  ## Never write `account.json` directly
38
68
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Real Agent ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:d924bab338240bf9b1ffa6dd391f2428bf182d40da44677bd3bde3ab53b7a0ba
4
+ content-hash: sha256:327b61590f8a5e2dd2a654cd848edd19eccf7df3acecccad012160e8923e8fa0
5
5
  brand: realagent-code
6
6
  product-name: Real Agent
7
7
  ---
@@ -1946,74 +1946,67 @@ defect. Turns that answered no inbound — subagent side-chains, turns driven fr
1946
1946
  claude.ai — are deliberately outside that set: the credit guards are what keep
1947
1947
  them out of a customer's chat.
1948
1948
 
1949
- Each door's `src/parity.test.ts` asserts the contract end to end against a real
1950
- JSONL and the real follower.
1951
-
1952
- **Six discard sites, each now logged.** A follower outcome is dropped at:
1953
-
1954
- | File | Guard | `reason=` |
1955
- |---|---|---|
1956
- | `telegram-channel/src/server.ts:241` | `pendingTurns <= 0` | `no-credit` |
1957
- | `webchat-channel/src/server.ts:378` | `pendingTurns <= 0` | `no-credit` |
1958
- | `whatsapp-channel/src/server.ts:481` | `pendingTurns <= 0` | `no-credit` |
1959
- | `whatsapp-channel/src/server.ts:489` | `consumedInbounds <= 0` | `no-consumed-inbound` |
1960
- | `whatsapp-channel/src/server.ts:617` | `pendingTurns <= 0` (2nd follower) | `no-credit` |
1961
- | `whatsapp-channel/src/server.ts:620` | `consumedInbounds <= 0` (2nd follower) | `no-consumed-inbound` |
1949
+ **Discard sites.** The WhatsApp and Telegram followers and their credit ledgers
1950
+ were removed, so those two doors now have none: one emitter reads the JSONL and
1951
+ the fan-out decides where an answer goes. Only webchat still carries credit
1952
+ guards, in `webchat-channel/src/server.ts`, and `src/parity.test.ts` survives
1953
+ for that door alone.
1962
1954
 
1963
1955
  ```
1964
- [<door>-channel] op=turn-drop sessionId=… messageId=… finalBytes=… reason=…
1965
- [<door>-turn-census] op=census sessionId=… pending=… held=<0|1> heldMessageId=… heldAgeMs=… lastMessageId=…
1956
+ [channel-fanout] op=fan-out reached=… notReached=… segments=… messageId=… sessionId=…
1957
+ [channel-parity] op=parity-census sessionId=… rendered=… sent=… missing=… missingIds=…
1966
1958
  ```
1967
1959
 
1968
- Any `op=turn-drop` with `finalBytes>0` is a lost answer. The census runs every
1969
- 60 s per follower; `held=1` with `heldAgeMs` rising across ticks is a hold that
1970
- never resolved, and `pending` not returning to 0 with no inbound in flight is
1971
- credit drift.
1960
+ `missing` above zero is a lost answer, and `missingIds` names each one. The
1961
+ census prints every cycle, including when the counts agree: an absent line means
1962
+ the census stopped, never that delivery is healthy.
1972
1963
 
1973
- **Read them in the right file.** Both lines are emitted by the *channel MCP
1974
- server*, whose stderr is teed to `${LOG_DIR}/mcp-<serverName>-<sessionId>.log`
1975
- (`claude-session-manager/src/channel-spawn-tee.ts`). They are **never** in
1976
- `server.log`, which carries the gateway-side half only — `op=turn-end`,
1977
- `op=turn-answer-absent`, `delivered=`. Grepping `server.log` for a drop returns
1978
- nothing and means nothing.
1964
+ ## What a second door actually requires
1979
1965
 
1980
- ```bash
1981
- # the discard and the hold, per session
1982
- grep -E 'op=(turn-drop|census)' ~/.<brand>/logs/mcp-telegram-channel-*.log | tail -20
1983
- # the gateway's view of the same turn
1984
- grep -E 'op=(turn-end|turn-answer-absent)' ~/.<brand>/logs/server.log | grep <sessionId>
1985
- ```
1966
+ Reaching a door other than the one the message arrived on needs two facts.
1986
1967
 
1987
- **A seventh discard site, and the one nothing was reading.** An answer that
1988
- survived every guard above could still be produced and thrown away at the last
1989
- step, because the gateway resolved its destination from admin identity rather
1990
- than from the channel it came in on. A public visitor has no admin identity, so
1991
- the resolver returned no target: nothing was attempted, no transport failed, and
1992
- the line read `reached=none notReached=none`.
1968
+ **The person must be resolvable from the turn's sender.** That resolution reads
1969
+ one place: the admin's `users.json` entry. `phone` (E.164) identifies a WhatsApp
1970
+ sender; `telegram` (the chat id, matched exactly) identifies a Telegram sender.
1971
+ `account.json` `whatsapp.adminPhones` is a different store with a different job:
1972
+ it says a number may reach the admin agent, not whose number it is, which is why
1973
+ adding a phone there binds nobody. Set the bindings with `admin-bind`, or at
1974
+ creation with `admin-add`'s `phone` and `telegram`.
1993
1975
 
1994
- The turn-end body now carries the channel server's own spawn `role`, and that is
1995
- the only thing that decides the destination:
1976
+ **The other door must hold a live target for that person.** `phone` addresses
1977
+ WhatsApp when a Telegram turn wants to reach it, and `telegram` addresses
1978
+ Telegram from a WhatsApp turn. Two-way reach needs both fields, not one.
1996
1979
 
1997
- | `role=` | Where the answer goes | `op=fan-out` |
1998
- |---|---|---|
1999
- | `public` | every segment through the posting key's own transport; no identity resolver, no registry read | never emitted |
2000
- | `admin` | every live door the person is reachable on, via `fanTargetsFor` | emitted |
2001
- | absent or unrecognised | nowhere. 400, plus one `op=turn-role-absent evicted=<yes\|no>` | never emitted |
2002
-
2003
- `op=turn-end` and `op=turn-strand-anomaly` both name the role, so no outcome is
2004
- unattributable.
2005
-
2006
- A roleless POST has one cause: a channel server from a build predating the field,
2007
- still attached to a session that outlived the platform restart. It reconnects its
2008
- stream, so nothing respawns it, and left alone it would post roleless turn-ends for
2009
- the life of that session. `evicted=yes` says the gateway dropped its subscriber, so
2010
- the next inbound spawns a server on the current build: one turn is lost per session
2011
- rather than all of them. That turn's answer is deliberately not delivered to the
2012
- posting key, because doing so would restore the very fallback whose reachability hid
2013
- the original defect. The standing check is one line per door per minute, over a
2014
- rolling hour:
1980
+ The three lines that name each failure:
1981
+
1982
+ | Line | Means |
1983
+ |---|---|
1984
+ | `[whatsapp-native] op=admin-identity senderId=… userId=unbound-empty` | no binding, so no second door is even looked for |
1985
+ | `[channel-fanout] op=no-target origin=… reason=no-admin-user` | the same fact, observed at the fan-out |
1986
+ | `[channel-fanout] op=no-target origin=… reason=origin-not-live` | the door holds no live target for this key |
1987
+
1988
+ A Telegram bot owned by a `role:'client'` sub-account can never resolve an
1989
+ admin: both resolvers return null on a client account before reading any chat
1990
+ id. No binding fixes that; the bot has to live on the house account.
1991
+
1992
+ `[whatsapp-account-scope] op=non-uuid-account caller=fan-out` means the fan-out
1993
+ was handed a Baileys credential dirname where a platform UUID belongs. That is
1994
+ the defect that stranded a live admin answer, and the line exists so the next
1995
+ occurrence announces itself instead of being inferred from a silent miss.
1996
+
1997
+ **Read them in `server.log`.** Both are emitted by the ui-server, which now owns
1998
+ the one emitter between the session JSONL and every channel. The per-door
1999
+ `op=turn-drop` and `op=census` lines that used to sit in
2000
+ `${LOG_DIR}/mcp-<serverName>-<sessionId>.log` are gone with the per-channel
2001
+ followers that were removed, and so is `delivered=fallback`, which no code
2002
+ emits.
2015
2003
 
2016
2004
  ```bash
2005
+ # every answer and where it went
2006
+ grep -E 'op=(fan-out|parity-census)' ~/.<brand>/logs/server.log | tail -20
2007
+ # one session
2008
+ grep 'op=fan-out' ~/.<brand>/logs/server.log | grep <sessionId>
2009
+ ```
2017
2010
  # the standing read; any undeliveredPublic>0 is a failure
2018
2011
  grep -E 'op=census' ~/.<brand>/logs/server.log | grep delivery-census | tail -5
2019
2012
  # a visitor's answer that reached nobody, or a turn-end that named no role
@@ -2817,6 +2810,29 @@ Two things to know:
2817
2810
  Text only for now. Photos, files and voice notes are not available on Telegram
2818
2811
  from the dashboard.
2819
2812
 
2813
+ ## Building your own Telegram functionality
2814
+
2815
+ Telegram is a core plugin, so it is on every install and cannot be switched off.
2816
+
2817
+ Beyond sending and reading messages, Real Agent can reach every one of the
2818
+ 185 things Telegram's Bot API can do: buttons under a message, changing a
2819
+ message after you sent it, uploading a photo or a document, pinning, polls, chat
2820
+ administration, and opening a small web page inside the Telegram chat itself.
2821
+
2822
+ Just ask for what you want. "Send the team a message with Yes and No buttons",
2823
+ "update that job card to say he's on site", "pin the rota to the group".
2824
+ Real Agent loads its Telegram build skill, which lists every available
2825
+ method and how to call it.
2826
+
2827
+ Five of the 185 are deliberately refused, because they would break your own
2828
+ inbound messages: setting or deleting the webhook by hand, polling Telegram for
2829
+ updates instead, and the two that move the bot off Telegram's cloud servers.
2830
+ Setting up or repointing a webhook is what the Telegram setup skill is for.
2831
+
2832
+ If Telegram publishes a new capability it works here immediately, with nothing
2833
+ released first. A scheduled check reconciles what Real Agent knows about
2834
+ against Telegram's live documentation and reports anything that changed.
2835
+
2820
2836
  ---
2821
2837
  # Outlook
2822
2838
  Source: https://docs.getmaxy.com/outlook-guide.md
@@ -2886,9 +2902,19 @@ Mail only: calendar and contacts tools refuse on a shared mailbox and say so. If
2886
2902
 
2887
2903
  When the admin agent drafts or sends a message to a known contact, it reads that contact's record first and applies any routing the record carries, for example a preferred send-to address, showing the result for your approval before anything is sent.
2888
2904
 
2905
+ ## What an empty Drafts folder does not prove
2906
+
2907
+ If a draft is gone from Drafts and a copy of it is in Sent Items, that does **not** mean this platform sent it. An external mail client signed in to the same mailbox produces exactly the same folder state, and so does a person clicking send on their phone. Never report that Real Agent sent something on the strength of folder state alone.
2908
+
2909
+ Two other things look alarming and are the same blind spot. A message that has left the Drafts folder still fetches by id, and while it remains a draft object it still comes back with no `from` — so "it still fetches" is not "it is still a draft". Read `isDraft` and `parentFolderId`, which every mail read now returns.
2910
+
2911
+ The one statement to trust is the hourly reconcile's verdict, `op=draft-reconcile … outcome=`. It compares a ledger of every draft this platform created and sent against what the mailbox actually holds. `outcome=sent-not-by-maxy` means the platform made the draft, never sent it, and it left the folder anyway.
2912
+
2913
+ Naming *what* sent it needs the mailbox tenant's Microsoft 365 unified audit log, where a `Send` record carries the client string. This plugin's delegated permissions cannot read it — the mailbox owner's M365 administrator pulls it. Say that, rather than guessing.
2914
+
2889
2915
  ## Observability
2890
2916
 
2891
- All log lines start with `[outlook-mcp]` and write to `server.log`. They are key=value, account-scoped:
2917
+ All log lines start with `[outlook-mcp]` and write to `server.log`, except the draft reconcile, which the scheduling heartbeat spawns and whose lines land in `check-due-events.log`. They are key=value, account-scoped:
2892
2918
 
2893
2919
  | Event | Line shape |
2894
2920
  |-------|------------|
@@ -2940,6 +2966,13 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
2940
2966
  | Draft-send request | `draft-send-request account=<id> id=<draftId>` |
2941
2967
  | Draft sent | `draft-sent account=<id> id=<draftId> status=202` |
2942
2968
  | Draft-send failed | `draft-send-failed account=<id> id=<draftId> error=<msg>` |
2969
+ | Ledger row written | `draft-ledger op=<created\|sent> account=<id> mailbox=<addr> draftId=<id> tool=<t> route=<r\|null>` — one per draft created and per draft sent, the left-hand side the reconcile compares the mailbox against |
2970
+ | Ledger row lost | `draft-ledger-failed op=<created\|sent> account=<id> draftId=<id> error=<msg>` — the row was not written; the mail still went. A `sent` row lost here can make a real Real Agent send read as `sent-not-by-maxy` later |
2971
+ | Draft reconciled | `draft-reconcile account=<id> mailbox=<addr> draftId=<id> outcome=<still-draft\|sent-by-maxy\|sent-not-by-maxy\|deleted\|unreadable> createdTs=<iso> sentDateTime=<iso\|null> parentFolderId=<id\|null> ageMs=<N>` — plus `sentCopyId=`/`sentCopyTo=`/`sentCopyAt=` on `sent-not-by-maxy`, and `status=`/`code=` on `unreadable`. **In `check-due-events.log`, not `server.log`** |
2972
+ | Reconcile census | `draft-reconcile-census account=<id> total=<N> stillDraft=<N> sentByMaxy=<N> sentNotByMaxy=<N> deleted=<N> unreadable=<N>` — **every pass including an all-zero one, so an absent census means the reconcile stopped running** |
2973
+ | Reconcile pruned | `draft-reconcile-pruned accounts=<N> removed=<N> kept=<N>` |
2974
+ | Reconcile skipped | `draft-reconcile-skipped reason=no-vendor-app` — this brand ships no Outlook app; distinct from "did not run" |
2975
+ | Reconcile could not run | `draft-reconcile-failed error=<msg>` — exit 1 |
2943
2976
 
2944
2977
  ## Diagnostic paths
2945
2978
 
@@ -2956,6 +2989,19 @@ grep -E "^\[outlook-mcp\] (mail-reply|draft-created)" ~/.maxy/logs/server.log \
2956
2989
  | grep -E "threaded=true|op=reply" | grep -E "convId=(null)?( |$)" | head
2957
2990
  ```
2958
2991
 
2992
+ ```bash
2993
+ # One draft's whole lifeline: created, then a verdict per hourly pass.
2994
+ grep -E "op=draft-ledger|op=draft-reconcile" ~/.maxy/logs/check-due-events.log | grep "draftId=<id>"
2995
+
2996
+ # Did the reconcile stop? An absent census IS the failure signal.
2997
+ grep -c "op=draft-reconcile-census" ~/.maxy/logs/check-due-events.log
2998
+
2999
+ # Confirm a sent-not-by-maxy: this must be EMPTY for the window in question.
3000
+ grep -E "op=request tool=outlook-(draft-send|mail-send|mail-reply)" ~/.maxy/logs/mcp-outlook-*.log
3001
+ ```
3002
+
3003
+ Attribution triage: `outcome=sent-not-by-maxy` with the send grep above empty means this platform made the draft, never sent it, and it left the folder anyway — an external mail client on the same mailbox. If the send grep is **not** empty for that draft id, suspect a lost ledger row instead and check for `draft-ledger-failed`.
3004
+
2959
3005
  Threading triage: **absence of a `convId` on a threaded write means threading was lost.** `draft-created threaded=false convId=null` is correct and expected — a new message has no parent conversation. `draft-created threaded=true convId=null` is a fault: Graph returned a reply draft that names no conversation, and the reply will land detached from the original.
2960
3006
 
2961
3007
  Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions issue; `elapsedMs > 5000` → Graph slowness or DNS.
@@ -4144,24 +4190,43 @@ both `kind: 'specialist'` and differ only by origin.
4144
4190
  repair, and it is not what runs unattended: an agent runs only when something
4145
4191
  dispatches it, an inbound message is operator-initiated, so the only autonomous
4146
4192
  path is the scheduler. The modal says so and points at the routines instead.
4147
- **A file in the LIVE directory whose name also exists under
4148
- `<accountDir>/plugins/*/agents/` is excluded from this set.** `agent-builder`
4149
- copies the authored file into `specialists/agents/` because presence there is
4150
- what makes an agent dispatchable, so the live directory says nothing about who
4151
- wrote the agent and every activated user-created specialist used to render
4152
- twice, the second time as Shipped. The account plugin tree is the authorship
4153
- evidence and its row wins; it is the same discriminator `disable` requires.
4154
- Both gates read filenames off disk, so a plugin file whose frontmatter does not
4155
- parse still suppresses its live twin it is authored either way, and
4156
- `specialistsSkipped` is what reports the parse failure. **The exclusion covers
4157
- both account directories, live and quarantine.** It was live-only while only a
4158
- shipped agent could be quarantined; now that an authored specialist can be
4159
- switched off, its specialist row carries the plugin, the skills, the detail
4160
- route and its own disabled state, so a second row from the shipped walk would
4161
- be a duplicate with none of that. The bundled templates are never deduped: a
4162
- bundled name is shipped by definition. Each exclusion logs
4163
- `op=list-shipped-dedup file=<name>` and the count rides `op=list` and the
4164
- response as `deduped`.
4193
+ **A file in the LIVE or QUARANTINE directory whose name also exists under
4194
+ `<accountDir>/plugins/*/agents/` is excluded from this set only when the two
4195
+ files agree.** `agent-builder` copies the authored file into
4196
+ `specialists/agents/` because presence there is what makes an agent
4197
+ dispatchable, so the live directory says nothing about who wrote the agent and
4198
+ every activated user-created specialist used to render twice, the second time
4199
+ as Shipped. But the filename alone was also not evidence that the live file IS
4200
+ that copy: `computeSpecialistDomains` builds the spawn manifest from the live
4201
+ file, so an excluded row could describe an agent nobody would run, and a
4202
+ premium agent which reaches an account only through the live directory, since
4203
+ the bundled templates carry no `--` name lost its only row to an authored
4204
+ file of the same name, its risk class replaced by that file's. **The test is
4205
+ the three fields the card shows and the prompt reads: `name`, `description`,
4206
+ and `tools` compared as a set.** Agreement means one row, exactly as before.
4207
+ Disagreement means both rows render and `op=list-shipped-divergent` names both
4208
+ files and the differing fields. Tools compare as a set rather than as the raw
4209
+ line, because a reordered `tools:` declares the same surface and derives the
4210
+ same class. The body is never compared: the card does not render the system
4211
+ prompt, so a body-only edit does not make the row wrong. A read failure on
4212
+ either side also keeps the row, because an unreadable file cannot be proven
4213
+ equal to anything and keeping it hides nothing — but it is reported as a
4214
+ compare failure, not as a divergence, since nothing was read to disagree. An
4215
+ authored file that does not parse therefore no longer suppresses its live
4216
+ twin. **That test answers a different
4217
+ question to the write gates.** `disable` and `enable` still key on the filename
4218
+ alone, because authorship is a property of the tree a file sits in, not of its
4219
+ frontmatter, so the operator keeps the control on an agent of their own whose
4220
+ frontmatter is broken. **The comparison covers both account directories, live
4221
+ and quarantine.** It was live-only while only a shipped agent could be
4222
+ quarantined; now that an authored specialist can be switched off, its
4223
+ specialist row carries the plugin, the skills, the detail route and its own
4224
+ disabled state, so a second row from the shipped walk would be a duplicate with
4225
+ none of that. The bundled templates are never compared: a bundled name is
4226
+ shipped by definition and has no authored twin to be a copy of. Each exclusion
4227
+ logs `op=list-shipped-dedup file=<name>`; each kept pair logs
4228
+ `op=list-shipped-divergent`. Both counts ride `op=list` and the response, as
4229
+ `deduped` and `divergent`.
4165
4230
 
4166
4231
  The former "Public" flyout toggle in the account menu was removed; this page is
4167
4232
  the sole agent surface.
@@ -4331,7 +4396,7 @@ explicit `?plugin=` parameter, not inferred from `--` in the slug.
4331
4396
 
4332
4397
  | Route | Behaviour |
4333
4398
  |-------|-----------|
4334
- | `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent), its user-created specialists from `plugins/*/agents/*.md`, and its shipped specialists, each row tagged `kind` and `origin` and carrying `risk`, `riskiestTool`, `riskRule`, `riskReason`, `unresolved` and `disabled`. A live-directory candidate whose filename also exists under `plugins/*/agents/` is dropped and counted, so an activated user-created agent is listed once. Returns `{agents, accountId, skipped, specialistsSkipped, shippedSkipped, deduped, riskSurfaceFailed}`. 401 when the session maps to no account. |
4399
+ | `GET /api/admin/agents` | Session-scoped. Lists the session account's public `agents/*/` dirs (never the `admin` agent), its user-created specialists from `plugins/*/agents/*.md`, and its shipped specialists, each row tagged `kind` and `origin` and carrying `risk`, `riskiestTool`, `riskRule`, `riskReason`, `unresolved` and `disabled`. A live-directory or quarantine-directory candidate whose filename also exists under `plugins/*/agents/` is dropped and counted only when the two files agree on `name`, `description` and the `tools` set, so an activated user-created agent is listed once and a divergent or colliding pair renders both rows. Returns `{agents, accountId, skipped, specialistsSkipped, shippedSkipped, deduped, divergent, riskSurfaceFailed}`. 401 when the session maps to no account. |
4335
4400
  | `GET /api/admin/agents/:slug` | Session-scoped. Without `?plugin=` or `?origin=`, returns a public agent's config fields + four owned docs + a `present` map (a missing or unreadable doc is `''`/`present.<role>=false`, never a 500). With `?plugin=`, that plugin's user-created specialist; with `?origin=shipped`, the shipped one. Both specialist shapes add `{risk, riskiestTool, riskRule, riskReason, unresolved, byTool, disabled}`, where `byTool` is one class per declared tool. Selection is explicit rather than inferred, because a premium file and a user-created specialist under a plugin of the same name produce the same slug. Unknown 404s, 401 as above. |
4336
4401
  | `POST /api/admin/agents/:slug/disable?accountId=` | Agents the operator authored only, enforced here rather than left to the UI: a slug NOT present under `plugins/*/agents/` 404s (`reason=not-user-created`), and the message names suspending the routines as what stops a shipped agent instead. An authored slug with no copy in `specialists/agents/` 409s (`reason=not-dispatchable`): it is already not dispatchable, so there is nothing to move and nothing to record. Otherwise it moves the file from `specialists/agents/` to `specialists/agents-disabled/` and returns `{ok, moved:true}` — the only outcome, since the two other cases refuse. It writes NO store entry: `agents-disabled.json` exists to stop provisioning re-delivering a withheld shipped file, and provisioning never writes the account plugin tree. Returns `routingReconciled`, false when either AGENTS.md pass threw; the move already happened and already stopped dispatch, so the request is not failed and the caller is told rather than left assuming the prose is right. Same `accountId` contract as delete. |
4337
4402
  | `POST /api/admin/agents/:slug/enable?accountId=` | Takes NO authorship check, and that asymmetry with disable is deliberate: an install that switched a shipped agent off under the old gate must still reach this route, or inverting the gate strands it. For an authored slug the quarantined file is the whole state — 404 when it is absent, `restored:true` when it moves back, and the store is never touched. For a shipped slug it covers both of the old disable's outcomes: a quarantined file moves back (`restored:true`), and a bundled-only agent has nothing to move so clearing the store entry is the whole job (`restored:false`), which is what stops provisioning withholding it. 404 only when neither the quarantine directory nor the store knows the slug. The store is read strictly first, because this is the read half of a read-modify-write that rewrites the file whole, and it is written back only when an entry was actually removed — so enabling never creates a store an account did not have. |
@@ -4377,11 +4442,13 @@ on** (`accountId=<id8>`) — a well-formed answer about the wrong account is
4377
4442
  otherwise indistinguishable from a correct one, which is exactly how an empty
4378
4443
  Agents page survived undiagnosed on a multi-account install.
4379
4444
 
4380
- - `op=list accountId=<id8> agents=<n> specialists=<m> skipped=<x> specialistsSkipped=<y>`
4381
- the heartbeat. Compare `accountId` against the account the operator is
4382
- viewing; `skipped>0`/`specialistsSkipped>0` is a malformed-source signal the
4383
- page renders as a banner, and `specialists=0` with user plugins present is the
4384
- broken-walk signature.
4445
+ - `op=list accountId=<id8> agents=<n> specialists=<m> shipped=<s> skipped=<x>
4446
+ specialistsSkipped=<y> shippedSkipped=<z> deduped=<d> divergent=<v>` the
4447
+ heartbeat. Compare `accountId` against the account the operator is viewing;
4448
+ `skipped>0`/`specialistsSkipped>0`/`shippedSkipped>0` is a malformed-source
4449
+ signal the page renders as a banner, and `specialists=0` with user plugins
4450
+ present is the broken-walk signature. `deduped` and `divergent` are described
4451
+ under their own lines below.
4385
4452
  - `op=detail accountId=<id8> slug=<…> kind=<public|specialist>` — with
4386
4453
  `present=<comma-list|none>` on the public branch, where a role absent for an
4387
4454
  agent expected complete is the missing-doc signature. One
@@ -4408,13 +4475,32 @@ Agents page survived undiagnosed on a multi-account install.
4408
4475
  - `op=delete accountId=<id8> slug=<…> outcome=<ok|failed>` with
4409
4476
  `reason=<graph-cleanup-failed|rm-error>` on failure (files preserved on a
4410
4477
  graph-cleanup throw).
4411
- - `op=list-shipped-dedup file=<name>` — one line per live-directory candidate
4412
- dropped as an authored duplicate, with `deduped=<n>` on the `op=list` line and
4413
- in the response. `shipped + shippedSkipped + deduped` equals the number of
4414
- distinct `.md` names across the three source directories, so a listing that
4415
- does not add up is visible without reproducing anything. The over-exclusion to
4416
- watch for is a `deduped` line naming a file that is also a bundled or premium
4417
- agent: the authored file then hides a genuinely shipped one.
4478
+ - `op=list-shipped-dedup file=<name>` — one line per live-directory or
4479
+ quarantine-directory candidate dropped as an authored duplicate, with
4480
+ `deduped=<n>` on the `op=list` line and in the response. `shipped +
4481
+ shippedSkipped + deduped` equals the number of distinct `.md` names across the
4482
+ three source directories, so a listing that does not add up is visible without
4483
+ reproducing anything. The over-exclusion to watch for is a `deduped` line
4484
+ naming a file that is also a bundled or premium agent: the authored file then
4485
+ hides a genuinely shipped one, which now requires it to have reproduced that
4486
+ agent's `name`, `description` and `tools` exactly.
4487
+ - `op=list-shipped-divergent file=<name> found=<path> authored=<path>
4488
+ fields=<comma-list>` — one line per candidate KEPT because it does not match
4489
+ the authored copy, naming both files and which of `name`, `description`,
4490
+ `tools` differ. Without it a divergence emits nothing: both files parse and the
4491
+ row renders green, so "the row is stale" is indistinguishable from "the agent
4492
+ changed" without diffing the account tree by hand. `found=` rather than
4493
+ `live=`, because the candidate may have come from the quarantine directory and
4494
+ the path itself says which. `divergent=<n>` rides the `op=list` line and the
4495
+ response. A kept candidate is parsed like any other, so it lands in `shipped`,
4496
+ or in `shippedSkipped` when its own frontmatter carries no `name:` — either
4497
+ way `divergent` is never a fourth term and the identity above is unchanged.
4498
+ - `op=list-shipped-compare-failed file=<name> side=<found|authored>
4499
+ reason="<msg>"` — a frontmatter read threw during that comparison. The row is
4500
+ kept, because an unreadable file proves nothing, but it is **not** counted in
4501
+ `divergent`: "could not read one of them" and "the two disagree" are different
4502
+ facts, and reporting the first as the second would put drift in the count that
4503
+ nobody observed.
4418
4504
  - `[agents] op=risk account=<id8> slug=<name> origin=<shipped|public|specialist>
4419
4505
  class=<green|amber|red> rule=<id|none> riskiestTool=<tool> tools=<n>
4420
4506
  unresolved=<n> unresolvedTools=<comma-list|none> disabled=<bool>` — one line
@@ -6115,6 +6201,12 @@ The `brand-excluded` branch closes the recurring crash-restart loop on brands th
6115
6201
 
6116
6202
  **Per-spawn signals (server.log).** Every spawn emits `pty-spawn-mcp-config servers=<N> tools=<M> bytes=<B> path=<…>` once, plus one `pty-spawn-agents-dir role=<admin|public> path=<…>` per added directory. Specialist spawns additionally emit `pty-spawn-allowlist specialist=<name> count=<N> stripped=<S> sourced-from=agent-frontmatter` where `stripped` is the count of brand-excluded tool names removed before argv emission. The diagnostic one-liner is `grep -E 'pty-spawn-mcp-config|pty-spawn-agents-dir|pty-spawn-allowlist|mcp-config-allowlist-coverage|specialist-tool-strip|boot-failed reason=' ~/.<brand>/logs/server.log | tail -50`.
6117
6203
 
6204
+ **Is the account's own specialist actually being used? (server.log).** An account can have its own specialists, have them registered and resolvable, and still never use them. Every reachability signal reads green through that state — the registry reconcile says a specialist CAN be reached, the roster census says it is advertised, the preference audit says whether the right one wrote an owned artefact — and none of them counts a dispatch. Skipped delegation writes no record either: the admin doing the work inline logs an ordinary success. Two signals close it, both in `server.log`.
6205
+
6206
+ Every five minutes, one line per provisioned account, emitted including all-zero so the line's presence proves the check ran: `[specialist-census] account=<id8> declared=<n> dispatched7d=<n> bypassed7d=<n>`. `declared` counts the specialists the account authored itself, not the ones the platform ships — provisioning copies every shipped template into each account, and one of them declares roughly 130 tools, so counting every card would make the other two numbers meaningless. `dispatched7d` counts dispatches in the last seven days whose subagent `agentType` names one of those authored specialists; `bypassed7d` counts direct main-conversation calls to a tool one of them declares, native tools excluded. **`declared` above zero with `dispatched7d` at zero is the fault.** An account whose card set cannot be read emits `[specialist-census] account=<id8> op=degraded reason=<r>` and no counts at all, because a count nobody measured is worse than no count.
6207
+
6208
+ Against that, a PreToolUse gate ([`specialist-dispatch-gate.sh`](../../admin/hooks/specialist-dispatch-gate.sh)) refuses the admin a tool one of the account's own specialists declares, and names the specialist in the refusal: `[specialist-gate] op=refuse account=<id8> tool=<t> specialist=<name>`. The caller is identified by `agent_type` in the hook envelope, which Claude Code sets inside a subagent and leaves empty in the main conversation, so the gate constrains one seat and never filters a subagent's tool surface. It fails open on an uninspectable envelope and says so with `op=degraded reason=<r>` when the card set cannot be read. Rising refusals against a flat `dispatched7d` is a distinct fault from silence: the admin is being stopped and still not delegating, which needs a change to how it is briefed rather than to the gate. The diagnostic one-liner is `grep -E '\[specialist-census\]|\[specialist-gate\]' ~/.<brand>/logs/server.log | tail -50`.
6209
+
6118
6210
  **Channel follower cold-start retry.** Each `dispatchOnce`-based channel PTY session has one JSONL follower ([`platform/ui/app/lib/channel-pty-bridge/follower.ts`](../../../ui/app/lib/channel-pty-bridge/follower.ts)) reading `GET /<sessionId>/log?follow=1` and fanning each assistant `end_turn` out to the awaiting `dispatchOnce`. (WhatsApp and public webchat have since migrated to the native event channel — an MCP notification in, a `reply`-tool call out — and no longer use this follower; the remaining `dispatchOnce` channels are email and telegram. The webchat detail below is the historical record of the bug the retry fixed.) A freshly-spawned PTY has no JSONL on disk until claude flushes its first line; during that window the manager answers `202 {pending:true}`. The follower retries every `CHANNEL_PTY_FOLLOWER_RETRY_MS` (default 1000) until a 200 stream opens or `CHANNEL_PTY_FOLLOWER_PENDING_MAX_MS` elapses. The follower is shared across channels, so that window defaults to the longest channel turn window (whatsapp's `WHATSAPP_PTY_TURN_TIMEOUT_MS`, 300000 — longer than webchat's 120000) so it never abandons a turn the caller is still awaiting. Because public sessions idle-reap, every webchat greeting is the first turn of a fresh spawn and crosses this window — before the retry, a 202 (which satisfies `res.ok`) was consumed as a single non-event line, the stream ended, and the follower died silently, timing out every public turn. The lifecycle is greppable as `follower-connect status=<code>` → `follower-retry attempt=N reason=pending` → `follower-open` → `outbound bytes=N`; `follower-give-up reason=pending-timeout` marks the JSONL never appearing. A `reject reason=turn-timeout` with no preceding `follower-open` (and no manager `log-follow-open`) for that sessionId is the signature. See `.docs/gated-public-agents.md` "Webchat turn lifecycle" for the full tag list.
6119
6211
 
6120
6212
  **Brand-process start counter.** `platform/ui/server-init.cjs` increments a persistent counter at `/tmp/server-init-<accountId>-restart.count` on every fresh start and emits `[server-init] start count=<N> account=<accountId> counter-path=<…>` to `server.log`. /tmp clears on reboot, so a clean reboot starts the count fresh; any value `>1` between operator-observed reboots means the brand process (driven by its `Requires=<brand>-claude-session-manager.service` clause) is restarting. The diagnostic one-liner is `grep '\[server-init\] start' ~/.<brand>/logs/server.log | tail -5` — the trailing `count=` value is the loop depth without counting SIGTERMs.
@@ -6674,6 +6766,16 @@ Note: passing an explicit second argument (e.g. `logs-read.sh <key> agent-stream
6674
6766
 
6675
6767
  **Diagnose if it ever recurs:** grep the per-conversation stream log for `[browser-render]`. `rendered=true domBytes=<n>` is the healthy signal. `rendered=false outcome=cdp-unreachable` means no Chromium is listening on the brand's CDP port — confirm with `curl 127.0.0.1:<cdpPort>/json/version`. Other outcomes (`navigate-failed`, `load-timeout`, `evaluate-failed`) name the failed CDP step.
6676
6768
 
6769
+ ## Stylesheet text is visible on a client document, or a styled box has lost its rules
6770
+
6771
+ **Symptom:** A client-facing HTML document shows raw CSS as text on the page, usually above the header, and a box further down the same document renders unstyled (no border, no padding). Both are one fault, not two.
6772
+
6773
+ **Cause:** A `<style>` element opened inside another one. HTML parses style content as raw text, so the two do not nest: the first `</style>` closes the outer element, everything after it becomes body content and renders as visible text, and the rule immediately after the stray opener is eaten as part of an invalid selector.
6774
+
6775
+ **Resolution:** Delete the stray opener. Put the block in the body as its own `<style>` element, or merge its rules into the one already open. If the document is deployed, redeploy it, then confirm the rule is present in the parsed stylesheet — a changed byte count is not proof.
6776
+
6777
+ **Diagnose:** `grep -F '[html-structure-census]' ~/.<brand>/logs/server.log | tail -1` reports `accounts=`, `documents=` and `malformed=` from the last standing tick, and each malformed document has its own `op=malformed account=<id8> file="<path>" line=<n>` line naming the stray opener. The summary is emitted on every run including a zero count, so no line at all means the walk is broken rather than the install being clean, and `documents=0` means the same. A write that would produce the shape is refused before it lands, logging `[html-structure-gate] op=refuse` to the same file.
6778
+
6677
6779
  ## First user-domain write rejected by `[graph-write-gate] reject reason=no-admin-user`
6678
6780
 
6679
6781
  **Symptom:** Admin chat reports "couldn't save that — set up your business profile first" or `[graph-write-gate] reject reason=no-admin-user` appears in `server.log` on the operator's first non-bootstrap write (a website, service, opening hours, etc.). Reproduces on Minimal-onboarded installs from before the seed-stamping fix shipped.
@@ -9,6 +9,24 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-08-07 (0.1.574)
13
+
14
+ - An answer meant for you now reaches every channel you have live, not just the one you asked from. That had never actually worked, and nothing reported it.
15
+ - A document sent to a customer is now checked before it leaves. A malformed one could reach the customer with nothing looking at it, and the page-two header check was asserting against something the document never carries, so an oversized or missing header on later pages gave no signal at all.
16
+ - The agents list now describes the specialist that actually runs. It was showing you the authored copy while dispatch used a different one, so what you read and what ran could differ.
17
+ - An account whose specialists are present but never actually used is now detected and reported, rather than looking healthy indefinitely.
18
+ - Email sent through Outlook can now be accounted for. A draft could leave your Drafts folder without the assistant ever sending it, and no surface could say who did; each request is now recorded so the answer comes from a record rather than an inference.
19
+
20
+ ## 2026-08-07 (0.1.573)
21
+
22
+ - One piece of the platform now sends every answer to every channel, instead of each channel deciding for itself. A standing check compares what the conversation record holds against what was actually sent, so a missing message is reported rather than noticed.
23
+ - A healthy conversation sitting idle is no longer mistaken for a stuck one and shut down. A recovered conversation now also re-sends the answer it was holding.
24
+ - Your second message in an admin webchat reaches the conversation you are already in. It was being addressed to a name the running conversation had never subscribed to, so it could report a delivery failure and then arrive twice.
25
+ - A file the assistant sends you now opens. The download link was refused for anyone outside the account that produced it, so the link returned a not-found.
26
+ - Telegram now supports far more of what the service can do, rather than the four operations it exposed before.
27
+ - A routine that is switched off by a check now stops waking the assistant, and the check that woke it is recorded. The audits behind those checks now report somewhere that is actually read.
28
+ - Sending an email draft no longer reports success when the step before it was refused, which could send the same message twice.
29
+
12
30
  ## 2026-08-07 (0.1.572)
13
31
 
14
32
  - A new dispatch planner in the admin gives the office a week-grid view of each worker's day with per-day state and standing divergence checks.