@rubytech/create-maxy-code 0.1.419 → 0.1.427

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 (334) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +13 -0
  3. package/payload/platform/package-lock.json +5158 -2922
  4. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  5. package/payload/platform/plugins/admin/PLUGIN.md +5 -1
  6. package/payload/platform/plugins/admin/hooks/__tests__/datetime-inject.test.sh +119 -0
  7. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-compliance.test.sh +22 -0
  8. package/payload/platform/plugins/admin/hooks/datetime-inject.sh +115 -0
  9. package/payload/platform/plugins/admin/hooks/prompt-optimiser-compliance.sh +14 -1
  10. package/payload/platform/plugins/admin/mcp/dist/index.js +62 -4
  11. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  12. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +44 -0
  13. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +96 -0
  15. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
  16. package/payload/platform/plugins/admin/skills/datetime/SKILL.md +9 -2
  17. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +16 -2
  18. package/payload/platform/plugins/docs/references/cross-account-authority.md +8 -0
  19. package/payload/platform/plugins/docs/references/internals.md +4 -0
  20. package/payload/platform/plugins/docs/references/memory-guide.md +2 -0
  21. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  22. package/payload/platform/plugins/email/PLUGIN.md +2 -2
  23. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js +73 -4
  24. package/payload/platform/plugins/email/mcp/dist/__tests__/imap-drafts.test.js.map +1 -1
  25. package/payload/platform/plugins/email/mcp/dist/__tests__/mailbox-resolve.test.js +42 -1
  26. package/payload/platform/plugins/email/mcp/dist/__tests__/mailbox-resolve.test.js.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/index.js +1 -0
  28. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  29. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts +4 -0
  30. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
  31. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +5 -0
  32. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
  33. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +25 -3
  34. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts.map +1 -1
  35. package/payload/platform/plugins/email/mcp/dist/lib/imap.js +67 -12
  36. package/payload/platform/plugins/email/mcp/dist/lib/imap.js.map +1 -1
  37. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts +1 -0
  38. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -1
  39. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +1 -0
  40. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
  41. package/payload/platform/plugins/email/references/email-reference.md +8 -0
  42. package/payload/platform/plugins/memory/PLUGIN.md +3 -0
  43. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +30 -1
  44. package/payload/platform/plugins/memory/skills/preference-audit/SKILL.md +50 -0
  45. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
  46. package/payload/platform/plugins/scheduling/mcp/dist/index.js +5 -2
  47. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  48. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js +50 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/agent-dispatch.test.js.map +1 -1
  50. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts +2 -0
  51. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.d.ts.map +1 -0
  52. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js +22 -0
  53. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/scheduled-seat-id.test.js.map +1 -0
  54. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts +35 -0
  55. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.d.ts.map +1 -1
  56. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js +36 -0
  57. package/payload/platform/plugins/scheduling/mcp/dist/lib/agent-dispatch.js.map +1 -1
  58. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts +11 -0
  59. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.d.ts.map +1 -0
  60. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js +19 -0
  61. package/payload/platform/plugins/scheduling/mcp/dist/lib/scheduled-seat-id.js.map +1 -0
  62. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js +85 -3
  63. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/agent-turn-dispatch.test.js.map +1 -1
  64. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js +32 -1
  65. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/standing-audit.test.js.map +1 -1
  66. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +14 -1
  67. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  68. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +94 -4
  69. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  70. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +23 -7
  71. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  72. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel.test.d.ts +2 -0
  73. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel.test.d.ts.map +1 -0
  74. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel.test.js +30 -0
  75. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-cancel.test.js.map +1 -0
  76. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts +29 -0
  77. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.d.ts.map +1 -1
  78. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js +47 -2
  79. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-cancel.js.map +1 -1
  80. package/payload/platform/plugins/whatsapp/PLUGIN.md +5 -1
  81. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/caller-scope-headers.test.d.ts +2 -0
  82. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/caller-scope-headers.test.d.ts.map +1 -0
  83. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/caller-scope-headers.test.js +20 -0
  84. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/caller-scope-headers.test.js.map +1 -0
  85. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.d.ts +2 -0
  86. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.d.ts.map +1 -0
  87. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.js +15 -0
  88. package/payload/platform/plugins/whatsapp/mcp/dist/__tests__/resolve-read-account.test.js.map +1 -0
  89. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts +1 -1
  90. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.d.ts.map +1 -1
  91. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js +5 -2
  92. package/payload/platform/plugins/whatsapp/mcp/dist/call-api.js.map +1 -1
  93. package/payload/platform/plugins/whatsapp/mcp/dist/caller-scope-headers.d.ts +9 -0
  94. package/payload/platform/plugins/whatsapp/mcp/dist/caller-scope-headers.d.ts.map +1 -0
  95. package/payload/platform/plugins/whatsapp/mcp/dist/caller-scope-headers.js +18 -0
  96. package/payload/platform/plugins/whatsapp/mcp/dist/caller-scope-headers.js.map +1 -0
  97. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +17 -14
  98. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  99. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts +7 -0
  100. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts.map +1 -0
  101. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js +13 -0
  102. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js.map +1 -0
  103. package/payload/platform/plugins/work/PLUGIN.md +1 -1
  104. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.d.ts +2 -0
  105. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.d.ts.map +1 -0
  106. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.js +72 -0
  107. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.js.map +1 -0
  108. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-client-fields.test.d.ts +2 -0
  109. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-client-fields.test.d.ts.map +1 -0
  110. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-client-fields.test.js +67 -0
  111. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-client-fields.test.js.map +1 -0
  112. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.d.ts +2 -0
  113. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.d.ts.map +1 -0
  114. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.js +66 -0
  115. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-raised-by-env.test.js.map +1 -0
  116. package/payload/platform/plugins/work/mcp/dist/index.js +3 -0
  117. package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
  118. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts +6 -0
  119. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts.map +1 -1
  120. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js +41 -1
  121. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js.map +1 -1
  122. package/payload/platform/scripts/lib/provision-account-dir.sh +2 -1
  123. package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts +39 -0
  124. package/payload/platform/services/claude-session-manager/dist/account-admin-census.d.ts.map +1 -0
  125. package/payload/platform/services/claude-session-manager/dist/account-admin-census.js +122 -0
  126. package/payload/platform/services/claude-session-manager/dist/account-admin-census.js.map +1 -0
  127. package/payload/platform/services/claude-session-manager/dist/account-timezone.d.ts +40 -0
  128. package/payload/platform/services/claude-session-manager/dist/account-timezone.d.ts.map +1 -0
  129. package/payload/platform/services/claude-session-manager/dist/account-timezone.js +126 -0
  130. package/payload/platform/services/claude-session-manager/dist/account-timezone.js.map +1 -0
  131. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  132. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  133. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  134. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  135. package/payload/platform/services/claude-session-manager/dist/http-server.js +53 -2
  136. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  137. package/payload/platform/services/claude-session-manager/dist/index.js +52 -0
  138. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  139. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.d.ts +37 -0
  140. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.d.ts.map +1 -0
  141. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.js +80 -0
  142. package/payload/platform/services/claude-session-manager/dist/passive-dispatch-prune.js.map +1 -0
  143. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.d.ts +70 -0
  144. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.d.ts.map +1 -0
  145. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.js +129 -0
  146. package/payload/platform/services/claude-session-manager/dist/passive-intake-audit.js.map +1 -0
  147. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +17 -0
  148. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  149. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +40 -0
  150. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  151. package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
  152. package/payload/platform/services/telegram-channel/dist/instructions.js +37 -50
  153. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  154. package/payload/platform/services/telegram-channel/dist/notification.d.ts +22 -0
  155. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  156. package/payload/platform/services/telegram-channel/dist/notification.js +22 -1
  157. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  158. package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
  159. package/payload/platform/services/webchat-channel/dist/instructions.js +44 -78
  160. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  161. package/payload/platform/services/webchat-channel/dist/notification.d.ts +22 -0
  162. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  163. package/payload/platform/services/webchat-channel/dist/notification.js +22 -1
  164. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  165. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +42 -38
  166. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  167. package/payload/platform/services/whatsapp-channel/dist/notification.js +66 -82
  168. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  169. package/payload/platform/templates/account.json +2 -1
  170. package/payload/platform/templates/agents/admin/IDENTITY.md +3 -2
  171. package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
  172. package/payload/server/public/assets/{AdminLoginScreens-B1PkOF66.js → AdminLoginScreens-yAIhWcGc.js} +1 -1
  173. package/payload/server/public/assets/AdminShell-Bk1AFQ4D.js +2 -0
  174. package/payload/server/public/assets/{Checkbox-Bm9Er726.js → Checkbox-B_lfjsqd.js} +1 -1
  175. package/payload/server/public/assets/Transcript-D67OZnVM.js +2 -0
  176. package/payload/server/public/assets/admin-DuOhkvrZ.js +1 -0
  177. package/payload/server/public/assets/{arc-DyzjCWmw.js → arc-dVLpR-ec.js} +1 -1
  178. package/payload/server/public/assets/architecture-YZFGNWBL-CgeA3HPD.js +1 -0
  179. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-qWfElD16.js → architectureDiagram-Q4EWVU46-C6u_CVzb.js} +1 -1
  180. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DO2fFHC3.js → blockDiagram-DXYQGD6D-Cy8F5b8O.js} +1 -1
  181. package/payload/server/public/assets/browser-CCWufeXr.js +1 -0
  182. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Ca-q5gDe.js → c4Diagram-AHTNJAMY-DRXrlepj.js} +1 -1
  183. package/payload/server/public/assets/calendar-CNk1iPrA.js +1 -0
  184. package/payload/server/public/assets/channel-B04VBWf7.js +1 -0
  185. package/payload/server/public/assets/chat-7qB-dwBq.js +1 -0
  186. package/payload/server/public/assets/chevron-left-CpcDbHIz.js +1 -0
  187. package/payload/server/public/assets/{chunk-2KRD3SAO-CX2PxuDw.js → chunk-2KRD3SAO-fEyfhRLz.js} +1 -1
  188. package/payload/server/public/assets/{chunk-336JU56O-D8l6U4Ne.js → chunk-336JU56O-NUWO4y7q.js} +2 -2
  189. package/payload/server/public/assets/chunk-426QAEUC-DZkHcXRA.js +1 -0
  190. package/payload/server/public/assets/{chunk-4BX2VUAB-CG32Lkbh.js → chunk-4BX2VUAB-BnfiNR3l.js} +1 -1
  191. package/payload/server/public/assets/{chunk-4TB4RGXK-BnAQbO-v.js → chunk-4TB4RGXK-Bj_aJOz8.js} +1 -1
  192. package/payload/server/public/assets/{chunk-55IACEB6-ejqRH2M5.js → chunk-55IACEB6-aK0DNSKb.js} +1 -1
  193. package/payload/server/public/assets/{chunk-5FUZZQ4R-ClkicQ-T.js → chunk-5FUZZQ4R-DeqAkq5C.js} +1 -1
  194. package/payload/server/public/assets/{chunk-5PVQY5BW-Be-mE_rx.js → chunk-5PVQY5BW-SShlujAQ.js} +1 -1
  195. package/payload/server/public/assets/{chunk-67CJDMHE-DPsemed2.js → chunk-67CJDMHE-CZpzQ0DL.js} +1 -1
  196. package/payload/server/public/assets/{chunk-7N4EOEYR-BQRQJETl.js → chunk-7N4EOEYR-COnjO6jy.js} +1 -1
  197. package/payload/server/public/assets/{chunk-AA7GKIK3-B1wcAVCK.js → chunk-AA7GKIK3-C9V31L2z.js} +1 -1
  198. package/payload/server/public/assets/{chunk-BSJP7CBP-DIua053h.js → chunk-BSJP7CBP-DSwnSGe3.js} +1 -1
  199. package/payload/server/public/assets/{chunk-CIAEETIT-C9GHSCS9.js → chunk-CIAEETIT-CnRUTuta.js} +1 -1
  200. package/payload/server/public/assets/{chunk-EDXVE4YY-C1T3l1xj.js → chunk-EDXVE4YY-BexiL2LH.js} +1 -1
  201. package/payload/server/public/assets/{chunk-ENJZ2VHE-8kPcXEQC.js → chunk-ENJZ2VHE-aTklWdJQ.js} +1 -1
  202. package/payload/server/public/assets/{chunk-FMBD7UC4-Bhoeq9sD.js → chunk-FMBD7UC4-svcEBQzs.js} +1 -1
  203. package/payload/server/public/assets/{chunk-FOC6F5B3-BIBpJi8I.js → chunk-FOC6F5B3-CG7R7Xri.js} +1 -1
  204. package/payload/server/public/assets/{chunk-ICPOFSXX-DELXp6fj.js → chunk-ICPOFSXX-CGN7Z4x9.js} +2 -2
  205. package/payload/server/public/assets/{chunk-K5T4RW27-DdckXldP.js → chunk-K5T4RW27-CWBh78jw.js} +1 -1
  206. package/payload/server/public/assets/{chunk-KGLVRYIC-BJhXejKT.js → chunk-KGLVRYIC-Ctiq4q-5.js} +1 -1
  207. package/payload/server/public/assets/{chunk-LIHQZDEY-BSd8nyM2.js → chunk-LIHQZDEY-BII77Q7K.js} +1 -1
  208. package/payload/server/public/assets/{chunk-ORNJ4GCN-CBi55uxq.js → chunk-ORNJ4GCN-DT7R01H8.js} +1 -1
  209. package/payload/server/public/assets/{chunk-OYMX7WX6-Dfbzl8xh.js → chunk-OYMX7WX6-y-Jkbd4C.js} +1 -1
  210. package/payload/server/public/assets/chunk-QZHKN3VN-D9Mwgzti.js +1 -0
  211. package/payload/server/public/assets/{chunk-U2HBQHQK-BN1w8_Cq.js → chunk-U2HBQHQK-DzFo6KbQ.js} +1 -1
  212. package/payload/server/public/assets/{chunk-X2U36JSP-CrRay1NJ.js → chunk-X2U36JSP-CYSwgtLV.js} +1 -1
  213. package/payload/server/public/assets/{chunk-XPW4576I-C_ZE776O.js → chunk-XPW4576I-DHlM4tOn.js} +1 -1
  214. package/payload/server/public/assets/{chunk-YZCP3GAM-CUSlqWBY.js → chunk-YZCP3GAM-B20rTmLl.js} +1 -1
  215. package/payload/server/public/assets/{chunk-ZZ45TVLE-BdPUfSiD.js → chunk-ZZ45TVLE-C7hP4J8m.js} +1 -1
  216. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CIo_TzfF.js +1 -0
  217. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-SzQYvAUZ.js +1 -0
  218. package/payload/server/public/assets/clone-BVjcjsBA.js +1 -0
  219. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-D4E9htTz.js → cose-bilkent-S5V4N54A-nKjTv9sR.js} +1 -1
  220. package/payload/server/public/assets/{dagre-D1B5TQL1.js → dagre-Cx8LiiUZ.js} +1 -1
  221. package/payload/server/public/assets/{dagre-KV5264BT-ByuvYV7N.js → dagre-KV5264BT-BNJYTnls.js} +1 -1
  222. package/payload/server/public/assets/data-BnHcS27X.js +1 -0
  223. package/payload/server/public/assets/{diagram-5BDNPKRD-B-0KduTa.js → diagram-5BDNPKRD-CoAN5_f9.js} +1 -1
  224. package/payload/server/public/assets/{diagram-G4DWMVQ6-CSQmo3eB.js → diagram-G4DWMVQ6-pRmsN4_x.js} +1 -1
  225. package/payload/server/public/assets/{diagram-MMDJMWI5-DK0kWiB3.js → diagram-MMDJMWI5-CRoylyp4.js} +1 -1
  226. package/payload/server/public/assets/{diagram-TYMM5635-CYbK2reZ.js → diagram-TYMM5635-BT3ZtG5b.js} +1 -1
  227. package/payload/server/public/assets/{dist-CADCHk0t.js → dist-CRpLAnCP.js} +1 -1
  228. package/payload/server/public/assets/{erDiagram-SMLLAGMA-C-JQHvJF.js → erDiagram-SMLLAGMA-DSKws1ZV.js} +1 -1
  229. package/payload/server/public/assets/file-download-Hf_UILaA.js +1 -0
  230. package/payload/server/public/assets/{flatten-ROVzlo1u.js → flatten-jn8bZgcI.js} +1 -1
  231. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-OTOd2czg.js → flowDiagram-DWJPFMVM-C2L38ZE5.js} +1 -1
  232. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BWJ778ou.js → ganttDiagram-T4ZO3ILL-Bm2h-vjm.js} +1 -1
  233. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DzQ18i0T.js +1 -0
  234. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CkNcyISe.js → gitGraphDiagram-UUTBAWPF-BoowbF7u.js} +1 -1
  235. package/payload/server/public/assets/graph-klLugHEK.js +51 -0
  236. package/payload/server/public/assets/{graph-labels-BFG4bjXQ.js → graph-labels-BLx-0vyT.js} +1 -1
  237. package/payload/server/public/assets/{graphlib-8z59Ng6C.js → graphlib-BvT2s_o1.js} +1 -1
  238. package/payload/server/public/assets/info-OMHHGYJF-CwsoaVzV.js +1 -0
  239. package/payload/server/public/assets/infoDiagram-42DDH7IO-ieNsH2bV.js +2 -0
  240. package/payload/server/public/assets/{isEmpty-DXf_sApt.js → isEmpty-Dkk6_L-B.js} +1 -1
  241. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-Cg7pRRGp.js → ishikawaDiagram-UXIWVN3A-nxcmj-IX.js} +1 -1
  242. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CZT_4r_Z.js → journeyDiagram-VCZTEJTY-q5SNIvmS.js} +1 -1
  243. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Dp1Vd5YM.js → kanban-definition-6JOO6SKY-BugG9Q1o.js} +1 -1
  244. package/payload/server/public/assets/{line-UPAcwY66.js → line-MZCQLR-q.js} +1 -1
  245. package/payload/server/public/assets/{linear-BeeahLKw.js → linear-DmQGNk-g.js} +1 -1
  246. package/payload/server/public/assets/{maximize-2-DkZ40lSl.js → maximize-2-CnNQnj_v.js} +1 -1
  247. package/payload/server/public/assets/{mermaid-parser.core-CLKle8df.js → mermaid-parser.core-DvoeqLxw.js} +2 -2
  248. package/payload/server/public/assets/{mermaid.core-CwRj5rL5.js → mermaid.core-BLaw7iG4.js} +3 -3
  249. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CNQUY9UJ.js → mindmap-definition-QFDTVHPH-DMkt10Vc.js} +1 -1
  250. package/payload/server/public/assets/operator-CRcySYv9.js +1 -0
  251. package/payload/server/public/assets/{ordinal-B2r5L1v4.js → ordinal-DuJYM7p1.js} +1 -1
  252. package/payload/server/public/assets/packet-4T2RLAQJ-DxB-bO4L.js +1 -0
  253. package/payload/server/public/assets/page-CVo3CWIY.js +1 -0
  254. package/payload/server/public/assets/page-DPTbZRwl.js +32 -0
  255. package/payload/server/public/assets/{pdf-render-m08Xm2Nm.js → pdf-render-riHufgrn.js} +1 -1
  256. package/payload/server/public/assets/pie-ZZUOXDRM-PSdKvB8-.js +1 -0
  257. package/payload/server/public/assets/{pieDiagram-DEJITSTG-Scnq58FX.js → pieDiagram-DEJITSTG-DRp4lD2B.js} +1 -1
  258. package/payload/server/public/assets/play-yEkd7ouW.js +1 -0
  259. package/payload/server/public/assets/public-Fi6ccYaE.js +1 -0
  260. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-wPfZo_vs.js → quadrantDiagram-34T5L4WZ-DkT63Dn4.js} +1 -1
  261. package/payload/server/public/assets/radar-PYXPWWZC-D8FqRiWq.js +1 -0
  262. package/payload/server/public/assets/{reduce-CFlDlJf5.js → reduce-COyotL2V.js} +1 -1
  263. package/payload/server/public/assets/{requirementDiagram-MS252O5E-yYkH12K2.js → requirementDiagram-MS252O5E-65GGtDkc.js} +1 -1
  264. package/payload/server/public/assets/rotate-ccw-xHksz9mr.js +1 -0
  265. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-fVWRBzzV.js → sankeyDiagram-XADWPNL6-D1vRwLWd.js} +1 -1
  266. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-Dbap3X6h.js → sequenceDiagram-FGHM5R23-Bp47ZSN2.js} +1 -1
  267. package/payload/server/public/assets/square-BZqaxg7D.js +1 -0
  268. package/payload/server/public/assets/{src-Bi0fLV9t.js → src-xN1oULc8.js} +1 -1
  269. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DkPLAqIh.js → stateDiagram-FHFEXIEX-BZHHcP_e.js} +1 -1
  270. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-qVtoGx56.js +1 -0
  271. package/payload/server/public/assets/tasks-COWmfts8.js +1 -0
  272. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-DabKBJyd.js → timeline-definition-GMOUNBTQ-DZ1wUyj-.js} +1 -1
  273. package/payload/server/public/assets/treeView-SZITEDCU-i-w4rJmQ.js +1 -0
  274. package/payload/server/public/assets/treemap-W4RFUUIX-Dij6AlAj.js +1 -0
  275. package/payload/server/public/assets/useSubAccountSwitcher-DADvGnr1.js +9 -0
  276. package/payload/server/public/assets/useSubAccountSwitcher-ayajZz8T.css +1 -0
  277. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CaKT7c-c.js → vennDiagram-DHZGUBPP-CttBahsn.js} +1 -1
  278. package/payload/server/public/assets/wardley-RL74JXVD-BJhOQVqW.js +1 -0
  279. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BZ5TTSOL.js → wardleyDiagram-NUSXRM2D-Y5tWieZg.js} +1 -1
  280. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-CLfeklJ9.js → xychartDiagram-5P7HB3ND-C78A9qbe.js} +1 -1
  281. package/payload/server/public/browser.html +7 -5
  282. package/payload/server/public/calendar.html +8 -6
  283. package/payload/server/public/chat.html +15 -12
  284. package/payload/server/public/data.html +11 -10
  285. package/payload/server/public/graph.html +12 -10
  286. package/payload/server/public/index.html +16 -14
  287. package/payload/server/public/operator.html +17 -14
  288. package/payload/server/public/public.html +15 -12
  289. package/payload/server/public/tasks.html +18 -0
  290. package/payload/server/server.js +775 -287
  291. package/payload/server/public/assets/AdminShell-D8wNArGQ.js +0 -2
  292. package/payload/server/public/assets/Transcript-CBIyQ8T5.js +0 -2
  293. package/payload/server/public/assets/admin-CWM9G_yA.js +0 -1
  294. package/payload/server/public/assets/architecture-YZFGNWBL-XiVs-N7Z.js +0 -1
  295. package/payload/server/public/assets/browser-D8QhNffV.js +0 -1
  296. package/payload/server/public/assets/calendar-qob5K4t_.js +0 -1
  297. package/payload/server/public/assets/channel-CYBkRz7E.js +0 -1
  298. package/payload/server/public/assets/chat-BNrxVUF6.js +0 -1
  299. package/payload/server/public/assets/chevron-left-BQThFVrd.js +0 -1
  300. package/payload/server/public/assets/chunk-426QAEUC-CPhOHriU.js +0 -1
  301. package/payload/server/public/assets/chunk-QZHKN3VN-CDnAAZVJ.js +0 -1
  302. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BdXUKVrS.js +0 -1
  303. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DAq-uJr_.js +0 -1
  304. package/payload/server/public/assets/clone-DQl28-jK.js +0 -1
  305. package/payload/server/public/assets/data-C-EqNbRi.js +0 -1
  306. package/payload/server/public/assets/gitGraph-7Q5UKJZL-BM-KaDZy.js +0 -1
  307. package/payload/server/public/assets/graph-DZ2HMpIB.js +0 -51
  308. package/payload/server/public/assets/info-OMHHGYJF-CtZajyAB.js +0 -1
  309. package/payload/server/public/assets/infoDiagram-42DDH7IO-C-_WYlmI.js +0 -2
  310. package/payload/server/public/assets/operator-FrXrSsBj.js +0 -1
  311. package/payload/server/public/assets/packet-4T2RLAQJ-T-SYedp7.js +0 -1
  312. package/payload/server/public/assets/page-CHWMI9Tm.js +0 -32
  313. package/payload/server/public/assets/page-CXKV01gT.js +0 -1
  314. package/payload/server/public/assets/pie-ZZUOXDRM-BWECFVVd.js +0 -1
  315. package/payload/server/public/assets/public-2p1pJ4WF.js +0 -1
  316. package/payload/server/public/assets/radar-PYXPWWZC-Dx0YUklX.js +0 -1
  317. package/payload/server/public/assets/rotate-ccw-D3o_bMBA.js +0 -1
  318. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DceWO1u6.js +0 -1
  319. package/payload/server/public/assets/treeView-SZITEDCU-C3197Rac.js +0 -1
  320. package/payload/server/public/assets/treemap-W4RFUUIX-BPrpx29h.js +0 -1
  321. package/payload/server/public/assets/useSubAccountSwitcher-8dLvF3Ux.js +0 -9
  322. package/payload/server/public/assets/useSubAccountSwitcher-BUvlETHN.css +0 -1
  323. package/payload/server/public/assets/wardley-RL74JXVD-DW4Jlbhw.js +0 -1
  324. /package/payload/server/public/assets/{_baseFor-BVuIxZJh.js → _baseFor-Bjj9It3Q.js} +0 -0
  325. /package/payload/server/public/assets/{admin-types-hioowVct.js → admin-types-ClHBm0ni.js} +0 -0
  326. /package/payload/server/public/assets/{array-B9UHiPd-.js → array-BrrxEo12.js} +0 -0
  327. /package/payload/server/public/assets/{chunk-CAM3fms7.js → chunk-Ck5AOu5v.js} +0 -0
  328. /package/payload/server/public/assets/{cytoscape.esm-D5_TRwB-.js → cytoscape.esm-CTWJxUxo.js} +0 -0
  329. /package/payload/server/public/assets/{defaultLocale-5eAKkKJC.js → defaultLocale-CFDbRV4k.js} +0 -0
  330. /package/payload/server/public/assets/{init-DlZdxViB.js → init-dXCzGgLP.js} +0 -0
  331. /package/payload/server/public/assets/{katex-Xo0SyWvP.js → katex-CHA1JFYJ.js} +0 -0
  332. /package/payload/server/public/assets/{path-6uRLdFF7.js → path-CCRpdXhK.js} +0 -0
  333. /package/payload/server/public/assets/{preload-helper-CvXuAEud.js → preload-helper-CH6UZRzu.js} +0 -0
  334. /package/payload/server/public/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "admin",
