@rubytech/create-maxy-code 0.1.383 → 0.1.386

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 (173) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/mcp/dist/index.js +37 -9
  3. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  4. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts +48 -0
  5. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.d.ts.map +1 -1
  6. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js +51 -0
  7. package/payload/platform/plugins/admin/mcp/dist/tools/account-lifecycle.js.map +1 -1
  8. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +37 -18
  9. package/payload/platform/plugins/cloudflare/references/hosting-sites.md +7 -4
  10. package/payload/platform/plugins/cloudflare/skills/cloudflare/SKILL.md +9 -6
  11. package/payload/platform/plugins/docs/references/admin-ui.md +36 -17
  12. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +1 -1
  13. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +2 -0
  14. package/payload/platform/plugins/telegram/skills/configure/SKILL.md +2 -0
  15. package/payload/platform/scripts/__tests__/check-plugin-references.test.sh +83 -0
  16. package/payload/platform/scripts/check-plugin-references.mjs +60 -11
  17. package/payload/platform/services/claude-session-manager/dist/index.js +27 -2
  18. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  19. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.d.ts +35 -0
  20. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.d.ts.map +1 -0
  21. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.js +114 -0
  22. package/payload/platform/services/claude-session-manager/dist/rootless-client-audit.js.map +1 -0
  23. package/payload/platform/services/claude-session-manager/package.json +2 -1
  24. package/payload/server/public/assets/{AdminLoginScreens-C32Kg8HN.js → AdminLoginScreens-oxqHKL3C.js} +1 -1
  25. package/payload/server/public/assets/AdminShell-DBD6ruS1.js +1 -0
  26. package/payload/server/public/assets/{Checkbox-C-dvkjKb.js → Checkbox-yr8-ftrE.js} +1 -1
  27. package/payload/server/public/assets/Transcript-FX_w6no6.js +2 -0
  28. package/payload/server/public/assets/admin-CQfZnuFy.js +1 -0
  29. package/payload/server/public/assets/{arc-JgkiMDpu.js → arc-C6-nr5UV.js} +1 -1
  30. package/payload/server/public/assets/architecture-YZFGNWBL-DdgmZ93B.js +1 -0
  31. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CyIbl1bD.js → architectureDiagram-Q4EWVU46-DIdho_T6.js} +1 -1
  32. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-CkcZVblf.js → blockDiagram-DXYQGD6D-D5rTTBSy.js} +1 -1
  33. package/payload/server/public/assets/{browser-BUuA2PPl.js → browser-DbdPeDK1.js} +1 -1
  34. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-VByNuobb.js → c4Diagram-AHTNJAMY-pfUVHdmy.js} +1 -1
  35. package/payload/server/public/assets/calendar-CFdOhe7Q.js +1 -0
  36. package/payload/server/public/assets/channel-Da-UUyqx.js +1 -0
  37. package/payload/server/public/assets/chat-BunxcNDk.js +1 -0
  38. package/payload/server/public/assets/chevron-left-Cngmu0Py.js +1 -0
  39. package/payload/server/public/assets/{chunk-2KRD3SAO-BFwiGcqo.js → chunk-2KRD3SAO-EF6cdj8U.js} +1 -1
  40. package/payload/server/public/assets/{chunk-336JU56O-BMCBU-vf.js → chunk-336JU56O-DE-lcZAc.js} +2 -2
  41. package/payload/server/public/assets/chunk-426QAEUC-B1kFAxo1.js +1 -0
  42. package/payload/server/public/assets/{chunk-4BX2VUAB-RfOeo8_F.js → chunk-4BX2VUAB--ANCzzyn.js} +1 -1
  43. package/payload/server/public/assets/{chunk-4TB4RGXK-B5xbgonc.js → chunk-4TB4RGXK-DTO_X80J.js} +1 -1
  44. package/payload/server/public/assets/{chunk-55IACEB6-K8U55ikJ.js → chunk-55IACEB6-4J3nrz44.js} +1 -1
  45. package/payload/server/public/assets/{chunk-5FUZZQ4R-DgmmsrAc.js → chunk-5FUZZQ4R-jH_eqAHL.js} +1 -1
  46. package/payload/server/public/assets/{chunk-5PVQY5BW-DQ6rtehF.js → chunk-5PVQY5BW-C-qU6p6k.js} +1 -1
  47. package/payload/server/public/assets/{chunk-67CJDMHE-CC2kfpmQ.js → chunk-67CJDMHE-DXxH9Snb.js} +1 -1
  48. package/payload/server/public/assets/{chunk-7N4EOEYR-BClZ5bzw.js → chunk-7N4EOEYR-CCkxuP7A.js} +1 -1
  49. package/payload/server/public/assets/{chunk-AA7GKIK3-adtwAFKX.js → chunk-AA7GKIK3-CyItuM8d.js} +1 -1
  50. package/payload/server/public/assets/{chunk-BSJP7CBP-fKY1UWEd.js → chunk-BSJP7CBP-Bg7gxYNf.js} +1 -1
  51. package/payload/server/public/assets/{chunk-CIAEETIT-knPAMmjG.js → chunk-CIAEETIT-BAXbOqv5.js} +1 -1
  52. package/payload/server/public/assets/{chunk-EDXVE4YY-C7FRKZ5e.js → chunk-EDXVE4YY-vdoErQoc.js} +1 -1
  53. package/payload/server/public/assets/{chunk-ENJZ2VHE-0cgFfv3z.js → chunk-ENJZ2VHE-Cz_z_6GK.js} +1 -1
  54. package/payload/server/public/assets/{chunk-FMBD7UC4-Dm_Hcblb.js → chunk-FMBD7UC4-Ck2jvOgW.js} +1 -1
  55. package/payload/server/public/assets/{chunk-FOC6F5B3-CfCJNXmK.js → chunk-FOC6F5B3-DucR0Qo8.js} +1 -1
  56. package/payload/server/public/assets/{chunk-ICPOFSXX-xeu6qrZj.js → chunk-ICPOFSXX-DolYDD7g.js} +2 -2
  57. package/payload/server/public/assets/{chunk-K5T4RW27-BQDCSuTh.js → chunk-K5T4RW27-FRzx7E5I.js} +1 -1
  58. package/payload/server/public/assets/{chunk-KGLVRYIC-D6fFAgbM.js → chunk-KGLVRYIC-Bb5S4Bmc.js} +1 -1
  59. package/payload/server/public/assets/{chunk-LIHQZDEY-Dskwlc-K.js → chunk-LIHQZDEY-c8VZJ-lU.js} +1 -1
  60. package/payload/server/public/assets/{chunk-ORNJ4GCN-CnHIrJeA.js → chunk-ORNJ4GCN-BAeh-LZ1.js} +1 -1
  61. package/payload/server/public/assets/{chunk-OYMX7WX6-B4bnsPkv.js → chunk-OYMX7WX6-BUg7Kysh.js} +1 -1
  62. package/payload/server/public/assets/chunk-QZHKN3VN-BjE-Glwf.js +1 -0
  63. package/payload/server/public/assets/{chunk-U2HBQHQK-BeEXzOqV.js → chunk-U2HBQHQK-K4r17UTk.js} +1 -1
  64. package/payload/server/public/assets/{chunk-X2U36JSP-D2rpgOqd.js → chunk-X2U36JSP-Byh-CyxC.js} +1 -1
  65. package/payload/server/public/assets/{chunk-XPW4576I-D-yT82Xf.js → chunk-XPW4576I-DC7lhr93.js} +1 -1
  66. package/payload/server/public/assets/{chunk-YZCP3GAM-DqxlLBm1.js → chunk-YZCP3GAM-B2cu7AEP.js} +1 -1
  67. package/payload/server/public/assets/{chunk-ZZ45TVLE-CA_Ep0OM.js → chunk-ZZ45TVLE-DH_p8sFX.js} +1 -1
  68. package/payload/server/public/assets/classDiagram-6PBFFD2Q-Dol3Ogeh.js +1 -0
  69. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-VUMj7gWH.js +1 -0
  70. package/payload/server/public/assets/clone-m7oGrdBC.js +1 -0
  71. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-DWb9mxTa.js → cose-bilkent-S5V4N54A-DeM6JI00.js} +1 -1
  72. package/payload/server/public/assets/{dagre-B3K7jxOZ.js → dagre-3drs3e4F.js} +1 -1
  73. package/payload/server/public/assets/{dagre-KV5264BT-BhiTISRq.js → dagre-KV5264BT-C6S-VUtN.js} +1 -1
  74. package/payload/server/public/assets/data-IqZlMSwG.js +1 -0
  75. package/payload/server/public/assets/{diagram-5BDNPKRD-D5yBIDpm.js → diagram-5BDNPKRD-BkvA32I3.js} +1 -1
  76. package/payload/server/public/assets/{diagram-G4DWMVQ6-CSTE1S4d.js → diagram-G4DWMVQ6-CVwtRMPB.js} +1 -1
  77. package/payload/server/public/assets/{diagram-MMDJMWI5-D8llv4XY.js → diagram-MMDJMWI5-DJ1JVMbd.js} +1 -1
  78. package/payload/server/public/assets/{diagram-TYMM5635-CITQ7aDM.js → diagram-TYMM5635-rfj-crtX.js} +1 -1
  79. package/payload/server/public/assets/{erDiagram-SMLLAGMA-BjR5nlmv.js → erDiagram-SMLLAGMA-TJPuweS5.js} +1 -1
  80. package/payload/server/public/assets/{flatten-DhYYI_lo.js → flatten-BZkoyJ4e.js} +1 -1
  81. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-Dl6e-E0f.js → flowDiagram-DWJPFMVM-LQWEUjbV.js} +1 -1
  82. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CppkrFAl.js → ganttDiagram-T4ZO3ILL-DdYsxKRw.js} +1 -1
  83. package/payload/server/public/assets/gitGraph-7Q5UKJZL-cJEaiQq4.js +1 -0
  84. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-clV_tnwa.js → gitGraphDiagram-UUTBAWPF-sx8-fokM.js} +1 -1
  85. package/payload/server/public/assets/{graph-CvQZVxwZ.js → graph-Bluocsf_.js} +3 -3
  86. package/payload/server/public/assets/{graph-labels-C7bJMcXs.js → graph-labels-BcLJmY-v.js} +1 -1
  87. package/payload/server/public/assets/{graphlib-Cnk6WL-8.js → graphlib-DixeJdCf.js} +1 -1
  88. package/payload/server/public/assets/info-OMHHGYJF-BUxfOTKa.js +1 -0
  89. package/payload/server/public/assets/infoDiagram-42DDH7IO-CD-d3Rgf.js +2 -0
  90. package/payload/server/public/assets/{isEmpty-DGhq_DBP.js → isEmpty-VRKFml5R.js} +1 -1
  91. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DUqusmP1.js → ishikawaDiagram-UXIWVN3A-BKUCuKhZ.js} +1 -1
  92. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-RMEvGtDJ.js → journeyDiagram-VCZTEJTY-BafZP6pM.js} +1 -1
  93. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BxkwQPCP.js → kanban-definition-6JOO6SKY-DQ4c6wRv.js} +1 -1
  94. package/payload/server/public/assets/{line-CiOuSBlJ.js → line-0_x1Wivu.js} +1 -1
  95. package/payload/server/public/assets/{linear-D22sItNm.js → linear-CZrU-J2V.js} +1 -1
  96. package/payload/server/public/assets/{mermaid-parser.core-kvN8ajT-.js → mermaid-parser.core-DwJ1cyfO.js} +2 -2
  97. package/payload/server/public/assets/{mermaid.core-DKHTTwbg.js → mermaid.core-DnUst0He.js} +3 -3
  98. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-CKUJxwdG.js → mindmap-definition-QFDTVHPH-oc-GwerE.js} +1 -1
  99. package/payload/server/public/assets/operator-DjK3MBSS.js +1 -0
  100. package/payload/server/public/assets/{ordinal-CibZU3PM.js → ordinal-v4prQcuq.js} +1 -1
  101. package/payload/server/public/assets/packet-4T2RLAQJ-B9U_2639.js +1 -0
  102. package/payload/server/public/assets/page-DQNYTb2d.js +1 -0
  103. package/payload/server/public/assets/page-zJQTRXB2.js +32 -0
  104. package/payload/server/public/assets/pdf-render-NQmflZ7K.js +11 -0
  105. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
  106. package/payload/server/public/assets/pie-ZZUOXDRM-Glh-lJOT.js +1 -0
  107. package/payload/server/public/assets/{pieDiagram-DEJITSTG-CRMabeJN.js → pieDiagram-DEJITSTG-BnBtQNHM.js} +1 -1
  108. package/payload/server/public/assets/public-CHCniosp.js +1 -0
  109. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DcrlhDuj.js → quadrantDiagram-34T5L4WZ-CkNqtzHI.js} +1 -1
  110. package/payload/server/public/assets/radar-PYXPWWZC-CTSgob8w.js +1 -0
  111. package/payload/server/public/assets/{reduce-Da0RBva0.js → reduce-0Mm1v1Rr.js} +1 -1
  112. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DP231rM_.js → requirementDiagram-MS252O5E-D7qc27KT.js} +1 -1
  113. package/payload/server/public/assets/{rotate-ccw-DPCP-Lmz.js → rotate-ccw-JDfwjfIL.js} +1 -1
  114. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6--HM5_dC8.js → sankeyDiagram-XADWPNL6-2Y4sqRY7.js} +1 -1
  115. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BsiVmDR9.js → sequenceDiagram-FGHM5R23-hXOiSHuT.js} +1 -1
  116. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cjdhqdjh.js → stateDiagram-FHFEXIEX-Dq4hhkLA.js} +1 -1
  117. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-DTj9o2vZ.js +1 -0
  118. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Cf5xPmSA.js → timeline-definition-GMOUNBTQ-Dn9wBUEw.js} +1 -1
  119. package/payload/server/public/assets/treeView-SZITEDCU-CXApkW1l.js +1 -0
  120. package/payload/server/public/assets/treemap-W4RFUUIX-DzvTXAcT.js +1 -0
  121. package/payload/server/public/assets/useSubAccountSwitcher-BpQ3oKiJ.js +9 -0
  122. package/payload/server/public/assets/{useSubAccountSwitcher-DqxG4I5C.css → useSubAccountSwitcher-CfI3J2IX.css} +1 -1
  123. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-PCor55Sw.js → vennDiagram-DHZGUBPP-BKVHJiIt.js} +1 -1
  124. package/payload/server/public/assets/wardley-RL74JXVD-BExXV4oI.js +1 -0
  125. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-bokCHsim.js → wardleyDiagram-NUSXRM2D-Bn9vvpdK.js} +1 -1
  126. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-B2YnsUoQ.js → xychartDiagram-5P7HB3ND-FIoMdPB3.js} +1 -1
  127. package/payload/server/public/browser.html +5 -4
  128. package/payload/server/public/calendar.html +6 -4
  129. package/payload/server/public/chat.html +10 -7
  130. package/payload/server/public/data.html +9 -5
  131. package/payload/server/public/graph.html +9 -7
  132. package/payload/server/public/index.html +11 -8
  133. package/payload/server/public/operator.html +12 -9
  134. package/payload/server/public/public.html +9 -7
  135. package/payload/server/server.js +5 -2
  136. package/payload/server/public/assets/AdminShell-DizHgGBB.js +0 -1
  137. package/payload/server/public/assets/admin-CSBsndZ4.js +0 -1
  138. package/payload/server/public/assets/architecture-YZFGNWBL-Dh5BzVzk.js +0 -1
  139. package/payload/server/public/assets/calendar-CGlyx54B.js +0 -1
  140. package/payload/server/public/assets/channel-CFHc3PQ7.js +0 -1
  141. package/payload/server/public/assets/chat-DT1PbRkD.js +0 -1
  142. package/payload/server/public/assets/chunk-426QAEUC-De9_O1hi.js +0 -1
  143. package/payload/server/public/assets/chunk-QZHKN3VN-COwe7oXR.js +0 -1
  144. package/payload/server/public/assets/classDiagram-6PBFFD2Q-wjL65qaU.js +0 -1
  145. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-D_EIplVd.js +0 -1
  146. package/payload/server/public/assets/clone-CEczy_Ef.js +0 -1
  147. package/payload/server/public/assets/data-BC4KTIX4.js +0 -1
  148. package/payload/server/public/assets/gitGraph-7Q5UKJZL-KHAc7IxG.js +0 -1
  149. package/payload/server/public/assets/info-OMHHGYJF-C6nm3sYf.js +0 -1
  150. package/payload/server/public/assets/infoDiagram-42DDH7IO-DhwCejsr.js +0 -2
  151. package/payload/server/public/assets/operator-4skjNtEa.js +0 -1
  152. package/payload/server/public/assets/packet-4T2RLAQJ-z-APhyNH.js +0 -1
  153. package/payload/server/public/assets/page-B0zvkWaG.js +0 -1
  154. package/payload/server/public/assets/page-BYlBTwqJ.js +0 -32
  155. package/payload/server/public/assets/pie-ZZUOXDRM-D_HVZjir.js +0 -1
  156. package/payload/server/public/assets/public-ClYyeEA1.js +0 -1
  157. package/payload/server/public/assets/radar-PYXPWWZC-DtKBIb3o.js +0 -1
  158. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-6v4alr7n.js +0 -1
  159. package/payload/server/public/assets/treeView-SZITEDCU-DbXWs029.js +0 -1
  160. package/payload/server/public/assets/treemap-W4RFUUIX-DUGgqAs9.js +0 -1
  161. package/payload/server/public/assets/useSubAccountSwitcher-B8FEcQNM.js +0 -9
  162. package/payload/server/public/assets/wardley-RL74JXVD-Cjn85RON.js +0 -1
  163. /package/payload/server/public/assets/{_baseFor-BFYpZGkN.js → _baseFor-C0Y1mc3g.js} +0 -0
  164. /package/payload/server/public/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
  165. /package/payload/server/public/assets/{cytoscape.esm-DqlHsaog.js → cytoscape.esm-Cn5b4Cdu.js} +0 -0
  166. /package/payload/server/public/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
  167. /package/payload/server/public/assets/{dist-BMqkacmt.js → dist-Dlks0Bfw.js} +0 -0
  168. /package/payload/server/public/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
  169. /package/payload/server/public/assets/{katex-pVJiI_rc.js → katex-BFwFoEts.js} +0 -0
  170. /package/payload/server/public/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
  171. /package/payload/server/public/assets/{preload-helper-B_l5jfgx.js → preload-helper-C5gCWwwF.js} +0 -0
  172. /package/payload/server/public/assets/{rough.esm-nHaDi0Kw.js → rough.esm-DsHEmXUZ.js} +0 -0
  173. /package/payload/server/public/assets/{src-CsEKjNXD.js → src-Bv7ZWAzF.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import type { CreatedBy } from "../../../../../lib/graph-write/dist/index.js";
1
2
  export interface AccountSummary {
2
3
  accountId: string;
3
4
  role: string;
@@ -43,6 +44,53 @@ export declare function provisionClientAccount(opts: {
43
44
  accountId: string;
44
45
  usersFile: string;
45
46
  }): string;
47
+ /** Minimal Neo4j session surface the root seed needs — the admin plugin's
48
+ * `getSession()` satisfies it; tests pass an in-memory fake. */
49
+ export interface SeedSessionLike {
50
+ run(query: string, params?: Record<string, unknown>): Promise<{
51
+ records: Array<{
52
+ get(key: string): unknown;
53
+ }>;
54
+ }>;
55
+ }
56
+ /** The provenance the root seed represents — matches the properties the cypher
57
+ * below stamps (`createdByAgent='system'`, `createdBySource='installer'`), the
58
+ * same shape `seed-neo4j.sh` writes for the house root. `agent:"system"` is the
59
+ * convention `checkLabelOriginGate` exempts. The seed runs as raw cypher through
60
+ * the admin session (not the memory-write chokepoint), so the gate never
61
+ * actually evaluates it in production; this constant is not the write's
62
+ * provenance carrier — it exists so a unit test can assert the gate *would*
63
+ * permit this origin. */
64
+ export declare const SEED_ORIGIN: CreatedBy;
65
+ /** Task 1359 — seed a client account's graph root: a bare :LocalBusiness{accountId}
66
+ * plus its owner :AdminUser (fresh per-account userId) joined by :ADMIN_OF, in
67
+ * one statement, then read both node existences back. Without this a created
68
+ * client account is rootless: `checkGraphWriteGate` refuses every non-root
69
+ * write and `checkLabelOriginGate` refuses the agent's attempt to create the
70
+ * root, so the graph is unpopulatable.
71
+ *
72
+ * Raw cypher through the admin session bypasses the memory-write gates by
73
+ * construction (they guard only the memory-write chokepoint), mirroring
74
+ * `seed-neo4j.sh`. The owner AdminUser carries a fresh `ownerUserId` because
75
+ * AdminUser is MERGE-keyed on `userId` and COALESCEs `accountId`; reusing the
76
+ * install owner's userId would keep `accountId=house` and leave the gate's
77
+ * `MATCH (au:AdminUser {accountId: <client>})` empty. Single-shot per account:
78
+ * MERGE on `LocalBusiness.accountId` (a UNIQUE key) makes a replay against the
79
+ * same accountId safe, but it is NOT replay-idempotent under a different
80
+ * `ownerUserId` (that would MERGE a second owner AdminUser); the sole caller
81
+ * mints a new accountId per create and never replays. Returns the two
82
+ * read-back booleans; the caller treats `businessSeeded && adminSeeded` as the
83
+ * verified post-condition. */
84
+ export declare function seedAccountGraphRoot(args: {
85
+ session: SeedSessionLike;
86
+ accountId: string;
87
+ ownerUserId: string;
88
+ ownerName: string;
89
+ createdAt: string;
90
+ }): Promise<{
91
+ businessSeeded: boolean;
92
+ adminSeeded: boolean;
93
+ }>;
46
94
  /** Cypher for the account-scoped node count and the irreversible purge. The
47
95
  * purge deletes every node carrying the accountId (account scope is a property,
48
96
  * never an edge — see CLAUDE.md), detaching its relationships. */
@@ -1 +1 @@
1
- {"version":3,"file":"account-lifecycle.d.ts","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;oCAEoC;AACpC,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAoBzE;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED;;+DAE+D;AAC/D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;+DAK+D;AAC/D,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED;;;;0EAI0E;AAC1E,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAO7F;AAED;;iFAEiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAO3C;AAED;;;yBAGyB;AACzB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAoBT;AAED;;mEAEmE;AACnE,eAAO,MAAM,yBAAyB,2DAA2D,CAAC;AAClG,eAAO,MAAM,oBAAoB,sDAAsD,CAAC"}
1
+ {"version":3,"file":"account-lifecycle.d.ts","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8CAA8C,CAAC;AAI9E,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;oCAEoC;AACpC,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,EAAE,CAoBzE;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED;;+DAE+D;AAC/D,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAE3E;AAED;;;;;+DAK+D;AAC/D,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED;;;;0EAI0E;AAC1E,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,CAO7F;AAED;;iFAEiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,GACT;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAO3C;AAED;;;yBAGyB;AACzB,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAoBT;AAED;iEACiE;AACjE,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAClH;AAED;;;;;;;0BAO0B;AAC1B,eAAO,MAAM,WAAW,EAAE,SAAoD,CAAC;AAE/E;;;;;;;;;;;;;;;;;;+BAkB+B;AAC/B,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB7D;AAED;;mEAEmE;AACnE,eAAO,MAAM,yBAAyB,2DAA2D,CAAC;AAClG,eAAO,MAAM,oBAAoB,sDAAsD,CAAC"}
@@ -109,6 +109,57 @@ export function provisionClientAccount(opts) {
109
109
  });
110
110
  return accountDir;
111
111
  }