3
- "description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
3
+ "description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: admin
3
3
  surface: platform
4
- description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
4
+ description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), admin-hostname (the admin-host twin of public-hostname — resolves the host that serves /calendar and the rest of the admin shell behind sign-in from the same ingress + alias + operator files, so agents point the operator at the admin /calendar URL instead of the public host that 404s it), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, the multi-account managed-service lifecycle tools (account_list, account_create, account_delete, account_purge, account_channel_routing, account_channel_routing_set — one Rubytech operator hosting N client accounts on one house install; create/delete/purge are operator-only and purge is two-step with a typed confirmation; account_create requires a managing house admin (its userId) and refuses without one, recording it as the sub-account's managingAdminUserId; account_reassign_admin repoints a client sub-account's managing admin to another live house admin (the repair path for a dangling managingAdminUserId); account_channel_routing_set repoints the install's single WhatsApp/Telegram inbound stream to a different sub-account), admin-add, admin-remove (refuses to remove a house admin who still manages one or more client sub-accounts, naming them so the operator reassigns each with account_reassign_admin first — never silently orphans them), admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), skill-search (free-text intent → skill resolver: ranks every plugin skill's SKILL.md name+description against a query and returns the top owners with their canonical skills/<slug>/ path, so intent→skill mapping survives the available-skills menu leaving context after a compaction), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
5
5
  tools:
6
6
  - name: system-status
7
7
  publicAllowlist: false
@@ -33,6 +33,9 @@ tools:
33
33
  - name: account_create
34
34
  publicAllowlist: false
35
35
  adminAllowlist: true
36
+ - name: account_reassign_admin
37
+ publicAllowlist: false
38
+ adminAllowlist: true
36
39
  - name: account_delete
37
40
  publicAllowlist: false
38
41
  adminAllowlist: true
@@ -175,6 +178,7 @@ Tools are available via the `admin` MCP server.
175
178
  - `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).
176
179
  - `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.
177
180
  - `hooks/prompt-optimiser-directive.sh` — **UserPromptSubmit hook.** Injects the standing prompt-optimiser restatement directive plus the per-turn three-tier routing ladder as `additionalContext`: **(1)** delegate to the specialist that owns the deliverable via the Agent tool — 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 neither fits, author inline — freestyle is 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.
181
+ - `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.
178
182
  - `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).
179
183
  - **Turn recorder — removed entirely.** The `turn-completed-graph-write.sh` Stop hook, the `/api/admin/claude-sessions` loopback bypass it relied on, the `[turn-recorder]` emitters, the envelope walker, and the recorder-auto-archive subscriber are deleted. It had been dormant for a long time (never re-registered in settings.json); the admin now writes to the graph by delegating to `database-operator` via the Task tool inside the live session, and the on-demand `/insight` pass (`skills/insight/SKILL.md`, a registered admin skill) is the per-session review. There is no per-turn spawn.