112
+ /** The provenance the root seed represents — matches the properties the cypher
113
+ * below stamps (`createdByAgent='system'`, `createdBySource='installer'`), the
114
+ * same shape `seed-neo4j.sh` writes for the house root. `agent:"system"` is the
115
+ * convention `checkLabelOriginGate` exempts. The seed runs as raw cypher through
116
+ * the admin session (not the memory-write chokepoint), so the gate never
117
+ * actually evaluates it in production; this constant is not the write's
118
+ * provenance carrier — it exists so a unit test can assert the gate *would*
119
+ * permit this origin. */
120
+ export const SEED_ORIGIN = { agent: "system", source: "installer" };
121
+ /** Task 1359 — seed a client account's graph root: a bare :LocalBusiness{accountId}
122
+ * plus its owner :AdminUser (fresh per-account userId) joined by :ADMIN_OF, in
123
+ * one statement, then read both node existences back. Without this a created
124
+ * client account is rootless: `checkGraphWriteGate` refuses every non-root
125
+ * write and `checkLabelOriginGate` refuses the agent's attempt to create the
126
+ * root, so the graph is unpopulatable.
127
+ *
128
+ * Raw cypher through the admin session bypasses the memory-write gates by
129
+ * construction (they guard only the memory-write chokepoint), mirroring
130
+ * `seed-neo4j.sh`. The owner AdminUser carries a fresh `ownerUserId` because
131
+ * AdminUser is MERGE-keyed on `userId` and COALESCEs `accountId`; reusing the
132
+ * install owner's userId would keep `accountId=house` and leave the gate's
133
+ * `MATCH (au:AdminUser {accountId: <client>})` empty. Single-shot per account:
134
+ * MERGE on `LocalBusiness.accountId` (a UNIQUE key) makes a replay against the
135
+ * same accountId safe, but it is NOT replay-idempotent under a different
136
+ * `ownerUserId` (that would MERGE a second owner AdminUser); the sole caller
137
+ * mints a new accountId per create and never replays. Returns the two
138
+ * read-back booleans; the caller treats `businessSeeded && adminSeeded` as the
139
+ * verified post-condition. */
140
+ export async function seedAccountGraphRoot(args) {
141
+ const { session, accountId, ownerUserId, ownerName, createdAt } = args;
142
+ const res = await session.run(`MERGE (lb:LocalBusiness {accountId: $accountId})
143
+ ON CREATE SET lb.createdBySource = 'installer',
144
+ lb.createdByAgent = 'system',
145
+ lb.createdAt = datetime()
146
+ MERGE (au:AdminUser {userId: $ownerUserId})
147
+ ON CREATE SET au.accountId = $accountId,
148
+ au.role = 'owner',
149
+ au.name = $ownerName,
150
+ au.createdAt = $createdAt
151
+ ON MATCH SET au.accountId = COALESCE(au.accountId, $accountId)
152
+ MERGE (au)-[r:ADMIN_OF]->(lb)
153
+ ON CREATE SET r.role = 'owner', r.grantedAt = $createdAt
154
+ WITH lb.accountId AS acct
155
+ RETURN COUNT { MATCH (b:LocalBusiness {accountId: acct}) } > 0 AS hasBusiness,
156
+ COUNT { MATCH (a:AdminUser {accountId: acct}) } > 0 AS hasAdmin`, { accountId, ownerUserId, ownerName, createdAt });
157
+ const rec = res.records[0];
158
+ return {
159
+ businessSeeded: Boolean(rec?.get("hasBusiness")),
160
+ adminSeeded: Boolean(rec?.get("hasAdmin")),
161
+ };
162
+ }
112
163
  /** Cypher for the account-scoped node count and the irreversible purge. The
113
164
  * purge deletes every node carrying the accountId (account scope is a property,
114
165
  * never an edge — see CLAUDE.md), detaching its relationships. */