180
184
 
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # Regression test for datetime-inject.sh.
3
+ #
4
+ # The hook injects a fresh <datetime> block as UserPromptSubmit additionalContext
5
+ # every turn, reporting "now" in $MAXY_ACCOUNT_TZ (the operator's graph timezone,
6
+ # stamped at spawn) or the server zone when that env is empty.
7
+ #
8
+ # Covers:
9
+ # 1. MAXY_ACCOUNT_TZ set -> exit 0, valid JSON, additionalContext has
10
+ # the <datetime> block, the named zone, and a UTC: line; source=graph.
11
+ # 2. MAXY_ACCOUNT_TZ empty -> exit 0, block falls back to the server zone
12
+ # and labels source as server-timezone.
13
+ # 3. Breadcrumb -> $LOG_DIR/datetime-inject.log gains a line
14
+ # with source= and refresh=per-turn.
15
+ # 4. python3 absent -> exit 0, empty stdout (fail-open).
16
+ # 5. node absent -> exit 0, empty stdout (fail-open).
17
+
18
+ set -u
19
+
20
+ HOOK="$(cd "$(dirname "$0")/.." && pwd)/datetime-inject.sh"
21
+ if [[ ! -x "$HOOK" ]]; then
22
+ echo "FAIL: $HOOK not executable" >&2
23
+ exit 1
24
+ fi
25
+
26
+ PASS=0
27
+ FAIL=0
28
+ STDIN='{"hook_event_name":"UserPromptSubmit","prompt":"when is the meeting","session_id":"sess-abc123"}'
29
+
30
+ # Case 1 — MAXY_ACCOUNT_TZ set: block reports that zone, source=graph.
31
+ out=$(printf '%s' "$STDIN" | MAXY_ACCOUNT_TZ="Europe/London" LOG_DIR="" bash "$HOOK" 2>/dev/null); ex=$?
32
+ if [[ "$ex" -eq 0 ]] && printf '%s' "$out" | python3 -c '
33
+ import json, sys
34
+ d = json.load(sys.stdin)
35
+ h = d["hookSpecificOutput"]
36
+ assert h["hookEventName"] == "UserPromptSubmit", "wrong hookEventName"
37
+ ctx = h["additionalContext"]
38
+ assert "<datetime>" in ctx and "</datetime>" in ctx, "no datetime block"
39
+ assert "Europe/London" in ctx, "zone not named"
40
+ assert "UTC:" in ctx, "no UTC line"
41
+ assert "account-timezone" in ctx, "source label missing for graph tz"
42
+ ' 2>/dev/null; then
43
+ echo "PASS: MAXY_ACCOUNT_TZ set -> datetime block in named zone"; PASS=$((PASS+1))
44
+ else
45
+ echo "FAIL: graph-tz injection (exit=$ex stdout=$out)" >&2; FAIL=$((FAIL+1))
46
+ fi
47
+
48
+ # Case 2 — MAXY_ACCOUNT_TZ empty: falls back to the server zone, labels it.
49
+ out2=$(printf '%s' "$STDIN" | MAXY_ACCOUNT_TZ="" LOG_DIR="" bash "$HOOK" 2>/dev/null); ex2=$?
50
+ if [[ "$ex2" -eq 0 ]] && printf '%s' "$out2" | python3 -c '
51
+ import json, sys
52
+ ctx = json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"]
53
+ assert "<datetime>" in ctx, "no datetime block"
54
+ assert "server-timezone" in ctx, "server fallback not labelled"
55
+ assert "UTC:" in ctx, "no UTC line"
56
+ ' 2>/dev/null; then
57
+ echo "PASS: empty MAXY_ACCOUNT_TZ -> server-zone fallback labelled"; PASS=$((PASS+1))
58
+ else
59
+ echo "FAIL: server fallback (exit=$ex2 stdout=$out2)" >&2; FAIL=$((FAIL+1))
60
+ fi
61
+
62
+ # Case 2b — MAXY_ACCOUNT_TZ set but NOT a real IANA zone: the node validation
63
+ # rejects it, falls back to the server zone, and labels the source neutrally as
64
+ # "unavailable" (never falsely "unset" — the value was present, just unusable).
65
+ out2b=$(printf '%s' "$STDIN" | MAXY_ACCOUNT_TZ="Not/AZone" LOG_DIR="" bash "$HOOK" 2>/dev/null); ex2b=$?
66
+ if [[ "$ex2b" -eq 0 ]] && printf '%s' "$out2b" | python3 -c '
67
+ import json, sys
68
+ ctx = json.load(sys.stdin)["hookSpecificOutput"]["additionalContext"]
69
+ assert "<datetime>" in ctx, "no datetime block"
70
+ assert "server-timezone (account timezone unavailable)" in ctx, "invalid tz not labelled unavailable"
71
+ assert "Not/AZone" not in ctx, "rejected zone leaked into the block"
72
+ ' 2>/dev/null; then
73
+ echo "PASS: set-but-invalid MAXY_ACCOUNT_TZ -> server fallback, labelled unavailable"; PASS=$((PASS+1))
74
+ else
75
+ echo "FAIL: invalid-tz fallback (exit=$ex2b stdout=$out2b)" >&2; FAIL=$((FAIL+1))
76
+ fi
77
+
78
+ # Case 3 — breadcrumb carries source= and refresh=per-turn.
79
+ LOGD=$(mktemp -d)
80
+ printf '%s' "$STDIN" | MAXY_ACCOUNT_TZ="Europe/London" LOG_DIR="$LOGD" bash "$HOOK" >/dev/null 2>&1
81
+ if [[ -f "$LOGD/datetime-inject.log" ]] \
82
+ && grep -q "\[datetime-inject\]" "$LOGD/datetime-inject.log" \
83
+ && grep -q "source=" "$LOGD/datetime-inject.log" \
84
+ && grep -q "refresh=per-turn" "$LOGD/datetime-inject.log"; then
85
+ echo "PASS: breadcrumb has source= and refresh=per-turn"; PASS=$((PASS+1))
86
+ else
87
+ echo "FAIL: breadcrumb missing (log=$(cat "$LOGD/datetime-inject.log" 2>/dev/null))" >&2; FAIL=$((FAIL+1))
88
+ fi
89
+ rm -rf "$LOGD"
90
+
91
+ # Case 4 — python3 absent -> fail-open (exit 0, empty stdout).
92
+ FAKEBIN=$(mktemp -d)
93
+ for util in cat printf env bash node date; do
94
+ src=$(command -v "$util" 2>/dev/null) && ln -sf "$src" "$FAKEBIN/$util" 2>/dev/null
95
+ done
96
+ nopy_out=$(printf '%s' "$STDIN" | PATH="$FAKEBIN" MAXY_ACCOUNT_TZ="Europe/London" bash "$HOOK" 2>/dev/null); nopy_exit=$?
97
+ if [[ "$nopy_exit" -eq 0 && -z "$nopy_out" ]]; then
98
+ echo "PASS: python3 absent -> exit 0, empty stdout (fail-open)"; PASS=$((PASS+1))
99
+ else
100
+ echo "FAIL: python3 absent (exit=$nopy_exit stdout=$nopy_out)" >&2; FAIL=$((FAIL+1))
101
+ fi
102
+ rm -rf "$FAKEBIN"
103
+
104
+ # Case 5 — node absent -> fail-open (exit 0, empty stdout).
105
+ FAKEBIN2=$(mktemp -d)
106
+ for util in cat printf env bash python3 date; do
107
+ src=$(command -v "$util" 2>/dev/null) && ln -sf "$src" "$FAKEBIN2/$util" 2>/dev/null
108
+ done
109
+ nonode_out=$(printf '%s' "$STDIN" | PATH="$FAKEBIN2" MAXY_ACCOUNT_TZ="Europe/London" bash "$HOOK" 2>/dev/null); nonode_exit=$?
110
+ if [[ "$nonode_exit" -eq 0 && -z "$nonode_out" ]]; then
111
+ echo "PASS: node absent -> exit 0, empty stdout (fail-open)"; PASS=$((PASS+1))
112
+ else
113
+ echo "FAIL: node absent (exit=$nonode_exit stdout=$nonode_out)" >&2; FAIL=$((FAIL+1))
114
+ fi
115
+ rm -rf "$FAKEBIN2"
116
+
117
+ echo "----"
118
+ echo "PASS=$PASS FAIL=$FAIL"
119
+ [[ "$FAIL" -eq 0 ]]
@@ -60,6 +60,16 @@ CHANNEL_REFRAME_TRIVIAL='{"type":"user","isMeta":true,"message":{"role":"user","
60
60
  # registration key `channel`, and the server embeds an inner source tag, so the
61
61
  # reframe arrives DOUBLE-wrapped. The strip must iterate to reach ## Context.
62
62
  CHANNEL_REFRAME_MULTI='{"type":"user","isMeta":true,"message":{"role":"user","content":"<channel source=\"channel\">\n<channel source=\"whatsapp\">\n## Context\nplease draft the works order PDF for order 926\n\n## Instruction\nChoose the installed specialist that owns this request and dispatch it with the Agent tool.\n</channel>\n</channel>"}}'
63
+ # Task 1505 — the same reframe carrying the account's active standing-rules block
64
+ # (Task 1486 prepends `## Standing rules\n- …\n\n` above ## Context on admin
65
+ # channel turns). The reframe detection must strip that optional prefix, mirroring
66
+ # the reader anchor, so a rules-present turn is evaluated identically to a
67
+ # rules-absent one; without the strip the turn no longer starts with ## Context
68
+ # and the hook silently stops flagging on exactly the rule-carrying turns.
69
+ CHANNEL_REFRAME_RULES='{"type":"user","isMeta":true,"message":{"role":"user","content":"<channel source=\"whatsapp-channel\">## Standing rules\n- Always greet the client by name\n- Use British spelling\n\n## Context\nplease draft the works order PDF for order 926\n\n## Instruction\nChoose the installed specialist that owns this request and dispatch it with the Agent tool.</channel>"}}'
70
+ # Same rules-prefixed shape, one-word Context payload — after the prefix strip the
71
+ # trivial filter must still read just the payload (`yes`), not the rules block.
72
+ CHANNEL_REFRAME_RULES_TRIVIAL='{"type":"user","isMeta":true,"message":{"role":"user","content":"<channel source=\"whatsapp-channel\">## Standing rules\n- Always greet the client by name\n- Use British spelling\n\n## Context\nyes\n\n## Instruction\nChoose the installed specialist that owns this request and dispatch it with the Agent tool.</channel>"}}'
63
73
 