@@ -1 +1 @@
1
- {"version":3,"file":"account-lifecycle.js","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,sEAAsE;AACtE,gFAAgF;AAChF,+EAA+E;AAC/E,uEAAuE;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAQlF;;oCAEoC;AACpC,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAqB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAClC,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAuB,CAAC;YAChF,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,EAAU;IAC5D,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AACvF,CAAC;AAED;;+DAE+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,mBAAmB,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;AACzE,CAAC;AAED;;;;;+DAK+D;AAC/D,MAAM,UAAU,0BAA0B,CAAC,WAAmB;IAC5D,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACzE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA0C,CAAC;QACnG,OAAO,OAAO,GAAG,CAAC,uBAAuB,KAAK,QAAQ,IAAI,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7H,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;0EAI0E;AAC1E,MAAM,UAAU,0BAA0B,CAAC,WAAmB,EAAE,eAAuB;IACrF,MAAM,KAAK,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA4B,CAAC;IACrF,GAAG,CAAC,uBAAuB,GAAG,eAAe,CAAC;IAC9C,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;iFAEiF;AACjF,MAAM,UAAU,iBAAiB,CAC/B,WAAmB,EACnB,EAAU,EACV,EAAU;IAEV,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAC9C,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IACjD,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC/B,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AACxD,CAAC;AAED;;;yBAGyB;AACzB,MAAM,UAAU,sBAAsB,CAAC,IAKtC;IACC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1D,YAAY,CACV,MAAM,EACN,CAAC,IAAI,EAAE,mGAAmG,CAAC,EAC3G;QACE,GAAG,EAAE;YACH,GAAG,OAAO,CAAC,GAAG;YACd,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;YACnD,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;YAC9C,UAAU,EAAE,IAAI,CAAC,SAAS;YAC1B,QAAQ,EAAE,UAAU;YACpB,OAAO,EAAE,IAAI,CAAC,SAAS;SACxB;QACD,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;KAClC,CACF,CAAC;IACF,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;mEAEmE;AACnE,MAAM,CAAC,MAAM,yBAAyB,GAAG,wDAAwD,CAAC;AAClG,MAAM,CAAC,MAAM,oBAAoB,GAAG,mDAAmD,CAAC"}
1
+ {"version":3,"file":"account-lifecycle.js","sourceRoot":"","sources":["../../src/tools/account-lifecycle.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy 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:86a7d5decfa1f569dcb4a23a7506fe36f23947c44dc68d4c43277be01c6418e2
4
+ content-hash: sha256:6a2a7d9672033ab7af53bc389dfd77c9462949665ee53614374147811c0921ef
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2553,7 +2553,7 @@ either is a regression.
2553
2553
 
2554
2554
  **`/chat` header always shows the account name.** The admin/operator `HeaderMenu` centre always renders the active account's name alongside the brand logo, on every chat surface, regardless of the active conversation — `headerTitle = businessName || brand.productName`. There is no per-conversation header title: the active conversation's label stays in the sidebar and the Conversations flyout (each resolves its own per-session title independently), never in the header. The account name is `LocalBusiness.name`, resolved by `fetchAccountName(accountId)` in `neo4j-store.ts` with **no theme gate** — unlike `fetchBranding` (which returns null unless a colour/logo property is also set, and stays the source for actual theming). The house account is seeded with `LocalBusiness.name = <brand productName>` (`seed-neo4j.sh`, coalesced ON MATCH so a re-seed backfills a name-less node and never clobbers a name the business-profile recorder set later), so the sub-account picker's house row shows the product name (e.g. "SiteDesk"), not the raw accountId UUID. When a name is absent the resolver emits `[branding] account-name-fallback accountId=<id8>… reason=no-name`, and the picker falls back to the accountId; the seed + backfill is the name source. The lite variant header is unchanged.
2555
2555
 
2556
- **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble showing the message text and, beneath it, a subtle always-visible time-of-day stamp (no label); delivered agent replies render as plain prose with the same time-of-day stamp beneath (the reply-document filename line stays, as prose); the stamp is HH:MM from the turn's `ts` (locale-formatted, right-aligned inside the operator bubble, left-aligned under agent prose), and a turn whose `ts` is null/unparseable shows no stamp, never an empty line — so only delivered operator and agent-reply turns are stamped, while tool runs, the collapsed "Thinking" block, and the agent-error banner stay time-free; a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp reader (`/whatsapp`) omits the prop and keeps the default `renderTimeline`, so its rendering is unchanged; both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx`, `app/chat/__tests__/transcript-render.test.tsx`). **Day-divider:** both renderers insert a centered `.day-divider` row between two consecutive timeline items on different local calendar days (label `Today`/`Yesterday`, else `Sat 14 Jun 2026`), so a thread spanning midnight is never an ambiguous run of HH:MM; the first dated item gets a leading divider, a null/unparseable `ts` marks no boundary, dividers are computed over the filtered `visibleItems`, and in `/chat` a day crossover flushes any open tool/think run first so a collapsed run never spans it. Per-bubble HH:MM stamps are unchanged. Shared helpers `dayKey`/`dayLabel`/`itemTs`/`DayDivider` live in `app/whatsapp/Transcript.tsx`.
2556
+ **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble showing the message text and, beneath it, a subtle always-visible time-of-day stamp (no label); delivered agent replies render as plain prose with the same time-of-day stamp beneath (the reply-document filename line stays, as prose); the stamp is HH:MM from the turn's `ts` (locale-formatted, right-aligned inside the operator bubble, left-aligned under agent prose), and a turn whose `ts` is null/unparseable shows no stamp, never an empty line — so only delivered operator and agent-reply turns are stamped, while tool runs, the collapsed "Thinking" block, and the agent-error banner stay time-free; a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp/operator reader (`/whatsapp`, `OperatorConversations`) omits the prop and keeps the default `renderTimeline`, so its bubble/DM chrome is unchanged except that since the attachment-render extraction the default renderer draws inbound `operator-inbound` uploads and `agent-file` deliveries inline (image thumbnail / pdf.js preview / audio player / download chip) via the attachment renderer shared with `/chat`, extracted to `app/chat/attachment-render.tsx` to break the cycle with `transcript-render.tsx`; `renderChatTimeline` is untouched. Both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx`, `app/chat/__tests__/transcript-render.test.tsx`, `app/chat/__tests__/pdf-preview.test.tsx`). **Day-divider:** both renderers insert a centered `.day-divider` row between two consecutive timeline items on different local calendar days (label `Today`/`Yesterday`, else `Sat 14 Jun 2026`), so a thread spanning midnight is never an ambiguous run of HH:MM; the first dated item gets a leading divider, a null/unparseable `ts` marks no boundary, dividers are computed over the filtered `visibleItems`, and in `/chat` a day crossover flushes any open tool/think run first so a collapsed run never spans it. Per-bubble HH:MM stamps are unchanged. Shared helpers `dayKey`/`dayLabel`/`itemTs`/`DayDivider` live in `app/whatsapp/Transcript.tsx`.
2557
2557
 
2558
2558
  **`/chat` live activity line.** While a turn is in flight the transcript tail shows one ephemeral `ChatActivity` line (mounted only while `busy`, admin/operator only). It tracks real activity, not a timer: while a `Task` subagent runs it shows that subagent's headline (`agentType · description`, prefixed `N agents ·` when ≥2 are concurrent), sourced from `agent-<hex>.meta.json` via named `activity` SSE events the admin reader pushes from the session's `subagents/` dir; with no subagent active it shows a neutral word that advances only on a real turn arrival. The line carries a turn-elapsed clock and flips to a `stalled` state once nothing has been written for 5 minutes (`now − lastEmitAt`), so the operator can tell a wedge from progress without SSH. It is never added to the persisted timeline. Detail and the `[webchat-activity]` observability live in [`admin-webchat-native-channel.md`](../../../.docs/admin-webchat-native-channel.md).
2559
2559
 
@@ -2803,13 +2803,19 @@ authoritative. This section names the surfaces and what backs each.
2803
2803
  > **Admin variant only.** Every surface below belongs to the `admin` shell
2804
2804
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
2805
2805
  > all: its dashboard is the persistent admin webchat at `/` and the data
2806
- > browser at `/data`, plus a read-only **Conversations** reader reached from a
2807
- > dynamic burger item — header order Chat → Conversations → Data → Log out, in
2808
- > a single-column `.platform.platform-operator` grid. The operator never sees
2809
- > the sessions list or the nav rows; the Conversations reader lists the agent's
2810
- > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
2811
- > via the same admin-gated reader routes, never the operator's own session. The
2812
- > operator surface is documented in full in the internal doc
2806
+ > browser at `/data`, plus a **Conversations** item reached from a dynamic
2807
+ > burger item — header order Chat → Conversations → Data → Log out, in a
2808
+ > single-column `.platform.platform-operator` grid. On the operator **chat**
2809
+ > surface the Conversations item opens the same in-chat session
2810
+ > switcher the admin `/chat` surface uses (`ChatSurface`'s `SessionList` flyout):
2811
+ > the operator's own admin webchat conversations, newest-first, each resuming
2812
+ > live via `/chat?session=<id>`, with rename / archive / delete / end / new — not
2813
+ > a read-only reader. The item gates on the flyout handler being wired, exactly
2814
+ > like the admin and lite items. The read-only inbound-channel **Conversations**
2815
+ > reader (WhatsApp, Telegram, public webchat, via the admin-gated
2816
+ > `/api/whatsapp-reader/conversations` route) remains on `/data`'s Conversations
2817
+ > item and in the admin Sidebar; it is no longer reachable from the operator chat
2818
+ > surface. The operator surface is documented in full in the internal doc
2813
2819
  > `maxy-code/.docs/operator-dashboard.md`.
2814
2820
  >
2815
2821
  > **Operator `/chat`.** The reduced operator chrome renders on
@@ -2819,6 +2825,17 @@ authoritative. This section names the surfaces and what backs each.
2819
2825
  > operator shell. Unlike the operator root — which forces the canonical session
2820
2826
  > — operator `/chat` honors `?session=`, opening a specific session inside the
2821
2827
  > reduced shell. Admin-host `/chat` is unchanged (full developer shell).
2828
+ > **Sub-account switch resets the session target.** Because `/chat` honors
2829
+ > `?session=`, the header sub-account switcher (`useSubAccountSwitcher`) drops
2830
+ > the `session` query param when it navigates after a switch, rather than
2831
+ > reloading verbatim. A verbatim reload would keep the *previous* account's
2832
+ > `?session=<id>`, which the reloaded surface re-pins — stranding the operator
2833
+ > on the wrong account's session . Dropping the param lands on the
2834
+ > account-scoped canonical surface, so `resolveCanonical` resolves the switched
2835
+ > account's latest owned session, or the new-session splash when it has none.
2836
+ > The pathname is preserved (a switch on `/chat` stays on `/chat`); the operator
2837
+ > root `/` and bare `/chat` were always correct (they force-ignore / carry no
2838
+ > `?session=`).
2822
2839
  >
2823
2840
  > **Width.** The operator header, the operator chat column, and the `/data`
2824
2841
  > header+toolbar+body all render in one centred column bounded to
@@ -2833,15 +2850,17 @@ authoritative. This section names the surfaces and what backs each.
2833
2850
  > surface; the rest of the admin shell stays edge-to-edge. The public webchat
2834
2851
  > (`.public-surface`) inherits the same value.
2835
2852
  >
2836
- > **Conversations item.** The dynamic Conversations burger item appears on
2837
- > **both** the operator chat surface and `/data` whenever ≥1 reviewable
2838
- > conversation exists (the count/channels are fetched per surface from
2839
- > `/whatsapp-reader/conversations` with the operator's session key). On `/data`
2840
- > selecting it renders the same read-only reader in place (the file browser is
2841
- > swapped for it; Back returns). The flyout-open signal
2842
- > `[operator-ui] op=conversations-menu present=<bool> channels=[…] count=<n>`
2843
- > fires on `/data` too `present=false` there while the chat surface shows the
2844
- > item is the props-not-wired signature.
2853
+ > **Conversations item.** The dynamic Conversations burger item gates on the
2854
+ > host wiring its open-handler (like the admin and lite items), not on a channel
2855
+ > count. On the operator **chat** surface the handler is always wired, so the
2856
+ > item is always present and opens the live session switcher (above). On `/data`
2857
+ > the handler is wired only when ≥1 reviewable inbound conversation exists
2858
+ > (fetched from `/whatsapp-reader/conversations` with the operator's session
2859
+ > key), and selecting it renders the read-only reader in place (the file browser
2860
+ > is swapped for it; Back returns) so `/data` keeps its hide-when-empty
2861
+ > behaviour. The flyout-open signal `[operator-ui] op=conversations-menu
2862
+ > present=<bool>` fires for the operator variant on both surfaces; `present=false`
2863
+ > while the operator expects the item is the handler-not-wired signature.
2845
2864
 
2846
2865
  | Surface | What it does | Backed by |
2847
2866
  |---|---|---|
@@ -33,15 +33,18 @@ If the site captures form data, add the `[[d1_databases]]` binding from `d1-data
33
33
 
34
34
  ## 3. Load the reused deploy token
35
35
 
36
- A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). Load the stable per-scope token minting it once only if absent per `api.md` § Provisioning and reusing a stable per-scope token:
36
+ A Pages deploy needs **Account · Cloudflare Pages · Edit**. If the site has Pages Functions hitting D1, the token also needs **Account · D1 · Edit** (the single most common breakage — see `d1-data-capture.md`). The `pages` scope resolves the stable `<brand>-pages-d1` token, which carries both. Resolve it with the deterministic helper, which mints it once only if absent and reuses it thereafter. This is the one command; do not source the secrets file and hand the resulting `CLOUDFLARE_API_TOKEN` to `wrangler` — that variable holds the **master**, not the per-scope token, and `wrangler pages` then returns `Authentication error [code: 10000]`:
37
37
 
38
38
  ```bash
39
+ KEY=$(bash platform/plugins/cloudflare/bin/cf-token.sh pages "${SECRETS_DIR}/cloudflare.env")
39
40
  set -a; . "${SECRETS_DIR}/cloudflare.env"; set +a
40
- : "${CLOUDFLARE_API_TOKEN:?credentials not loaded}"; : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
41
- # PAGES_D1 = the reused per-scope token (Pages Edit, + D1 Edit if the site uses D1),
42
- # loaded from cloudflare.env and minted once if absent see api.md § Provisioning and reusing.
41
+ : "${CLOUDFLARE_ACCOUNT_ID:?account id not loaded}"
42
+ PAGES_D1="${!KEY}" # the active Pages(+D1) per-scope token NOT the sourced CLOUDFLARE_API_TOKEN (that is the master)
43
+ : "${PAGES_D1:?per-scope token not resolvedre-run cf-token.sh and read its output}"
43
44
  ```
44
45
 
46
+ A `10000` here is a token-selection symptom, not a permissions wall: it means the master reached `wrangler`. Re-run the helper line and retry with `${PAGES_D1}`; never fall back to a manual step on the strength of a `10000`. The mint-once, reuse, prefix-routing, and redaction details live in `api.md` § Provisioning and reusing a stable per-scope token; this command is self-contained and does not depend on reading that section first.
47
+
45
48
  ## 4. Deploy
46
49
 
47
50
  ```bash
@@ -9,7 +9,9 @@ Run by the admin/main session, the operator-infra surface that holds the `Bash`
9
9
 
10
10
  This is the entry point for every Cloudflare task on the install. Pick the operation class, read the matching reference, run the command via Bash, relay the literal output, and prove the outcome with a live signal. There are no Cloudflare MCP tools; the plugin registers none. The agent never browser-automates the dashboard — the operator clicks where a click is genuinely required.
11
11
 
12
- | You want to… | Read | Outcome contract |
12
+ **How to open every `references/…` file named in this skill.** These reference files live at the plugin root (`platform/plugins/cloudflare/references/`), one directory above this skill. They are not children of the directory this skill announces as its base. Read each one with the `plugin-read` tool, which resolves the path plugin-root-relative: `plugin-read` with `file="references/api.md"` returns the file. A plain `Read` of `references/api.md` against this skill's announced base directory resolves to `.../skills/cloudflare/references/api.md`, which does not exist and returns "File does not exist". That miss is not a reason to skip the reference, to substitute a non-canonical file, or to improvise the command from memory. Use `plugin-read` for every `references/…` mention below, the same way the sibling `site-deploy` and `calendar-site` skills do.
13
+
14
+ | You want to… | Read via `plugin-read` | Outcome contract |
13
15
  |---|---|---|
14
16
  | Stand up / diagnose / reset a tunnel so a hostname is reachable | `references/manual-setup.md` | external `curl -I https://<hostname>` → `200` |
15
17
  | Switch accounts, edit an apex CNAME by hand, or other dashboard-only clicks | `references/dashboard-guide.md` | the click-path completed in the operator's browser |
@@ -27,19 +29,20 @@ This is the entry point for every Cloudflare task on the install. Pick the opera
27
29
 
28
30
  Neither the master token nor any per-scope token is ever written into a project tree, committed, or echoed into chat — the per-scope tokens persist only in the account-scoped secrets file, never in a deployable project tree.
29
31
 
30
- ### Token operations: run the helper first (binding)
32
+ ### Resolve the per-scope token before any `wrangler` / API call (binding)
31
33
 
32
- Before **any** Cloudflare API token operation (verify, list, mint, reuse), resolve the per-scope token with the deterministic helper, never a hand-built `curl`:
34
+ This binding governs **every** Pages, D1, DNS, or Access operation, not only calls you think of as "token operations." Before the first `wrangler pages …`, `wrangler d1 …`, or Cloudflare-API `curl` of a task, resolve the per-scope token with the deterministic helper and pass **that** token to the command:
33
35
 
34
36
  ```bash
35
37
  KEY=$(bash platform/plugins/cloudflare/bin/cf-token.sh <scope> <secrets-file>) # scope: pages | d1 | dns | access
36
38
  set -a; . <secrets-file>; set +a
37
- # the active, persisted per-scope token is now in the variable named by $KEY
39
+ # ${!KEY} now holds the active, persisted per-scope token. Pass it explicitly:
40
+ CLOUDFLARE_API_TOKEN="${!KEY}" wrangler pages project list
38
41
  ```
39
42
 
40
- The helper owns the one decision the false-negative failure turns on: it reads the master's `cfat_`/`cfut_` prefix and routes verify, `permission_groups`, and mint to the matching endpoint family, mints once if absent (no expiry), verifies through the transient post-mint window, persists to the secrets file, and prints only the secrets key name. It never prints a token value.
43
+ **The antipattern this exists to stop.** Sourcing the secrets file loads the **master** token into `CLOUDFLARE_API_TOKEN`. If you then run `wrangler pages …` (or a Cloudflare-API `curl`) with the environment as-sourced, you hand the **master** to the tool, not the per-scope token. The master is the wrong token for these endpoints, so Cloudflare rejects the call, and the rejection reads like a permissions wall when it is really a token-selection mistake. `. <secrets-file>` immediately followed by `wrangler pages …` is the exact sequence that trips this; the helper line above is what prevents it. Never let `CLOUDFLARE_API_TOKEN` reach `wrangler`/`curl` still holding the sourced master.
41
44
 
42
- A raw `curl` to a token endpoint chosen **before** the master's prefix is established is out of bounds. The wrong endpoint returns a stable false-negative that reads as "cannot mint" but is not: a `cfat_` master at `/user/tokens/verify` returns `Invalid API Token`, and a `cfut_` master at any `/accounts/{id}/...` endpoint returns `9109`. If you see either from a token probe, the cause is the endpoint, not the token. Stop and run the helper. Doing the verify or mint by hand is permitted only after reading the `references/api.md` section on routing token endpoints by prefix, and routing by the established prefix.
45
+ **A `10000` / `Invalid API Token` / `9109` from a `wrangler` or API call is a token-selection symptom, never "this token lacks permission."** The recovery is always the same: run `cf-token.sh <scope> <secrets-file>` and re-issue the command with the resolved per-scope token. Concretely: a just-sourced master handed to `wrangler pages …` returns `Authentication error [code: 10000]`; a `cfat_` master at `/user/tokens/verify` returns `Invalid API Token`; a `cfut_` master at any `/accounts/{id}/...` endpoint returns `9109`. None of these means "cannot deploy" or "lacks Pages/DNS permission." Do not fall back to a manual DNS record, a tunnel workaround, or an operator ask on the strength of one of these codes — run the helper and retry. The helper reads the master's `cfat_`/`cfut_` prefix, routes verify/`permission_groups`/mint to the matching endpoint family, mints once if absent (no expiry), verifies through the transient post-mint window, persists to the secrets file, and prints only the secrets key name; it never prints a token value. Doing verify or mint by hand is permitted only after reading the `references/api.md` section on routing token endpoints by prefix, and routing by the established prefix.
43
46
 
44
47
  ## Outcome contracts — what "done" means
45
48
 
@@ -73,7 +73,7 @@ either is a regression.
73
73
 
74
74
  **`/chat` header always shows the account name.** The admin/operator `HeaderMenu` centre always renders the active account's name alongside the brand logo, on every chat surface, regardless of the active conversation — `headerTitle = businessName || brand.productName`. There is no per-conversation header title: the active conversation's label stays in the sidebar and the Conversations flyout (each resolves its own per-session title independently), never in the header. The account name is `LocalBusiness.name`, resolved by `fetchAccountName(accountId)` in `neo4j-store.ts` with **no theme gate** — unlike `fetchBranding` (which returns null unless a colour/logo property is also set, and stays the source for actual theming). The house account is seeded with `LocalBusiness.name = <brand productName>` (`seed-neo4j.sh`, coalesced ON MATCH so a re-seed backfills a name-less node and never clobbers a name the business-profile recorder set later), so the sub-account picker's house row shows the product name (e.g. "SiteDesk"), not the raw accountId UUID. When a name is absent the resolver emits `[branding] account-name-fallback accountId=<id8>… reason=no-name`, and the picker falls back to the accountId; the seed + backfill is the name source. The lite variant header is unchanged.
75
75
 
76
- **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble showing the message text and, beneath it, a subtle always-visible time-of-day stamp (no label); delivered agent replies render as plain prose with the same time-of-day stamp beneath (the reply-document filename line stays, as prose); the stamp is HH:MM from the turn's `ts` (locale-formatted, right-aligned inside the operator bubble, left-aligned under agent prose), and a turn whose `ts` is null/unparseable shows no stamp, never an empty line — so only delivered operator and agent-reply turns are stamped, while tool runs, the collapsed "Thinking" block, and the agent-error banner stay time-free; a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp reader (`/whatsapp`) omits the prop and keeps the default `renderTimeline`, so its rendering is unchanged; both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx`, `app/chat/__tests__/transcript-render.test.tsx`). **Day-divider:** both renderers insert a centered `.day-divider` row between two consecutive timeline items on different local calendar days (label `Today`/`Yesterday`, else `Sat 14 Jun 2026`), so a thread spanning midnight is never an ambiguous run of HH:MM; the first dated item gets a leading divider, a null/unparseable `ts` marks no boundary, dividers are computed over the filtered `visibleItems`, and in `/chat` a day crossover flushes any open tool/think run first so a collapsed run never spans it. Per-bubble HH:MM stamps are unchanged. Shared helpers `dayKey`/`dayLabel`/`itemTs`/`DayDivider` live in `app/whatsapp/Transcript.tsx`.
76
+ **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble showing the message text and, beneath it, a subtle always-visible time-of-day stamp (no label); delivered agent replies render as plain prose with the same time-of-day stamp beneath (the reply-document filename line stays, as prose); the stamp is HH:MM from the turn's `ts` (locale-formatted, right-aligned inside the operator bubble, left-aligned under agent prose), and a turn whose `ts` is null/unparseable shows no stamp, never an empty line — so only delivered operator and agent-reply turns are stamped, while tool runs, the collapsed "Thinking" block, and the agent-error banner stay time-free; a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp/operator reader (`/whatsapp`, `OperatorConversations`) omits the prop and keeps the default `renderTimeline`, so its bubble/DM chrome is unchanged except that since the attachment-render extraction the default renderer draws inbound `operator-inbound` uploads and `agent-file` deliveries inline (image thumbnail / pdf.js preview / audio player / download chip) via the attachment renderer shared with `/chat`, extracted to `app/chat/attachment-render.tsx` to break the cycle with `transcript-render.tsx`; `renderChatTimeline` is untouched. Both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx`, `app/chat/__tests__/transcript-render.test.tsx`, `app/chat/__tests__/pdf-preview.test.tsx`). **Day-divider:** both renderers insert a centered `.day-divider` row between two consecutive timeline items on different local calendar days (label `Today`/`Yesterday`, else `Sat 14 Jun 2026`), so a thread spanning midnight is never an ambiguous run of HH:MM; the first dated item gets a leading divider, a null/unparseable `ts` marks no boundary, dividers are computed over the filtered `visibleItems`, and in `/chat` a day crossover flushes any open tool/think run first so a collapsed run never spans it. Per-bubble HH:MM stamps are unchanged. Shared helpers `dayKey`/`dayLabel`/`itemTs`/`DayDivider` live in `app/whatsapp/Transcript.tsx`.
77
77
 
78
78
  **`/chat` live activity line.** While a turn is in flight the transcript tail shows one ephemeral `ChatActivity` line (mounted only while `busy`, admin/operator only). It tracks real activity, not a timer: while a `Task` subagent runs it shows that subagent's headline (`agentType · description`, prefixed `N agents ·` when ≥2 are concurrent), sourced from `agent-<hex>.meta.json` via named `activity` SSE events the admin reader pushes from the session's `subagents/` dir; with no subagent active it shows a neutral word that advances only on a real turn arrival. The line carries a turn-elapsed clock and flips to a `stalled` state once nothing has been written for 5 minutes (`now − lastEmitAt`), so the operator can tell a wedge from progress without SSH. It is never added to the persisted timeline. Detail and the `[webchat-activity]` observability live in [`admin-webchat-native-channel.md`](../../../.docs/admin-webchat-native-channel.md).
79
79
 
@@ -323,13 +323,19 @@ authoritative. This section names the surfaces and what backs each.
323
323
  > **Admin variant only.** Every surface below belongs to the `admin` shell
324
324
  > variant. The **operator** variant (`operator.<host>`) mounts no sidebar at
325
325
  > all: its dashboard is the persistent admin webchat at `/` and the data
326
- > browser at `/data`, plus a read-only **Conversations** reader reached from a
327
- > dynamic burger item — header order Chat → Conversations → Data → Log out, in
328
- > a single-column `.platform.platform-operator` grid. The operator never sees
329
- > the sessions list or the nav rows; the Conversations reader lists the agent's
330
- > inbound channel conversations (WhatsApp, Telegram, public webchat) read-only
331
- > via the same admin-gated reader routes, never the operator's own session. The
332
- > operator surface is documented in full in the internal doc
326
+ > browser at `/data`, plus a **Conversations** item reached from a dynamic
327
+ > burger item — header order Chat → Conversations → Data → Log out, in a
328
+ > single-column `.platform.platform-operator` grid. On the operator **chat**
329
+ > surface the Conversations item opens the same in-chat session
330
+ > switcher the admin `/chat` surface uses (`ChatSurface`'s `SessionList` flyout):
331
+ > the operator's own admin webchat conversations, newest-first, each resuming
332
+ > live via `/chat?session=<id>`, with rename / archive / delete / end / new — not
333
+ > a read-only reader. The item gates on the flyout handler being wired, exactly
334
+ > like the admin and lite items. The read-only inbound-channel **Conversations**
335
+ > reader (WhatsApp, Telegram, public webchat, via the admin-gated
336
+ > `/api/whatsapp-reader/conversations` route) remains on `/data`'s Conversations
337
+ > item and in the admin Sidebar; it is no longer reachable from the operator chat
338
+ > surface. The operator surface is documented in full in the internal doc
333
339
  > `maxy-code/.docs/operator-dashboard.md`.
334
340
  >
335
341
  > **Operator `/chat`.** The reduced operator chrome renders on
@@ -339,6 +345,17 @@ authoritative. This section names the surfaces and what backs each.
339
345
  > operator shell. Unlike the operator root — which forces the canonical session
340
346
  > — operator `/chat` honors `?session=`, opening a specific session inside the
341
347
  > reduced shell. Admin-host `/chat` is unchanged (full developer shell).
348
+ > **Sub-account switch resets the session target.** Because `/chat` honors
349
+ > `?session=`, the header sub-account switcher (`useSubAccountSwitcher`) drops
350
+ > the `session` query param when it navigates after a switch, rather than
351
+ > reloading verbatim. A verbatim reload would keep the *previous* account's
352
+ > `?session=<id>`, which the reloaded surface re-pins — stranding the operator
353
+ > on the wrong account's session . Dropping the param lands on the
354
+ > account-scoped canonical surface, so `resolveCanonical` resolves the switched
355
+ > account's latest owned session, or the new-session splash when it has none.
356
+ > The pathname is preserved (a switch on `/chat` stays on `/chat`); the operator
357
+ > root `/` and bare `/chat` were always correct (they force-ignore / carry no
358
+ > `?session=`).
342
359
  >
343
360
  > **Width.** The operator header, the operator chat column, and the `/data`
344
361
  > header+toolbar+body all render in one centred column bounded to
@@ -353,15 +370,17 @@ authoritative. This section names the surfaces and what backs each.
353
370
  > surface; the rest of the admin shell stays edge-to-edge. The public webchat
354
371
  > (`.public-surface`) inherits the same value.
355
372
  >
356
- > **Conversations item.** The dynamic Conversations burger item appears on
357
- > **both** the operator chat surface and `/data` whenever ≥1 reviewable
358
- > conversation exists (the count/channels are fetched per surface from
359
- > `/whatsapp-reader/conversations` with the operator's session key). On `/data`
360
- > selecting it renders the same read-only reader in place (the file browser is
361
- > swapped for it; Back returns). The flyout-open signal
362
- > `[operator-ui] op=conversations-menu present=<bool> channels=[…] count=<n>`
363
- > fires on `/data` too `present=false` there while the chat surface shows the
364
- > item is the props-not-wired signature.
373
+ > **Conversations item.** The dynamic Conversations burger item gates on the
374
+ > host wiring its open-handler (like the admin and lite items), not on a channel
375
+ > count. On the operator **chat** surface the handler is always wired, so the
376
+ > item is always present and opens the live session switcher (above). On `/data`
377
+ > the handler is wired only when ≥1 reviewable inbound conversation exists
378
+ > (fetched from `/whatsapp-reader/conversations` with the operator's session
379
+ > key), and selecting it renders the read-only reader in place (the file browser
380
+ > is swapped for it; Back returns) so `/data` keeps its hide-when-empty
381
+ > behaviour. The flyout-open signal `[operator-ui] op=conversations-menu
382
+ > present=<bool>` fires for the operator variant on both surfaces; `present=false`
383
+ > while the operator expects the item is the handler-not-wired signature.
365
384
 
366
385
  | Surface | What it does | Backed by |
367
386
  |---|---|---|
@@ -21,7 +21,7 @@ Activate when the operator asks for any of:
21
21
  Do **not** activate for:
22
22
 
23
23
  - Listing or searching emails as the end-goal (no draft intent) — let `email-search` / `email-graph-query` answer directly.
24
- - Setup, status checks, OTP extraction — those are direct tool calls. (For Google/Workspace setup the agent must require an app password, never the regular password — see the email plugin's `references/email-reference.md` § Setup → Google. For Microsoft / Microsoft 365 addresses there is no email-plugin path at all — register via the `outlook` Graph plugin's `outlook-account-register`, not `email-setup` — see § Setup → Microsoft.)
24
+ - Setup, status checks, OTP extraction — those are direct tool calls. (For Google/Workspace setup the agent must require an app password, never the regular password — see the email plugin's `references/email-reference.md` § Setup → Google, opened with the `plugin-read` tool (`file="references/email-reference.md"`), which resolves it at the plugin root; a plain `Read` against this skill's base directory misses. For Microsoft / Microsoft 365 addresses there is no email-plugin path at all — register via the `outlook` Graph plugin's `outlook-account-register`, not `email-setup` — see § Setup → Microsoft.)
25
25
  - Any automated send loop. If the operator asks for drip / scheduled / broadcast sends, decline and explain that this skill is human-in-the-loop only.
26
26
 
27
27
  ## Flow — reply to an existing thread
@@ -63,5 +63,7 @@ Subsequent tool calls use the persisted refresh token to mint access tokens tran
63
63
 
64
64
  ## Reference files
65
65
 
66
+ Open each with the `plugin-read` tool (`file="references/auth.md"`), which resolves the path at the plugin root. A plain `Read` of `references/auth.md` against this skill's base directory misses, because these files live one level up at the plugin root, not under the skill.
67
+
66
68
  - [`references/auth.md`](references/auth.md) — Entra app registration + PKCE flow + re-vendor procedure
67
69
  - [`references/graph-surfaces.md`](references/graph-surfaces.md) — Graph endpoint and response shape per tool
@@ -22,6 +22,8 @@ A finished Telegram setup has all of these, and the skill is not done until each
22
22
 
23
23
  ## Flow
24
24
 
25
+ Read `references/setup-guide.md` (cited throughout this flow) with the `plugin-read` tool (`file="references/setup-guide.md"`), which resolves it at the plugin root. A plain `Read` against this skill's base directory misses, because the file lives one level up at the plugin root, not under the skill.
26
+
25
27
  ### Step 1 — Get the bot token
26
28
 
27
29
  Follow `references/setup-guide.md` "Creating a Bot": the operator creates the bot via @BotFather and gives you the token (e.g. `123456:ABC-DEF…`). Confirm you have it before continuing.
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # Task 1362 — the reference-reachability guard.
3
+ # A SKILL.md's plugin-root-only bare `references/…` citation is reachable ONLY
4
+ # when the SKILL.md declares the `plugin-read` convention. Without it, a plain
5
+ # Read against the announced skill base dir misses, so the citation is a
6
+ # violation. Skill-local references, full `plugins/<name>/references/…` paths,
7
+ # and PLUGIN.md plugin-root references are unaffected.
8
+ set -u
9
+ GATE="$(cd "$(dirname "$0")/.." && pwd)/check-plugin-references.mjs"
10
+ [[ -f "$GATE" ]] || { echo "FAIL: gate script missing at $GATE" >&2; exit 1; }
11
+ PASS=0; FAIL=0
12
+
13
+ ROOT=$(mktemp -d)
14
+ PREMIUM="$ROOT/premium-empty"
15
+ mkdir -p "$PREMIUM"
16
+ trap 'rm -rf "$ROOT"' EXIT
17
+
18
+ reset_root() { rm -rf "$ROOT/platform"; }
19
+
20
+ # Write a SKILL.md for plugin "$1" (skill slug == plugin name) with body "$2".
21
+ make_skill() {
22
+ local plugin="$1" body="$2"
23
+ local dir="$ROOT/platform/plugins/$plugin/skills/$plugin"
24
+ mkdir -p "$dir"
25
+ { printf -- '---\nname: %s\ndescription: test skill\n---\n\n' "$plugin"; printf '%s\n' "$body"; } > "$dir/SKILL.md"
26
+ }
27
+
28
+ # Create a reference file at an arbitrary path under $ROOT.
29
+ make_ref() { local rel="$1"; mkdir -p "$ROOT/$(dirname "$rel")"; printf 'ref body\n' > "$ROOT/$rel"; }
30
+
31
+ check() {
32
+ local desc="$1" expected="$2"
33
+ node "$GATE" "$ROOT/platform" "$PREMIUM" >/dev/null 2>&1
34
+ local got=$?
35
+ if [[ "$got" -eq "$expected" ]]; then echo "PASS: $desc"; ((PASS++))
36
+ else echo "FAIL: $desc (expected exit $expected, got $got)"; ((FAIL++)); fi
37
+ }
38
+
39
+ # CASE 1: plugin-root-only ref, NO plugin-read declared → violation.
40
+ reset_root
41
+ make_ref "platform/plugins/foo/references/api.md"
42
+ make_skill "foo" 'Read \`references/api.md\` before the API call.'
43
+ check "plugin-root-only ref without plugin-read fails" 1
44
+
45
+ # CASE 2: same ref, plugin-read declared → passes.
46
+ reset_root
47
+ make_ref "platform/plugins/foo/references/api.md"
48
+ make_skill "foo" 'Open \`references/api.md\` with the \`plugin-read\` tool before the API call.'
49
+ check "plugin-root-only ref with plugin-read passes" 0
50
+
51
+ # CASE 3: skill-local ref, NO plugin-read → passes (plain Read resolves it).
52
+ reset_root
53
+ make_ref "platform/plugins/foo/skills/foo/references/local.md"
54
+ make_skill "foo" 'Read \`references/local.md\` for the details.'
55
+ check "skill-local ref without plugin-read passes" 0
56
+
57
+ # CASE 4: full plugins/<name>/references/... path, NO plugin-read → passes.
58
+ reset_root
59
+ make_ref "platform/plugins/foo/references/api.md"
60
+ make_skill "foo" 'Read \`plugins/foo/references/api.md\` before the API call.'
61
+ check "full plugin-path ref without plugin-read passes" 0
62
+
63
+ # CASE 5: genuinely missing ref (neither skill-local nor plugin-root) → violation.
64
+ reset_root
65
+ make_skill "foo" 'Open \`references/ghost.md\` with \`plugin-read\`.'
66
+ check "missing ref fails even with plugin-read" 1
67
+
68
+ # CASE 6: a MISSING full plugins/<name>/references/... path from a SKILL.md without
69
+ # plugin-read → violation, and the plugin-root "declare plugin-read" hint must NOT
70
+ # fire (the hint is scoped to the bare references/ shape only; a full path is not
71
+ # reachability-limited by the missing convention). Guards the Task 1362 hint-scope fix.
72
+ reset_root
73
+ make_skill "foo" 'Read \`plugins/foo/references/ghost.md\` before the API call.'
74
+ OUT=$(node "$GATE" "$ROOT/platform" "$PREMIUM" 2>&1); GOT=$?
75
+ if [[ "$GOT" -eq 1 ]] && ! grep -q "exists at plugin root" <<<"$OUT"; then
76
+ echo "PASS: missing full-path ref fails without a false plugin-read hint"; ((PASS++))
77
+ else
78
+ echo "FAIL: missing full-path ref (exit=$GOT, hint present=$(grep -qc 'exists at plugin root' <<<"$OUT" && echo yes || echo no))"; ((FAIL++))
79
+ fi
80
+
81
+ echo ""
82
+ echo "Results: $PASS passed, $FAIL failed"
83
+ [[ "$FAIL" -eq 0 ]] && exit 0 || exit 1