64
74
  assert_flagged() {
65
75
  local name="$1" t="$2" out
@@ -181,6 +191,18 @@ assert_flagged "multi-target double-wrapped reframe + inline authoring -> flagge
181
191
  # 18c. Same double-wrapped reframe with an Agent dispatch -> not flagged.
182
192
  assert_not_flagged "multi-target double-wrapped reframe + Agent dispatch -> not flagged" \
183
193
  "$(mk_transcript "$CHANNEL_REFRAME_MULTI" "$ASSISTANT_REPLY" "$ASSISTANT_AGENT")"
194
+ # 18d. Task 1505 — rules-prefixed reframe, inline authoring, no marker -> flagged.
195
+ # The regression guard: with the standing-rules block above ## Context the pre-fix
196
+ # hook read is_reframe=False and silently stopped flagging this class.
197
+ assert_flagged "rules-prefixed reframe + inline authoring, no marker -> flagged" \
198
+ "$(mk_transcript "$CHANNEL_REFRAME_RULES" "$ASSISTANT_REPLY" "$ASSISTANT_BASH" "$ASSISTANT_WRITE")"
199
+ # 18e. Same rules-prefixed reframe with an Agent dispatch -> not flagged.
200
+ assert_not_flagged "rules-prefixed reframe + Agent dispatch -> not flagged" \
201
+ "$(mk_transcript "$CHANNEL_REFRAME_RULES" "$ASSISTANT_REPLY" "$ASSISTANT_AGENT")"
202
+ # 18f. Rules-prefixed reframe whose Context payload is one word -> trivial, not
203
+ # flagged (the prefix strip must leave just the payload for the trivial filter).
204
+ assert_not_flagged "rules-prefixed reframe with one-word Context payload -> not flagged (trivial)" \
205
+ "$(mk_transcript "$CHANNEL_REFRAME_RULES_TRIVIAL" "$ASSISTANT_TEXT")"
184
206
  # 19. Channel-wrapped NON-reframe turn (no ## Instruction section — webchat
185
207
  # today, Task 776) with no marker -> not directive-equivalent, not flagged.
186
208
  assert_not_flagged "channel-wrapped non-reframe turn, no marker -> not flagged" \
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # Task 1515 — UserPromptSubmit hook: inject a fresh <datetime> block as
3
+ # additionalContext on every turn, so the agent always has the current instant
4
+ # (date, time, timezone, weekday, and the raw UTC ISO) without hand-computing it
5
+ # and without depending on Claude Code's spawn-time "Today's date" line going
6
+ # stale. This is the per-turn current-time surface the datetime skill points at.
7
+ #
8
+ # The instant is computed fresh here every turn (refresh=per-turn). The timezone
9
+ # is NOT computed here — it is the operator's own zone, resolved from their
10
+ # UserProfile on the graph once per admin spawn and stamped into MAXY_ACCOUNT_TZ.
11
+ # When MAXY_ACCOUNT_TZ is empty (a non-admin turn, an unset profile timezone, or
12
+ # a neo4j hiccup at spawn) the block falls back to the server's own zone and
13
+ # labels the source, so a reader can always tell which clock the agent saw.
14
+ #
15
+ # Mirrors prompt-optimiser-directive.sh: emit hookSpecificOutput.additionalContext
16
+ # via python3, fail-open with exit 0 on every path. UserPromptSubmit output is
17
+ # ADDED to the prompt, never substituted — the raw prompt always still reaches
18
+ # the model.
19
+ #
20
+ # Input (stdin, UserPromptSubmit contract): { "session_id": "...", ... } —
21
+ # session_id is used only for the breadcrumb; the prompt text is ignored
22
+ # (the block is standing and prompt-independent).
23
+ # Output (stdout JSON):
24
+ # { "hookSpecificOutput": { "hookEventName": "UserPromptSubmit",
25
+ # "additionalContext": "<datetime>...</datetime>" } }
26
+
27
+ set -uo pipefail
28
+
29
+ # Drain stdin FIRST, before any exit path. Claude Code writes the full
30
+ # UserPromptSubmit payload (which carries the prompt text) to our stdin; if we
31
+ # exit before reading it, a payload larger than the pipe buffer leaves the parent
32
+ # blocked on write and then hit with EPIPE/SIGPIPE. The sibling
33
+ # prompt-optimiser-directive.sh drains first for this exact reason. Only
34
+ # session_id is used here (for the breadcrumb); the block is prompt-independent.
35
+ HOOK_INPUT=$(cat 2>/dev/null || true)
36
+
37
+ # Fail-open if either encoder is unavailable: python3 wraps the JSON envelope,
38
+ # node formats the instant in the target zone (Intl has the full IANA database).
39
+ command -v python3 >/dev/null 2>&1 || exit 0
40
+ command -v node >/dev/null 2>&1 || exit 0
41
+
42
+ # The operator's zone, stamped at spawn from the graph. Empty -> server zone.
43
+ ACCOUNT_TZ="${MAXY_ACCOUNT_TZ:-}"
44
+
45
+ # Format the current instant. node prints two tab-separated fields:
46
+ # 1. the resolved IANA zone actually used (so the server-fallback names a real
47
+ # zone, not the string "server")
48
+ # 2. the assembled block body
49
+ # TZ_IN is passed as argv[1]; empty means "use the runtime's own zone".
50
+ BLOCK=$(TZ_IN="$ACCOUNT_TZ" node -e '
51
+ const tzIn = process.env.TZ_IN && process.env.TZ_IN.trim().length ? process.env.TZ_IN.trim() : null;
52
+ const now = new Date();
53
+ // Resolve the zone actually used. A supplied zone that Intl rejects falls back
54
+ // to the runtime zone, so a bad stamp can never break the turn.
55
+ let zone = tzIn;
56
+ try { if (zone) new Intl.DateTimeFormat("en-GB", { timeZone: zone }); }
57
+ catch { zone = null; }
58
+ if (!zone) zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
59
+ // zone === tzIn only when a non-empty, valid graph timezone was stamped; every
60
+ // other case (env empty on a non-admin turn, or a stamped value Intl rejected)
61
+ // falls back to the runtime zone. Label that neutrally as "unavailable" rather
62
+ // than asserting "unset" — the [datetime-tz] spawn log distinguishes unset from
63
+ // invalid from unavailable; here all the hook knows is the zone is not the graph one.
64
+ const source = tzIn && zone === tzIn ? "account-timezone (graph)" : "server-timezone (account timezone unavailable)";
65
+ const local = new Intl.DateTimeFormat("en-GB", {
66
+ weekday: "long", day: "numeric", month: "long", year: "numeric",
67
+ hour: "2-digit", minute: "2-digit", timeZone: zone, timeZoneName: "longOffset",
68
+ }).format(now);
69
+ const body = [
70
+ "Current date and time, read this rather than computing it by hand:",
71
+ "Now: " + local + " (" + zone + ")",
72
+ "UTC: " + now.toISOString(),
73
+ "Source: " + source,
74
+ ].join("\n");
75
+ process.stdout.write(zone + "\t" + body);
76
+ ' 2>/dev/null) || exit 0
77
+
78
+ [ -n "$BLOCK" ] || exit 0
79
+ ZONE_USED="${BLOCK%%$'\t'*}"
80
+ BODY="${BLOCK#*$'\t'}"
81
+ CONTEXT="<datetime>
82
+ $BODY
83
+ </datetime>"
84
+
85
+ OUT=$(printf '%s' "$CONTEXT" | python3 -c '
86
+ import json, sys
87
+ ctx = sys.stdin.read()
88
+ print(json.dumps({
89
+ "hookSpecificOutput": {
90
+ "hookEventName": "UserPromptSubmit",
91
+ "additionalContext": ctx,
92
+ }
93
+ }))
94
+ ' 2>/dev/null) || exit 0
95
+
96
+ [ -n "$OUT" ] || exit 0
97
+ printf '%s\n' "$OUT"
98
+
99
+ # Breadcrumb: source (graph vs server-fallback) + refresh=per-turn so a future
100
+ # staleness report can tell which clock was live each turn without reproduction.
101
+ # Fail-open: a missing/unwritable LOG_DIR just skips the write.
102
+ SID=$(printf '%s' "$HOOK_INPUT" | python3 -c 'import json,sys
103
+ try:
104
+ print(json.load(sys.stdin).get("session_id","") or "?")
105
+ except Exception:
106
+ print("?")' 2>/dev/null)
107
+ SID=${SID:-?}
108
+ if [ -n "$ACCOUNT_TZ" ] && [ "$ZONE_USED" = "$ACCOUNT_TZ" ]; then SRC=graph; else SRC=server-fallback; fi
109
+ NOW_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "?")
110
+ LINE="$NOW_ISO [datetime-inject] injected tz=$ZONE_USED source=$SRC refresh=per-turn session=$SID"
111
+ if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then
112
+ printf '%s\n' "$LINE" >> "$LOG_DIR/datetime-inject.log" 2>/dev/null || true
113
+ fi
114
+ echo "$LINE" >&2 || true
115
+ exit 0
@@ -42,7 +42,7 @@ INPUT=$(cat 2>/dev/null || true)
42
42
  command -v python3 >/dev/null 2>&1 || exit 0
43
43
 
44
44
  RESULT=$(printf '%s' "$INPUT" | python3 -c '
45
- import json, sys, os, datetime
45
+ import json, sys, os, datetime, re
46
46
 
47
47
  def fail_open():
48
48
  sys.exit(0)
@@ -144,6 +144,19 @@ while inner.startswith("<channel"):
144
144
  inner = inner[end + 1:close].strip()
145
145
  is_channel = True
146
146
 
147
+ # Task 1505 — mirror the reader anchor (parse-transcript.ts COMPOSED_ADMIN_FRAME).
148
+ # For admin channel turns Task 1486 prepends the account active standing-rules block
149
+ # (`## Standing rules\n- …\n\n`) above `## Context`, so a rules-present channel turn
150
+ # no longer begins with `## Context` and both the reframe detection and the trivial
151
+ # strip below silently stopped firing on exactly those turns. Strip an optional
152
+ # leading standing-rules block — anchored to the `## Standing rules` header (not any
153
+ # prefix), non-greedy up to the `\n\n` immediately before `## Context` — so both see
154
+ # the `## Context` payload as before rules injection. Channel-only and match-gated on
155
+ # a following `## Context`, so a rules-absent channel turn is byte-identical to before
156
+ # and a typed (non-channel) prompt is untouched.
157
+ if is_channel:
158
+ inner = re.sub(r"^## Standing rules\n[\s\S]*?\n\n(?=## Context\n)", "", inner, count=1)
159
+
147
160
  # Task 805 — a channel-wrapped reframe turn (the ## Context / ## Instruction
148
161
  # sections of composeAdminContent) is directive-equivalent: the directive hook suppresses
149
162
  # itself on channel turns (Task 753), so the reframe is the only routing
@@ -27,7 +27,7 @@ import { publishSite } from "./tools/publish-site.js";
27
27
  import { resolveCapabilitiesHere } from "./tools/capabilities-here.js";
28
28
  import { runCatalogueCensus } from "./lib/catalogue-census.js";
29
29
  import { lifelineTool } from "../../../../lib/mcp-lifeline/dist/index.js";
30
- import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, findHouseAccount, getChannelRoutingAccountId, setChannelRoutingAccountId, seedAccountGraphRoot, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, } from "./tools/account-lifecycle.js";
30
+ import { listAccountsFromDir, isHouseAccount, archiveAccountDir, provisionClientAccount, findHouseAccount, getChannelRoutingAccountId, setChannelRoutingAccountId, seedAccountGraphRoot, readHouseAdminUserIds, setManagingAdmin, findClientAccountsManagedBy, reassignManagingAdmin, ACCOUNT_NODE_COUNT_CYPHER, ACCOUNT_PURGE_CYPHER, } from "./tools/account-lifecycle.js";
31
31
  function refusalGuidance(kind) {
32
32
  switch (kind) {
33
33
  case "unsafe-slug":
@@ -1085,6 +1085,17 @@ eagerTool(server, "admin-remove", "Remove an admin from this account. Removes th
1085
1085
  if (admins.length <= 1) {
1086
1086
  return { content: [{ type: "text", text: `${TAG} Cannot remove the last admin. At least one admin must remain on the account.` }], isError: true };
1087
1087
  }
1088
+ // Task 1501 — never orphan a managed sub-account. A managing admin is always a
1089
+ // house admin, so this bites only when removing from the house account: if
1090
+ // `userId` is the managing admin of any client sub-account, refuse and name
1091
+ // them so the operator reassigns each (account_reassign_admin) before retrying.
1092
+ if (isHouseAccount(ACCOUNTS_ROOT, ACCOUNT_ID)) {
1093
+ const managed = findClientAccountsManagedBy(ACCOUNTS_ROOT, userId);
1094
+ if (managed.length > 0) {
1095
+ process.stderr.write(`[account-lifecycle] op=admin-remove-reconcile userId=${userId.slice(0, 8)} managedAccounts=${managed.length} action=blocked\n`);
1096
+ return { content: [{ type: "text", text: `${TAG} Cannot remove ${userId}: they are the managing admin of ${managed.length} client sub-account(s): ${managed.join(", ")}. Reassign each to another house admin with account_reassign_admin, then retry.` }], isError: true };
1097
+ }
1098
+ }
1088
1099
  // Resolve the admin's name from Neo4j (canonical) for the confirmation
1089
1100
  // message. Best-effort — fall back to userId if the graph is unreachable.
1090
1101
  let removedName = userId;
@@ -3256,9 +3267,23 @@ eagerTool(server, "account_list", "List every sub-account on this install (one h
3256
3267
  content: [{ type: "text", text: accounts.length ? lines.join("\n") : "No accounts found." }],
3257
3268
  };
3258
3269
  });
3259
- eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\"), seeds default plugins, and seeds the account's graph root (a :LocalBusiness plus its owner :AdminUser) so you can populate it immediately. Returns the new accountId. Operator-only; never creates the house sub-account (that is the install itself).", {}, async () => {
3270
+ eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this install (managed service). REQUIRES a managing admin: pass `adminUserId`, the userId of the house team member who will manage this client (get the choices from admin-list; ask the operator which one before creating). Mints a UUID, scaffolds the account directory (agents, specialists, settings, account.json with role:\"client\" and managingAdminUserId), seeds default plugins, and seeds the account's graph root (a :LocalBusiness plus its owner :AdminUser) so you can populate it immediately. Refuses if no adminUserId is given or if it is not a current house admin. Returns the new accountId. Operator-only; never creates the house sub-account (that is the install itself).", {
3271
+ adminUserId: z.string().describe("The userId of the house admin who will manage this client sub-account. Must be a current house admin (see admin-list). Required — creation is refused without it."),
3272
+ }, async ({ adminUserId }) => {
3260
3273
  if (!ACCOUNT_ID)
3261
3274
  return refuseNoAccount("account_create");
3275
+ // Task 1500 — every client sub-account must carry a responsible house admin.
3276
+ // Refuse (tool error, never a silent default) when none is named, or when
3277
+ // the named userId is not a current house admin.
3278
+ if (!adminUserId || !adminUserId.trim()) {
3279
+ process.stderr.write(`[account-lifecycle] op=create-refused reason=no-admin adminUserId=-\n`);
3280
+ return { content: [{ type: "text", text: `account_create refused: name the managing house admin (adminUserId). Call admin-list to see the choices and ask the operator which one manages this client.` }], isError: true };
3281
+ }
3282
+ const houseAdminUserIds = readHouseAdminUserIds(ACCOUNTS_ROOT);
3283
+ if (!houseAdminUserIds.includes(adminUserId)) {
3284
+ process.stderr.write(`[account-lifecycle] op=create-refused reason=unknown-admin adminUserId=${adminUserId.slice(0, 8)}\n`);
3285
+ return { content: [{ type: "text", text: `account_create refused: ${adminUserId} is not a current house admin. Call admin-list for the valid managing-admin choices.` }], isError: true };
3286
+ }
3262
3287
  const newId = randomUUID();
3263
3288
  try {
3264
3289
  const accountDir = provisionClientAccount({
@@ -3302,11 +3327,26 @@ eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this
3302
3327
  finally {
3303
3328
  await seedSession.close();
3304
3329
  }
3305
- process.stderr.write(`[account-lifecycle] op=create accountId=${newId} dir-exists=${dirExists} root-seeded=${rootSeeded}\n`);
3330
+ process.stderr.write(`[account-lifecycle] op=create accountId=${newId} dir-exists=${dirExists} root-seeded=${rootSeeded} managingAdmin=${adminUserId.slice(0, 8)}\n`);
3306
3331
  if (!rootSeeded) {
3307
3332
  return { content: [{ type: "text", text: `account_create FAILED: graph root not seeded for ${newId} (business+admin read-back failed). The account directory was scaffolded but is rootless — run account_delete ${newId} and retry.` }], isError: true };
3308
3333
  }
3309
- return { content: [{ type: "text", text: `Created client sub-account ${newId}. Switch to it from the sub-account picker to work in its data.` }] };
3334
+ // Task 1500 persist the validated managing admin on the new sub-account.
3335
+ // Done after the root seed so a rootless failure never leaves a half-created
3336
+ // account claiming a manager. Its own try: at this point the account is
3337
+ // fully created and seeded, so a write failure must not be reported as a
3338
+ // generic create failure (which would push the operator to retry and
3339
+ // orphan a second account). Report the real state — created but unmanaged —
3340
+ // with the recovery action.
3341
+ try {
3342
+ setManagingAdmin(ACCOUNTS_ROOT, newId, adminUserId);
3343
+ }
3344
+ catch (err) {
3345
+ const msg = err instanceof Error ? err.message : String(err);
3346
+ process.stderr.write(`[account-lifecycle] op=create-admin-write-failed accountId=${newId} error=${msg}\n`);
3347
+ return { content: [{ type: "text", text: `account_create: sub-account ${newId} was created and its graph root seeded, but recording the managing admin failed: ${msg}. The account exists with no managing admin — run account_delete ${newId} and retry.` }], isError: true };
3348
+ }
3349
+ return { content: [{ type: "text", text: `Created client sub-account ${newId}, managed by admin ${adminUserId}. Switch to it from the sub-account picker to work in its data.` }] };
3310
3350
  }
3311
3351
  catch (err) {
3312
3352
  const msg = err instanceof Error ? err.message : String(err);
@@ -3314,6 +3354,24 @@ eagerTool(server, "account_create", "Provision a new CLIENT sub-account on this
3314
3354
  return { content: [{ type: "text", text: `account_create FAILED: ${msg}` }], isError: true };
3315
3355
  }
3316
3356
  });
3357
+ eagerTool(server, "account_reassign_admin", "Reassign a CLIENT sub-account's managing house admin. Pass `accountId` (the client sub-account) and `adminUserId` (the new managing admin's userId — must be a current house admin; get the choices from admin-list and ask the operator which team member takes it). Rewrites the sub-account's managingAdminUserId; the new admin is picked up on the next passive intake. Use it to repair a dangling managing admin (one whose house admin was removed) or to hand a client to a different team member. Refuses if the account is not a client sub-account, or if adminUserId is not a current house admin. Operator-only.", {
3358
+ accountId: z.string().describe("The client sub-account (UUID) whose managing admin changes."),
3359
+ adminUserId: z.string().describe("The userId of the house admin to become the managing admin. Must be a current house admin (see admin-list)."),
3360
+ }, async ({ accountId, adminUserId }) => {
3361
+ if (!ACCOUNT_ID)
3362
+ return refuseNoAccount("account_reassign_admin");
3363
+ const res = reassignManagingAdmin(ACCOUNTS_ROOT, accountId, adminUserId);
3364
+ if (!res.ok) {
3365
+ const msg = res.reason === "no-account"
3366
+ ? `${accountId} is not a registered account on this install.`
3367
+ : res.reason === "not-client"
3368
+ ? `${accountId} is not a client sub-account (the house account has no managing admin).`
3369
+ : `${adminUserId} is not a current house admin. Call admin-list for the valid managing-admin choices.`;
3370
+ return { content: [{ type: "text", text: `account_reassign_admin refused: ${msg}` }], isError: true };
3371
+ }
3372
+ process.stderr.write(`[account-lifecycle] op=reassign accountId=${accountId} from=${res.previousAdminUserId ? res.previousAdminUserId.slice(0, 8) : "-"} to=${adminUserId.slice(0, 8)}\n`);
3373
+ return { content: [{ type: "text", text: `Reassigned client sub-account ${accountId} to managing admin ${adminUserId}. It takes effect on the next passive intake.` }] };
3374
+ });
3317
3375
  eagerTool(server, "account_delete", "Archive a CLIENT sub-account: move its directory to data/accounts/.trash/ so it no longer appears in the sub-account list. Graph data is RETAINED — run account_purge with the typed confirmation to irreversibly delete it. Refuses to archive the house sub-account. Pass the accountId to archive.", {
3318
3376
  accountId: z.string().describe("The client accountId (UUID) to archive."),
3319
3377
  }, async ({ accountId }) => {