@rubytech/create-maxy-code 0.1.496 → 0.1.497

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 (205) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +327 -0
  3. package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +35 -0
  4. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +20 -6
  5. package/payload/platform/plugins/docs/references/admin-ui.md +14 -4
  6. package/payload/platform/plugins/docs/references/outlook-guide.md +5 -1
  7. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  8. package/payload/platform/plugins/outlook/PLUGIN.md +6 -2
  9. package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.d.ts +2 -0
  10. package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.d.ts.map +1 -0
  11. package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.js +54 -0
  12. package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.js.map +1 -0
  13. package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.d.ts +2 -0
  14. package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.d.ts.map +1 -0
  15. package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.js +19 -0
  16. package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.js.map +1 -0
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.d.ts +2 -0
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.d.ts.map +1 -0
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.js +20 -0
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.js.map +1 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/index.js +21 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  23. package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.d.ts +40 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.d.ts.map +1 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.js +143 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.js.map +1 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.d.ts +5 -0
  28. package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.d.ts.map +1 -0
  29. package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.js +44 -0
  30. package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.js.map +1 -0
  31. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +14 -0
  32. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
  33. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +13 -1
  34. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
  35. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.d.ts +27 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.d.ts.map +1 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.js +69 -0
  38. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.js.map +1 -0
  39. package/payload/platform/plugins/outlook/mcp/package.json +1 -0
  40. package/payload/platform/plugins/outlook/references/graph-surfaces.md +10 -1
  41. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +4 -0
  42. package/payload/platform/plugins/outlook/skills/outlook-mail-ingest/SKILL.md +85 -0
  43. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  45. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  46. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  47. package/payload/server/public/activity.html +5 -5
  48. package/payload/server/public/assets/{AdminLoginScreens-CFF28MGy.js → AdminLoginScreens-BXDYzNbq.js} +1 -1
  49. package/payload/server/public/assets/AdminShell-CiTHCp5t.js +2 -0
  50. package/payload/server/public/assets/{activity-CElZcdjR.js → activity-cVe5ZnG7.js} +1 -1
  51. package/payload/server/public/assets/admin-BbcLizN0.js +1 -0
  52. package/payload/server/public/assets/{arc-CFMJyMm-.js → arc-BrHWN2Kr.js} +1 -1
  53. package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +1 -0
  54. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CiSSgdL8.js → architectureDiagram-Q4EWVU46-1OxSL3rT.js} +1 -1
  55. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-_sXtIW5c.js → blockDiagram-DXYQGD6D-C6yKiblF.js} +1 -1
  56. package/payload/server/public/assets/{bot-Q-JmeVT3.js → bot-Bb8yj02k.js} +1 -1
  57. package/payload/server/public/assets/{browser-BrfPJMYF.js → browser-BdWeCfYJ.js} +1 -1
  58. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Bh7OAMXs.js → c4Diagram-AHTNJAMY-BzTyHS1I.js} +1 -1
  59. package/payload/server/public/assets/calendar-B4KbpiXR.js +1 -0
  60. package/payload/server/public/assets/channel-uhtvXKHn.js +1 -0
  61. package/payload/server/public/assets/chat-BEcbZB3e.js +1 -0
  62. package/payload/server/public/assets/chevron-left-Cy_RXwhn.js +1 -0
  63. package/payload/server/public/assets/chevron-right-CEjdB4AT.js +1 -0
  64. package/payload/server/public/assets/{chunk-2KRD3SAO-D4fktAwW.js → chunk-2KRD3SAO-BGAUfWvM.js} +1 -1
  65. package/payload/server/public/assets/{chunk-336JU56O-CcQoKH2G.js → chunk-336JU56O-M1H_u2Qv.js} +2 -2
  66. package/payload/server/public/assets/chunk-426QAEUC-D4XtWgLR.js +1 -0
  67. package/payload/server/public/assets/{chunk-4BX2VUAB-C13Wz3zS.js → chunk-4BX2VUAB-CkxXXCiF.js} +1 -1
  68. package/payload/server/public/assets/{chunk-4TB4RGXK-BL48D7gN.js → chunk-4TB4RGXK-DPWZYGHH.js} +1 -1
  69. package/payload/server/public/assets/{chunk-55IACEB6-TmOKdy8a.js → chunk-55IACEB6-CBkNmqrt.js} +1 -1
  70. package/payload/server/public/assets/{chunk-5FUZZQ4R-DZ_jSF6N.js → chunk-5FUZZQ4R-uIqIu9ha.js} +1 -1
  71. package/payload/server/public/assets/{chunk-5PVQY5BW-De4G1vfV.js → chunk-5PVQY5BW-B_S7XzpE.js} +1 -1
  72. package/payload/server/public/assets/{chunk-67CJDMHE-Cif9Uu5C.js → chunk-67CJDMHE-BbMV1lgW.js} +1 -1
  73. package/payload/server/public/assets/{chunk-7N4EOEYR-WjTHCXnd.js → chunk-7N4EOEYR-C49Mv-nQ.js} +1 -1
  74. package/payload/server/public/assets/{chunk-AA7GKIK3-CwCfdQmb.js → chunk-AA7GKIK3-Dy_pJOp_.js} +1 -1
  75. package/payload/server/public/assets/{chunk-BSJP7CBP-9A67yXwT.js → chunk-BSJP7CBP-Ce-OmPqn.js} +1 -1
  76. package/payload/server/public/assets/{chunk-CIAEETIT-DPMiWSBi.js → chunk-CIAEETIT-tnjq-APC.js} +1 -1
  77. package/payload/server/public/assets/{chunk-EDXVE4YY-C2f_Qvx-.js → chunk-EDXVE4YY-uzO7kO4e.js} +1 -1
  78. package/payload/server/public/assets/{chunk-ENJZ2VHE-B-Q9EbSQ.js → chunk-ENJZ2VHE-DQMOsG1f.js} +1 -1
  79. package/payload/server/public/assets/{chunk-FMBD7UC4-Bmt_ZRlV.js → chunk-FMBD7UC4-Top6y08S.js} +1 -1
  80. package/payload/server/public/assets/{chunk-FOC6F5B3-DfGJGxpX.js → chunk-FOC6F5B3-B9LPNCUS.js} +1 -1
  81. package/payload/server/public/assets/{chunk-ICPOFSXX-Wyym1Q8K.js → chunk-ICPOFSXX-DeIt3WoY.js} +2 -2
  82. package/payload/server/public/assets/{chunk-K5T4RW27-DMpoaqw2.js → chunk-K5T4RW27-DAnRM04h.js} +1 -1
  83. package/payload/server/public/assets/{chunk-KGLVRYIC-DYUFgri2.js → chunk-KGLVRYIC-C0QYcbLe.js} +1 -1
  84. package/payload/server/public/assets/{chunk-LIHQZDEY-x3LmeKmx.js → chunk-LIHQZDEY-BjvA5ykt.js} +1 -1
  85. package/payload/server/public/assets/{chunk-ORNJ4GCN-BeuNAWkW.js → chunk-ORNJ4GCN-Cs46Gj7z.js} +1 -1
  86. package/payload/server/public/assets/{chunk-OYMX7WX6-BUWVAfQA.js → chunk-OYMX7WX6-CBHIFMBx.js} +1 -1
  87. package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +1 -0
  88. package/payload/server/public/assets/{chunk-U2HBQHQK-DKS5adQo.js → chunk-U2HBQHQK-5_DL8yx6.js} +1 -1
  89. package/payload/server/public/assets/{chunk-X2U36JSP-CT_UM4IN.js → chunk-X2U36JSP-BDCaWRe9.js} +1 -1
  90. package/payload/server/public/assets/{chunk-XPW4576I-ByFnN4JO.js → chunk-XPW4576I-B10QuxYb.js} +1 -1
  91. package/payload/server/public/assets/{chunk-YZCP3GAM-DCetC20J.js → chunk-YZCP3GAM-81jJqyDd.js} +1 -1
  92. package/payload/server/public/assets/{chunk-ZZ45TVLE-Bh1QIv8N.js → chunk-ZZ45TVLE-DGx3J6Dx.js} +1 -1
  93. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CBH5ngSm.js +1 -0
  94. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Ck3eNZce.js +1 -0
  95. package/payload/server/public/assets/clone-Cv8evfNb.js +1 -0
  96. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-DgLEaApT.js → cose-bilkent-S5V4N54A-CRDvLRXs.js} +1 -1
  97. package/payload/server/public/assets/{dagre-unhMIyVo.js → dagre--rdjN6xZ.js} +1 -1
  98. package/payload/server/public/assets/{dagre-KV5264BT-BfkUPrEV.js → dagre-KV5264BT-DHbVQPnV.js} +1 -1
  99. package/payload/server/public/assets/data-L-hMHLBN.js +1 -0
  100. package/payload/server/public/assets/{diagram-5BDNPKRD-BjjwcTD9.js → diagram-5BDNPKRD-D8HC1Idg.js} +1 -1
  101. package/payload/server/public/assets/{diagram-G4DWMVQ6-BbgWtVSS.js → diagram-G4DWMVQ6-evvg8aey.js} +1 -1
  102. package/payload/server/public/assets/{diagram-MMDJMWI5-DyAnkWGy.js → diagram-MMDJMWI5-4hZuk-Dd.js} +1 -1
  103. package/payload/server/public/assets/{diagram-TYMM5635-SAMMoLxq.js → diagram-TYMM5635-Cwei1ql9.js} +1 -1
  104. package/payload/server/public/assets/{erDiagram-SMLLAGMA-Cvo-5-fD.js → erDiagram-SMLLAGMA-BG5OK4Fx.js} +1 -1
  105. package/payload/server/public/assets/{flatten-BXzaHgtL.js → flatten-naJ5PJ4f.js} +1 -1
  106. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-y3o2Ycld.js → flowDiagram-DWJPFMVM-CPB_poda.js} +1 -1
  107. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BEm_CQaK.js → ganttDiagram-T4ZO3ILL-CD6BL1UP.js} +1 -1
  108. package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +1 -0
  109. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CqmOxS7F.js → gitGraphDiagram-UUTBAWPF-C-e8FDGn.js} +1 -1
  110. package/payload/server/public/assets/{graph-BLO4nVi3.js → graph-COyGp2x1.js} +3 -3
  111. package/payload/server/public/assets/{graph-labels-ibEi7ggo.js → graph-labels-CR-HqNdV.js} +1 -1
  112. package/payload/server/public/assets/{graphlib-_HfbQcBf.js → graphlib-CdCZY1Mj.js} +1 -1
  113. package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +1 -0
  114. package/payload/server/public/assets/infoDiagram-42DDH7IO-BDInZV9G.js +2 -0
  115. package/payload/server/public/assets/{isEmpty-uB1dEu_Z.js → isEmpty-Bnpg7b8f.js} +1 -1
  116. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-C_d0vcZC.js → ishikawaDiagram-UXIWVN3A-CFLhfPpo.js} +1 -1
  117. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DsjRBqlW.js → journeyDiagram-VCZTEJTY-BN0PaMgT.js} +1 -1
  118. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BwtK6LS-.js → kanban-definition-6JOO6SKY-CmRilACp.js} +1 -1
  119. package/payload/server/public/assets/{line-szBCFOLJ.js → line-DR6Zk7d6.js} +1 -1
  120. package/payload/server/public/assets/{linear-X3O7lwK3.js → linear-CAOWPodQ.js} +1 -1
  121. package/payload/server/public/assets/{maximize-2-CezchDEF.js → maximize-2-ne1kaQlS.js} +1 -1
  122. package/payload/server/public/assets/{mermaid-parser.core-BavvMAhU.js → mermaid-parser.core-0vmQl6Gn.js} +2 -2
  123. package/payload/server/public/assets/{mermaid.core-Br2F9Hdm.js → mermaid.core-DWgpHiLh.js} +3 -3
  124. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-4qlwXoJ-.js → mindmap-definition-QFDTVHPH-CViR7VGV.js} +1 -1
  125. package/payload/server/public/assets/operator-Q0F_EEyq.js +1 -0
  126. package/payload/server/public/assets/{ordinal-CZY9laxg.js → ordinal-Wx4m9zHk.js} +1 -1
  127. package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +1 -0
  128. package/payload/server/public/assets/{page-BL1ko2_-.js → page-DwfYACyC.js} +4 -4
  129. package/payload/server/public/assets/page-bRjlk8Dt.js +1 -0
  130. package/payload/server/public/assets/{pdf-render-H884L3EI.js → pdf-render-0hyh3Jlf.js} +1 -1
  131. package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +1 -0
  132. package/payload/server/public/assets/{pieDiagram-DEJITSTG-BbXryIg0.js → pieDiagram-DEJITSTG-B7YQ1_Kr.js} +1 -1
  133. package/payload/server/public/assets/{public-DxfSLhNp.js → public-BACeh5xm.js} +1 -1
  134. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BHLjcvh3.js → quadrantDiagram-34T5L4WZ-Dxs0nGu5.js} +1 -1
  135. package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +1 -0
  136. package/payload/server/public/assets/{reduce-BGQzAenn.js → reduce-BUmc8RDR.js} +1 -1
  137. package/payload/server/public/assets/{requirementDiagram-MS252O5E-BXaVO0lk.js → requirementDiagram-MS252O5E-DDNYDqZc.js} +1 -1
  138. package/payload/server/public/assets/{rotate-ccw-CWvGAut4.js → rotate-ccw-tY4mfSrJ.js} +1 -1
  139. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CmN1s17Q.js → sankeyDiagram-XADWPNL6-DLWoegU5.js} +1 -1
  140. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DC08e5NY.js → sequenceDiagram-FGHM5R23-DINNPlTY.js} +1 -1
  141. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DnoQMuOW.js → stateDiagram-FHFEXIEX-Dsan8EIl.js} +1 -1
  142. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B4yeYYgL.js +1 -0
  143. package/payload/server/public/assets/{tasks-DlXIN3v3.js → tasks-C4nPXsbX.js} +1 -1
  144. package/payload/server/public/assets/{time-entry-format-VzHwljTv.js → time-entry-format-CmqjpGG2.js} +1 -1
  145. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-eTn3uRaH.js → timeline-definition-GMOUNBTQ-CISzecz5.js} +1 -1
  146. package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +1 -0
  147. package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +1 -0
  148. package/payload/server/public/assets/{triangle-alert-C5BpoPlq.js → triangle-alert-DKZ69dOa.js} +1 -1
  149. package/payload/server/public/assets/{useCopyFeedback-h1qT8mpZ.js → useCopyFeedback-Cf5jFDR5.js} +1 -1
  150. package/payload/server/public/assets/useSubAccountSwitcher-DFnDNIDO.css +1 -0
  151. package/payload/server/public/assets/useSubAccountSwitcher-DdctgpPL.js +9 -0
  152. package/payload/server/public/assets/{useVoiceRecorder-kqMitFlk.js → useVoiceRecorder-BVdKbTku.js} +2 -2
  153. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DNx6o_ZQ.js → vennDiagram-DHZGUBPP-Bh2s76nJ.js} +1 -1
  154. package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +1 -0
  155. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BZOchqF3.js → wardleyDiagram-NUSXRM2D-Grzw0FHg.js} +1 -1
  156. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-ZBEGH6ZI.js → xychartDiagram-5P7HB3ND-7pY90HjH.js} +1 -1
  157. package/payload/server/public/browser.html +4 -4
  158. package/payload/server/public/calendar.html +6 -6
  159. package/payload/server/public/chat.html +13 -14
  160. package/payload/server/public/data.html +11 -11
  161. package/payload/server/public/graph.html +9 -10
  162. package/payload/server/public/index.html +15 -16
  163. package/payload/server/public/operator.html +14 -15
  164. package/payload/server/public/public.html +13 -14
  165. package/payload/server/public/tasks.html +6 -6
  166. package/payload/server/public/assets/AdminShell-ClIT8c0K.js +0 -2
  167. package/payload/server/public/assets/admin-BHiA9_Ks.js +0 -1
  168. package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +0 -1
  169. package/payload/server/public/assets/calendar-D45S_aal.js +0 -1
  170. package/payload/server/public/assets/channel-BMyXE4zc.js +0 -1
  171. package/payload/server/public/assets/chat-gy8scQov.js +0 -1
  172. package/payload/server/public/assets/chevron-down-CdxvexgY.js +0 -1
  173. package/payload/server/public/assets/chevron-left-D5LLz6ke.js +0 -1
  174. package/payload/server/public/assets/chevron-right-DzPmwDew.js +0 -1
  175. package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +0 -1
  176. package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +0 -1
  177. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +0 -1
  178. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +0 -1
  179. package/payload/server/public/assets/clone-BiTMTzp_.js +0 -1
  180. package/payload/server/public/assets/data-CUsNHUpu.js +0 -1
  181. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +0 -1
  182. package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +0 -1
  183. package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +0 -2
  184. package/payload/server/public/assets/operator-B-3vkwO8.js +0 -1
  185. package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +0 -1
  186. package/payload/server/public/assets/page-BbxoY4QI.js +0 -1
  187. package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +0 -1
  188. package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +0 -1
  189. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +0 -1
  190. package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +0 -1
  191. package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +0 -1
  192. package/payload/server/public/assets/useSubAccountSwitcher-DDStL7Nt.js +0 -9
  193. package/payload/server/public/assets/useSubAccountSwitcher-DnfX9Uxe.css +0 -1
  194. package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +0 -1
  195. /package/payload/server/public/assets/{_baseFor-CMWvz0uA.js → _baseFor-k8hYPe-U.js} +0 -0
  196. /package/payload/server/public/assets/{array-Cny_NT6T.js → array-BFDiaBgf.js} +0 -0
  197. /package/payload/server/public/assets/{cytoscape.esm-De0CCmB2.js → cytoscape.esm-Ch0xptA4.js} +0 -0
  198. /package/payload/server/public/assets/{defaultLocale-BahRht8U.js → defaultLocale-ZeknFqNe.js} +0 -0
  199. /package/payload/server/public/assets/{dist-C9D_xz81.js → dist-B2N0v8hZ.js} +0 -0
  200. /package/payload/server/public/assets/{init-DoIvjoJ_.js → init-0VJVrkRJ.js} +0 -0
  201. /package/payload/server/public/assets/{katex-Bfn1OZEl.js → katex-Br7bH10N.js} +0 -0
  202. /package/payload/server/public/assets/{path-CTezU8L5.js → path-INs8XTPH.js} +0 -0
  203. /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-CH6UZRzu.js} +0 -0
  204. /package/payload/server/public/assets/{rough.esm-BJo4pvXu.js → rough.esm-DU9bZLvZ.js} +0 -0
  205. /package/payload/server/public/assets/{src-BTKk8KAG.js → src-DHSQDYx-.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.496",
3
+ "version": "0.1.497",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -0,0 +1,327 @@
1
+ # Task 1942 — Sub-account switcher to sidebar brand head — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. Steps use checkbox (`- [ ]`) syntax.
4
+
5
+ **Goal:** Open the sub-account picker by clicking the sidebar brand head; remove it from the footer kebab.
6
+
7
+ **Architecture:** `Sidebar.tsx` owns a `switcherOpen` state and renders the brand head as a button (icon + lockup + chevron) when the switcher is available, with `SubAccountPicker` + a full-screen scrim anchored beneath `.side-brand`. `AccountMenu.tsx` loses the switcher block, its five props, and the `SubAccountPicker` import. `globals.css` makes `.side-brand` a positioned anchor, re-anchors the panel left-aligned, and deletes the dead footer overrides.
8
+
9
+ **Tech Stack:** React 18, TypeScript, vitest + jsdom (Node 22), lucide-react icons.
10
+
11
+ ## Global Constraints
12
+
13
+ - Node 22 for the jsdom test run (`platform/ui` requirement).
14
+ - Switcher availability gate is exactly `subAccounts.length > 1 && Boolean(onSwitchAccount)`; unchanged from today.
15
+ - No new task-id leaks in shipped docs; docs edit at `platform/plugins/docs/references/admin-ui.md`.
16
+ - All paths below are relative to the worktree's `maxy-code/platform/ui/`.
17
+
18
+ ---
19
+
20
+ ### Task 1: Strip the switcher out of AccountMenu
21
+
22
+ **Files:**
23
+ - Modify: `app/components/AccountMenu.tsx`
24
+ - Test: `app/components/__tests__/AccountMenu.test.tsx`
25
+
26
+ **Interfaces:**
27
+ - Produces: `AccountMenuProps` with no `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` fields.
28
+
29
+ - [ ] **Step 1: Update the test to assert the switcher is gone**
30
+
31
+ In `AccountMenu.test.tsx`, delete the two switcher tests (`renders no Switch account affordance with 0-1 sub-accounts` and `opens the sub-account picker and fires onSwitchAccount + refreshAccounts`, lines 56-88). Add one test:
32
+
33
+ ```tsx
34
+ it('renders no Switch account affordance (relocated to sidebar brand head, Task 1942)', () => {
35
+ render(<AccountMenu {...baseProps} variant="admin" cacheKey="k" onOpenConversations={vi.fn()} />)
36
+ expect(screen.queryByRole('menuitem', { name: /switch account/i })).toBeNull()
37
+ expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
38
+ })
39
+ ```
40
+
41
+ Remove `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` from any remaining render in the file.
42
+
43
+ - [ ] **Step 2: Run — expect fail (props still typed / item still renders)**
44
+
45
+ Run: `npx vitest run app/components/__tests__/AccountMenu.test.tsx`
46
+ Expected: FAIL (the Switch account menuitem still renders).
47
+
48
+ - [ ] **Step 3: Remove the switcher from AccountMenu.tsx**
49
+
50
+ - Delete the `import { SubAccountPicker } from './SubAccountPicker'` line.
51
+ - In `AccountMenuProps`, delete the five switcher prop declarations and their doc comment block (`subAccounts?`, `activeAccountId?`, `switchingAccount?`, `onSwitchAccount?`, `refreshAccounts?`).
52
+ - In the destructure, remove `subAccounts = []`, `activeAccountId = null`, `switchingAccount = false`, `onSwitchAccount`, `refreshAccounts`.
53
+ - Delete the `switcherOpen` state and `switcherAvailable` const.
54
+ - Delete the whole `{switcherAvailable && ( … <SubAccountPicker … /> … )}` JSX block.
55
+ - Remove `UserRound` from the lucide-react import (it was used only by the deleted block; grep the file to confirm no other use).
56
+
57
+ - [ ] **Step 4: Run — expect pass**
58
+
59
+ Run: `npx vitest run app/components/__tests__/AccountMenu.test.tsx`
60
+ Expected: PASS.
61
+
62
+ - [ ] **Step 5: Commit**
63
+
64
+ ```bash
65
+ git add maxy-code/platform/ui/app/components/AccountMenu.tsx maxy-code/platform/ui/app/components/__tests__/AccountMenu.test.tsx
66
+ git commit -m "refactor(1942): remove sub-account switcher from AccountMenu"
67
+ ```
68
+
69
+ ---
70
+
71
+ ### Task 2: Brand head becomes the switcher trigger in Sidebar
72
+
73
+ **Files:**
74
+ - Modify: `app/Sidebar.tsx`
75
+ - Test: `app/__tests__/Sidebar-subaccount-switcher.test.tsx` (create), `app/__tests__/Sidebar-user-footer.test.tsx`
76
+
77
+ **Interfaces:**
78
+ - Consumes: `SubAccountPicker` from `./components/SubAccountPicker`; `ChevronDown` from `lucide-react`; existing `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` Sidebar props.
79
+
80
+ - [ ] **Step 1: Write the failing test (new file)**
81
+
82
+ Create `app/__tests__/Sidebar-subaccount-switcher.test.tsx`:
83
+
84
+ ```tsx
85
+ import React from 'react'
86
+ import { render, screen, fireEvent } from '@testing-library/react'
87
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
88
+ import { Sidebar } from '../Sidebar'
89
+
90
+ function jsonResponse(body: unknown, status = 200): Response {
91
+ return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } }) as Response
92
+ }
93
+ function routedFetch() {
94
+ return vi.spyOn(global, 'fetch').mockImplementation((input: RequestInfo | URL) => {
95
+ const url = typeof input === 'string' ? input : input.toString()
96
+ if (url.includes('/api/admin/sidebar-sessions')) return Promise.resolve(jsonResponse({ accountId: 'acct', sessions: [] }))
97
+ if (url.includes('/api/admin/system-stats')) return Promise.resolve(jsonResponse({ cpuPct: null, memUsedPct: 0.5, platform: 'darwin' }))
98
+ if (url.includes('/api/admin/version')) return Promise.resolve(jsonResponse({ installed: '1.0.0', latest: null, updateAvailable: false }))
99
+ return Promise.resolve(jsonResponse({}, 404))
100
+ })
101
+ }
102
+ const ACCOUNTS = [
103
+ { accountId: 'a1', businessName: 'House', role: 'admin', isHouse: true },
104
+ { accountId: 'a2', businessName: 'Client', role: 'admin', isHouse: false },
105
+ ]
106
+ function baseProps(over: Record<string, unknown> = {}) {
107
+ return {
108
+ businessName: 'House', cacheKey: 'k', role: 'admin', userName: 'Ada Ops', userAvatar: null,
109
+ onSelectProjects: vi.fn(), onSelectTasks: vi.fn(), onCloseMobileDrawer: vi.fn(),
110
+ collapsed: false, mobileDrawerOpen: false, onSelectData: vi.fn(),
111
+ onLogout: vi.fn(), onDisconnect: vi.fn(async () => true), disconnecting: false, claudeConnected: true,
112
+ ...over,
113
+ }
114
+ }
115
+ beforeEach(() => { Object.defineProperty(document, 'hidden', { value: false, configurable: true }) })
116
+ afterEach(() => { vi.restoreAllMocks() })
117
+
118
+ describe('Sidebar sub-account switcher on the brand head (Task 1942)', () => {
119
+ it('brand head is a button that toggles the picker and fires refreshAccounts', () => {
120
+ routedFetch()
121
+ const refreshAccounts = vi.fn()
122
+ render(<Sidebar {...baseProps({ subAccounts: ACCOUNTS, activeAccountId: 'a1', switchingAccount: false, onSwitchAccount: vi.fn(), refreshAccounts })} />)
123
+ const head = screen.getByRole('button', { name: /switch sub-account/i })
124
+ expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
125
+ fireEvent.click(head)
126
+ expect(screen.getByPlaceholderText(/search sub-accounts/i)).toBeInTheDocument()
127
+ expect(refreshAccounts).toHaveBeenCalledTimes(1)
128
+ fireEvent.click(head)
129
+ expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
130
+ })
131
+
132
+ it('selecting a card calls onSwitchAccount and closes the picker', () => {
133
+ routedFetch()
134
+ const onSwitchAccount = vi.fn()
135
+ render(<Sidebar {...baseProps({ subAccounts: ACCOUNTS, activeAccountId: 'a1', switchingAccount: false, onSwitchAccount, refreshAccounts: vi.fn() })} />)
136
+ fireEvent.click(screen.getByRole('button', { name: /switch sub-account/i }))
137
+ fireEvent.click(screen.getByRole('button', { name: 'Client' }))
138
+ expect(onSwitchAccount).toHaveBeenCalledWith('a2')
139
+ expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
140
+ })
141
+
142
+ it('single-account install: brand head is not a switcher button and no picker mounts', () => {
143
+ routedFetch()
144
+ render(<Sidebar {...baseProps({ subAccounts: [ACCOUNTS[0]], activeAccountId: 'a1', onSwitchAccount: vi.fn() })} />)
145
+ expect(screen.queryByRole('button', { name: /switch sub-account/i })).toBeNull()
146
+ expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
147
+ })
148
+ })
149
+ ```
150
+
151
+ - [ ] **Step 2: Run — expect fail**
152
+
153
+ Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
154
+ Expected: FAIL (no button named "Switch sub-account").
155
+
156
+ - [ ] **Step 3: Implement the trigger in Sidebar.tsx**
157
+
158
+ Add imports: `SubAccountPicker` from `'./components/SubAccountPicker'`, and `ChevronDown` to the existing `lucide-react` import.
159
+
160
+ Add state beside `footerMenuOpen`:
161
+
162
+ ```tsx
163
+ const [switcherOpen, setSwitcherOpen] = useState(false)
164
+ const switcherAvailable = (subAccounts?.length ?? 0) > 1 && Boolean(onSwitchAccount)
165
+ ```
166
+
167
+ Replace the `<div className="side-brand">…</div>` block (icon + lockup) with:
168
+
169
+ ```tsx
170
+ <div className="side-brand">
171
+ {switcherAvailable ? (
172
+ <button
173
+ type="button"
174
+ className="side-brand-trigger"
175
+ aria-haspopup="menu"
176
+ aria-expanded={switcherOpen}
177
+ aria-label="Switch sub-account"
178
+ onClick={() => {
179
+ const opening = !switcherOpen
180
+ setSwitcherOpen(opening)
181
+ if (opening) refreshAccounts?.()
182
+ console.info(`[admin-ui] op=subaccount-switcher-toggle anchor=brand-head open=${opening} available=true accounts=${subAccounts?.length ?? 0}`)
183
+ }}
184
+ >
185
+ <img className="side-brand-icon" src={brandIcon} alt="" aria-hidden="true" />
186
+ <span className="side-brand-lockup">
187
+ <span className="side-brand-business">{displayName}</span>
188
+ <span className="side-brand-product">{brand.productName}</span>
189
+ </span>
190
+ <ChevronDown size={14} className="side-brand-caret" aria-hidden="true" />
191
+ </button>
192
+ ) : (
193
+ <>
194
+ <img className="side-brand-icon" src={brandIcon} alt="" aria-hidden="true" />
195
+ <span className="side-brand-lockup">
196
+ <span className="side-brand-business">{displayName}</span>
197
+ <span className="side-brand-product">{brand.productName}</span>
198
+ </span>
199
+ </>
200
+ )}
201
+ {switcherOpen && (
202
+ <>
203
+ <div className="side-brand-scrim" aria-hidden="true" onClick={() => setSwitcherOpen(false)} />
204
+ <SubAccountPicker
205
+ open={switcherOpen}
206
+ accounts={subAccounts ?? []}
207
+ activeAccountId={activeAccountId ?? null}
208
+ switching={switchingAccount ?? false}
209
+ onSelect={(accountId) => onSwitchAccount?.(accountId)}
210
+ onClose={() => setSwitcherOpen(false)}
211
+ />
212
+ </>
213
+ )}
214
+ </div>
215
+ ```
216
+
217
+ In the footer `<AccountMenu … />`, delete the five props `subAccounts`, `activeAccountId`, `switchingAccount`, `onSwitchAccount`, `refreshAccounts` (the Sidebar keeps consuming them for the head). Leave `onOpenConversations` and the rest.
218
+
219
+ - [ ] **Step 4: Run — expect pass**
220
+
221
+ Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
222
+ Expected: PASS.
223
+
224
+ - [ ] **Step 5: Fix the footer test that asserted the switcher was in the kebab**
225
+
226
+ In `app/__tests__/Sidebar-user-footer.test.tsx`, the test `threads the sub-account switcher and Conversations into the kebab menu` (lines 53-75) now asserts a false thing. Rewrite it to cover Conversations only:
227
+
228
+ ```tsx
229
+ it('threads Conversations into the kebab menu (switcher moved to brand head, Task 1942)', async () => {
230
+ routedFetch({ cpuPct: null, memUsedPct: 0.5, platform: 'darwin' })
231
+ const onOpenConversations = vi.fn()
232
+ render(<Sidebar {...baseProps({ onOpenConversations })} />)
233
+ fireEvent.click(screen.getByRole('button', { name: 'Account menu' }))
234
+ expect(screen.queryByRole('menuitem', { name: /switch account/i })).toBeNull()
235
+ fireEvent.click(await screen.findByRole('menuitem', { name: /conversations/i }))
236
+ expect(onOpenConversations).toHaveBeenCalledTimes(1)
237
+ })
238
+ ```
239
+
240
+ - [ ] **Step 6: Run both Sidebar suites — expect pass**
241
+
242
+ Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx app/__tests__/Sidebar-user-footer.test.tsx`
243
+ Expected: PASS.
244
+
245
+ - [ ] **Step 7: Commit**
246
+
247
+ ```bash
248
+ git add maxy-code/platform/ui/app/Sidebar.tsx maxy-code/platform/ui/app/__tests__/Sidebar-subaccount-switcher.test.tsx maxy-code/platform/ui/app/__tests__/Sidebar-user-footer.test.tsx
249
+ git commit -m "feat(1942): brand head opens the sub-account picker"
250
+ ```
251
+
252
+ ---
253
+
254
+ ### Task 3: CSS — anchor the panel under the head, style the trigger, drop dead overrides
255
+
256
+ **Files:**
257
+ - Modify: `app/globals.css`
258
+
259
+ **Interfaces:** consumes class names `side-brand`, `side-brand-trigger`, `side-brand-caret`, `side-brand-scrim`, `sub-account-picker-panel` from Task 2.
260
+
261
+ - [ ] **Step 1: Add `position: relative` to `.side-brand`**
262
+
263
+ In the `.side-brand { … }` rule, add `position: relative;`.
264
+
265
+ - [ ] **Step 2: Add the trigger, caret, and scrim rules** (place beside `.side-brand`)
266
+
267
+ ```css
268
+ .side-brand-trigger {
269
+ display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;
270
+ background: none; border: 0; padding: 0; margin: 0;
271
+ font: inherit; color: inherit; text-align: left; cursor: pointer;
272
+ }
273
+ .side-brand-caret { flex: none; color: var(--text-tertiary); }
274
+ .side-brand-trigger:hover .side-brand-caret { color: var(--text-secondary); }
275
+ .platform.sidebar-collapsed .side-brand-caret { display: none; }
276
+ .side-brand-scrim { position: fixed; inset: 0; z-index: 40; }
277
+ .platform.sidebar-collapsed .side-brand-scrim { display: none; }
278
+ ```
279
+
280
+ - [ ] **Step 3: Re-anchor `.sub-account-picker-panel` under the head**
281
+
282
+ The brand head is now the only consumer. In `.sub-account-picker-panel`, remove `left: 50%;` and `transform: translateX(-50%);`, remove the `min-width` (if present), and set `left: 0; right: 0; margin-top: 8px; z-index: 41;` so the panel spans the head's inner width and sits above the scrim.
283
+
284
+ - [ ] **Step 4: Delete the dead footer overrides**
285
+
286
+ Delete the `.side-foot-menu .sub-account-picker-panel { … }` and `.side-foot-menu .sub-account-picker-list { … }` rules and the comment above them that describes the footer kebab anchoring.
287
+
288
+ - [ ] **Step 5: Run the class-name CSS gate + the switcher suites**
289
+
290
+ Run: `npm run -s check:classname-css 2>/dev/null || node scripts/check-classname-css.mjs`
291
+ Then: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
292
+ Expected: gate clean; tests PASS.
293
+
294
+ - [ ] **Step 6: Commit**
295
+
296
+ ```bash
297
+ git add maxy-code/platform/ui/app/globals.css
298
+ git commit -m "style(1942): anchor sub-account picker under the sidebar brand head"
299
+ ```
300
+
301
+ ---
302
+
303
+ ### Task 4: Docs
304
+
305
+ **Files:**
306
+ - Modify: `platform/plugins/docs/references/admin-ui.md`
307
+
308
+ - [ ] **Step 1: Update the switcher location**
309
+
310
+ Find the passage describing the sub-account switcher opening from the footer kebab / user-footer AccountMenu. Rewrite it to state the switcher opens by clicking the sidebar brand head (the account name), with the picker dropping beneath it; note the footer kebab no longer carries it. Keep it to the existing register; no task-id string in the shipped doc.
311
+
312
+ - [ ] **Step 2: Commit**
313
+
314
+ ```bash
315
+ git add maxy-code/platform/plugins/docs/references/admin-ui.md
316
+ git commit -m "docs(1942): sub-account switcher now opens from the sidebar brand head"
317
+ ```
318
+
319
+ ## Self-Review
320
+
321
+ - **Spec coverage:** brand-head trigger (T2), whole-head + chevron + scrim decisions (T2/T3), AccountMenu strip (T1), CSS re-anchor + dead-override delete (T3), observability toggle log (T2 Step 3), docs (T4), test updates incl. the footer-test fix (T2 Step 5). All spec items mapped.
322
+ - **Placeholder scan:** none; every code step shows code.
323
+ - **Type consistency:** `SubAccountPicker` prop names (`open`, `accounts`, `activeAccountId`, `switching`, `onSelect`, `onClose`) match its component signature; Sidebar prop names match the existing signature.
324
+
325
+ ## Known follow-ups
326
+
327
+ None at plan time. Any code-review finding out of scope is filed as a new `.tasks/` file per the sprint rules.
@@ -0,0 +1,35 @@
1
+ # Task 1942 — Sub-account switcher moves to the sidebar brand head
2
+
3
+ Design doc. Source task: `.tasks/1942-subaccount-switcher-moves-to-sidebar-brand-head.md`.
4
+
5
+ ## Goal
6
+
7
+ Open the sub-account picker by clicking the account name at the top of the sidebar, not from the footer kebab. Remove the switcher from the footer menu.
8
+
9
+ ## Resolved decisions (brainstorm)
10
+
11
+ 1. **Click target:** the whole brand head (`side-brand`: icon + two-line lockup) becomes one button when the switcher is available.
12
+ 2. **Affordance:** a small down-chevron renders after the business name to signal the dropdown.
13
+ 3. **Close:** re-clicking the head toggles it shut, and a transparent full-screen scrim behind the panel closes it on any outside click. Mirrors the footer-menu scrim it replaces.
14
+
15
+ ## Availability gate (unchanged from today)
16
+
17
+ Switcher is available only when `subAccounts.length > 1 && Boolean(onSwitchAccount)`. Unavailable → the head stays a plain non-interactive `<div>`, no chevron, no picker. This is the current `switcherAvailable` gate, moved from `AccountMenu` to `Sidebar`.
18
+
19
+ ## Components and flow
20
+
21
+ - **`Sidebar.tsx`** owns `switcherOpen` state. The brand head renders as `<button aria-haspopup="menu" aria-expanded={switcherOpen}>` when available, else the current `<div>`. Click toggles `switcherOpen`; the closed→open transition fires `refreshAccounts?.()` and emits `[admin-ui] op=subaccount-switcher-toggle anchor=brand-head open=<bool> available=<bool> accounts=<N>`. `SubAccountPicker` renders inside `.side-brand`, anchored beneath it, with a sibling scrim that closes on click.
22
+ - **`AccountMenu.tsx`** loses the `switcherAvailable`/`switcherOpen` block, the `SubAccountPicker` import, and the five switcher props. `UserRound` import dropped if otherwise unused.
23
+ - **`globals.css`** gives `.side-brand` `position: relative` and re-anchors `.sub-account-picker-panel` left-aligned under the head (drop the retired header-title `translateX(-50%)` centering). Adds `.side-brand` button-reset styling (transparent, inherit, pointer) and the chevron. Deletes the dead `.side-foot-menu .sub-account-picker-panel` / `-list` overrides.
24
+
25
+ ## Collapsed rail (edge, in scope to not break)
26
+
27
+ When `sidebar-collapsed`, existing CSS hides `.side-brand-lockup` (name + chevron), leaving only the icon. The head-button still functions on the icon, but no new collapsed affordance is added (per task out-of-scope). The picker anchors under `.side-brand` in both states.
28
+
29
+ ## Testing
30
+
31
+ vitest + jsdom (Node 22). New `Sidebar` test: available → head is a button, click mounts the picker and fires `refreshAccounts`, re-click closes; unavailable → not a button, no picker. `AccountMenu.test.tsx`: no "Switch account", switcher props dropped. `SubAccountPicker.test.tsx` unchanged.
32
+
33
+ ## Out of scope
34
+
35
+ `SubAccountPicker` internals, the switch mechanism, other `AccountMenu` items, a dedicated collapsed-rail affordance, maxy-lite, Task 1940's mobile lite tab bar.
@@ -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:019a4f941c1466cfb0a9490c7c92f2eccbba4e67e57df48ddcb28f7b1ef5d835
4
+ content-hash: sha256:5db8891dfc1b60b2180597591dcad1b7ca9e89612f488314dd07e7dd73e5c119
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -2123,6 +2123,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
2123
2123
  | `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
2124
2124
  | `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. Needs `Mail.ReadWrite` + `Mail.Send`. |
2125
2125
  | `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
2126
+ | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. The plugin's only Neo4j coupling. Needs `Mail.Read`. |
2126
2127
  | `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
2127
2128
  | `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
2128
2129
  | `outlook-calendar-event` | Full detail of a single event by id. |
@@ -2159,6 +2160,9 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
2159
2160
  | Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
2160
2161
  | Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
2161
2162
  | Mail search | `mail-search account=<id> query=<trunc-32> count=<N> elapsedMs=<N>` |
2163
+ | Mail ingest (per message) | `mail-ingest op=ingest id=<id> conversationId=<id> disposition=ingest` |
2164
+ | Mail ingest (per thread) | `mail-ingest op=thread-dispatched root=<key> messages=<N> participants=<N>` (or `op=thread-failed root=<key> status=<N> err=<msg>`; `status=null` with a non-`none` `err` is a spawn launch failure, not a script rejection) |
2165
+ | Mail ingest (summary) | `mail-ingest op=summary ingested=<N> discarded=<N> threadsDispatched=<N> threadsSkipped=<N> unresolved=<N>` — one per call; `threadsSkipped` with `unresolved>0` is the closed-set resolution refusing an unknown participant, not a failure |
2162
2166
  | Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
2163
2167
  | Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
2164
2168
  | Event created | `cal-create-request account=<id> subj=<trunc-64>` then `event-created account=<id> eventId=<trunc-16> status=201`. A non-201 logs `event-create-failed ... status=<N>` and throws. A 201 whose body carried no readable id logs `event-created-id-unknown account=<id> status=201` and returns a created-but-id-unknown result (the event exists; do not retry) rather than a failure. |
@@ -2218,7 +2222,7 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
2218
2222
 
2219
2223
  ## Out of scope
2220
2224
 
2221
- Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), ingesting reviewed mail into the business graph (the `email-ingest` analogue), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
2225
+ Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` lands text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
2222
2226
 
2223
2227
  ---
2224
2228
  # Google Calendar
@@ -3312,10 +3316,20 @@ authoritative. This section names the surfaces and what backs each.
3312
3316
  > the sidebar collapse toggle, the page title, and a Claude status pill on the
3313
3317
  > right (Connected / Connect, driven by `/api/health`). The pill replaces the
3314
3318
  > retired burger Disconnect visibility and footer strip.
3315
- > - **Account kebab.** The version / Connect-Disconnect / Change PIN / Switch
3316
- > account / Log out items moved from the burger onto a kebab on the sidebar user
3317
- > footer (`AccountMenu`, extracted from the old `HeaderMenu`; the operator
3318
- > variant still mounts `AccountMenu` for its reduced set).
3319
+ > - **Account kebab.** The version / Connect-Disconnect / Change PIN / Log out
3320
+ > items moved from the burger onto a kebab on the sidebar user footer
3321
+ > (`AccountMenu`, extracted from the old `HeaderMenu`; the operator variant
3322
+ > still mounts `AccountMenu` for its reduced set).
3323
+ > - **Sub-account switcher on the brand head.** The head at the top of the sidebar
3324
+ > splits into two controls when the install has more than one sub-account: the
3325
+ > brand icon jumps straight to the house account, and the name plus chevron
3326
+ > opens the `SubAccountPicker` directly beneath the head (an outside click or a
3327
+ > second click on the name closes it). Clicking the icon while already on the
3328
+ > house account does nothing. A single-account install shows the same head as a
3329
+ > plain, non-interactive lockup, and the collapsed rail hides both controls. The
3330
+ > switcher used to sit in the account kebab; it no longer does. The switch
3331
+ > navigation itself (`useSubAccountSwitcher`, dropping the `?session=` param) is
3332
+ > unchanged.
3319
3333
  > - **Mobile (< 720px).** The sidebar opens as a drawer from a 44x44 header menu
3320
3334
  > button, and a fixed bottom tab bar (Home, Chat, Tasks, Data, Calendar) sits
3321
3335
  > under the content; Graph, Activity, and Browser stay reachable through the
@@ -411,10 +411,20 @@ authoritative. This section names the surfaces and what backs each.
411
411
  > the sidebar collapse toggle, the page title, and a Claude status pill on the
412
412
  > right (Connected / Connect, driven by `/api/health`). The pill replaces the
413
413
  > retired burger Disconnect visibility and footer strip.
414
- > - **Account kebab.** The version / Connect-Disconnect / Change PIN / Switch
415
- > account / Log out items moved from the burger onto a kebab on the sidebar user
416
- > footer (`AccountMenu`, extracted from the old `HeaderMenu`; the operator
417
- > variant still mounts `AccountMenu` for its reduced set).
414
+ > - **Account kebab.** The version / Connect-Disconnect / Change PIN / Log out
415
+ > items moved from the burger onto a kebab on the sidebar user footer
416
+ > (`AccountMenu`, extracted from the old `HeaderMenu`; the operator variant
417
+ > still mounts `AccountMenu` for its reduced set).
418
+ > - **Sub-account switcher on the brand head.** The head at the top of the sidebar
419
+ > splits into two controls when the install has more than one sub-account: the
420
+ > brand icon jumps straight to the house account, and the name plus chevron
421
+ > opens the `SubAccountPicker` directly beneath the head (an outside click or a
422
+ > second click on the name closes it). Clicking the icon while already on the
423
+ > house account does nothing. A single-account install shows the same head as a
424
+ > plain, non-interactive lockup, and the collapsed rail hides both controls. The
425
+ > switcher used to sit in the account kebab; it no longer does. The switch
426
+ > navigation itself (`useSubAccountSwitcher`, dropping the `?session=` param) is
427
+ > unchanged.
418
428
  > - **Mobile (< 720px).** The sidebar opens as a drawer from a 44x44 header menu
419
429
  > button, and a fixed bottom tab bar (Home, Chat, Tasks, Data, Calendar) sits
420
430
  > under the content; Graph, Activity, and Browser stay reachable through the
@@ -22,6 +22,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
22
22
  | `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
23
23
  | `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. Needs `Mail.ReadWrite` + `Mail.Send`. |
24
24
  | `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
25
+ | `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. The plugin's only Neo4j coupling. Needs `Mail.Read`. |
25
26
  | `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
26
27
  | `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
27
28
  | `outlook-calendar-event` | Full detail of a single event by id. |
@@ -58,6 +59,9 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
58
59
  | Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
59
60
  | Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
60
61
  | Mail search | `mail-search account=<id> query=<trunc-32> count=<N> elapsedMs=<N>` |
62
+ | Mail ingest (per message) | `mail-ingest op=ingest id=<id> conversationId=<id> disposition=ingest` |
63
+ | Mail ingest (per thread) | `mail-ingest op=thread-dispatched root=<key> messages=<N> participants=<N>` (or `op=thread-failed root=<key> status=<N> err=<msg>`; `status=null` with a non-`none` `err` is a spawn launch failure, not a script rejection) |
64
+ | Mail ingest (summary) | `mail-ingest op=summary ingested=<N> discarded=<N> threadsDispatched=<N> threadsSkipped=<N> unresolved=<N>` — one per call; `threadsSkipped` with `unresolved>0` is the closed-set resolution refusing an unknown participant, not a failure |
61
65
  | Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
62
66
  | Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
63
67
  | Event created | `cal-create-request account=<id> subj=<trunc-64>` then `event-created account=<id> eventId=<trunc-16> status=201`. A non-201 logs `event-create-failed ... status=<N>` and throws. A 201 whose body carried no readable id logs `event-created-id-unknown account=<id> status=201` and returns a created-but-id-unknown result (the event exists; do not retry) rather than a failure. |
@@ -117,4 +121,4 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
117
121
 
118
122
  ## Out of scope
119
123
 
120
- Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), ingesting reviewed mail into the business graph (the `email-ingest` analogue), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
124
+ Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` lands text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "outlook",
3
- "description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-attachment: list any message's attachments (received or draft), or download one file attachment's bytes to the account uploads folder; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send, all with attachments — outlook-draft takes an optional replyToMessageId to draft a threaded reply, unsent, under the original conversation; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.",
3
+ "description": "Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-attachment: list any message's attachments (received or draft), or download one file attachment's bytes to the account uploads folder; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-ingest: write operator-reviewed messages into the business graph as :ConversationArchive threads (per-message ingest/discard, conversationId threading, closed-set participant resolution); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send, all with attachments — outlook-draft takes an optional replyToMessageId to draft a threaded reply, unsent, under the original conversation; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.",
4
4
  "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "Rubytech LLC"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: outlook
3
- description: Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-attachment: list any message's attachments (received or draft), or download one file attachment's bytes to the account uploads folder; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send, all with attachments — outlook-draft takes an optional replyToMessageId to draft a threaded reply, unsent, under the original conversation; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.
3
+ description: Microsoft 365 / Outlook.com via Microsoft Graph. Mail is read (list, search, full-body), reply, delete, and compose (draft, draft-edit, send); contacts are read-only; calendar is read + control. Per-account OAuth device-code flow; no client secret. One account can hold several mailboxes at once; every operational tool takes an optional mailbox selector (email or graphUserId) and refuses ambiguously when two or more are attached and none is named. Tools — outlook-account-register / outlook-account-register-poll: device-code register (start + poll); outlook-mail-list / outlook-mail-search: inbox triage (~255-char preview) with a date window (list) or KQL from:/subject: filters (search) and a nextCursor for paging beyond the newest page; outlook-mail-fetch-body: full body of one message by id; outlook-mail-attachment: list any message's attachments (received or draft), or download one file attachment's bytes to the account uploads folder; outlook-mail-reply: in-thread reply (replyAll, cc/bcc, attachments); outlook-mail-delete: move to Deleted Items (recoverable); outlook-mail-ingest: write operator-reviewed messages into the business graph as :ConversationArchive threads (per-message ingest/discard, conversationId threading, closed-set participant resolution); outlook-mail-otp-extract: poll for a one-time code; outlook-mail-send / outlook-draft / outlook-draft-edit / outlook-draft-send: compose, edit, and send, all with attachments — outlook-draft takes an optional replyToMessageId to draft a threaded reply, unsent, under the original conversation; outlook-calendar-list / outlook-calendar-event: read calendar; outlook-calendar-create / outlook-calendar-update / outlook-calendar-cancel / outlook-calendar-respond / outlook-calendar-freebusy: control calendar; outlook-contacts-list: contacts; outlook-mailbox-info: auth state + folder count for one mailbox; outlook-mailbox-list: every connected mailbox.
4
4
  tools:
5
5
  - name: outlook-account-register
6
6
  publicAllowlist: false
@@ -26,6 +26,9 @@ tools:
26
26
  - name: outlook-mail-delete
27
27
  publicAllowlist: false
28
28
  adminAllowlist: false
29
+ - name: outlook-mail-ingest
30
+ publicAllowlist: false
31
+ adminAllowlist: false
29
32
  - name: outlook-mail-otp-extract
30
33
  publicAllowlist: false
31
34
  adminAllowlist: false
@@ -99,6 +102,7 @@ Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes. Mail is read (
99
102
  - **Mail (read):** `outlook-mail-list` returns messages newest-first with an optional `since`/`before` date window, and `outlook-mail-search` runs a Graph `$search` (KQL) query where `from:`/`to:`/`subject:`/`received>=` filters live — both return Microsoft's short `bodyPreview` (~255 chars) for cheap triage and a `nextCursor` for paging beyond the 250-per-page window (the cursor is an opaque `@odata.nextLink`; pass it back as `cursor`). Graph forbids `$search`+`$filter`, so the strict date window is on the list tool and sender/subject filtering is on the search tool. `outlook-mail-fetch-body` reads the COMPLETE body of one message by id (HTML decoded to text, no preview cap) — the full-read path. All three read tools report `hasAttachments` per message, so the agent knows to reach for the attachment tool.
100
103
  - **Mail (attachments):** `outlook-mail-attachment` lists or downloads any message's attachments — received or draft. Called with `messageId` alone it LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of `file`/`item`/`reference`) via `GET /me/messages/{id}/attachments` with `$select` (no bytes); a draft is a message in the same collection, so passing a draftId lists what that draft carries, and the returned `name`/`id` is the handle `outlook-draft-edit`'s `removeAttachments` takes. Called with `messageId`+`attachmentId` it DOWNLOADS one file attachment: the bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved absolute path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — `item` (embedded message/event) and `reference` (cloud-drive link) kinds are refused, as are empty payloads and anything over the 25 MB cap; in each case nothing is written. Requires `Mail.Read`.
101
104
  - **Mail (reply / delete):** `outlook-mail-reply` replies in-thread (native `conversationId` threading) with `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file — files over Graph's ~3 MB inline limit stream through a chunked upload session); the body is prepended above the quoted original. `outlook-mail-delete` moves messages to Deleted Items (recoverable — never hard-deletes). Both require `Mail.ReadWrite` (reply also needs `Mail.Send`).
105
+ - **Mail (ingest to graph):** `outlook-mail-ingest` writes operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads, the analogue of the IMAP `email-ingest`. Input is `decisions`: one `{messageId, disposition:'ingest'|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched via `runMailFetchBody`, threads are grouped by Graph `conversationId`, and participant resolution is closed-set — every From/To/Cc address must already resolve to a `:Person.email` or `:AdminUser.email` for the `accountId`, or its thread is skipped and the address is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch one at a time through the shared `plugins/memory/bin/conversation-archive-ingest.sh --source email` (same RFC822 thread-JSON shape the `email` normaliser consumes). This is the plugin's only Neo4j coupling (`neo4j-driver`). Human-in-the-loop: nothing enters the graph without an explicit per-message decision; the `outlook-mail-ingest` skill drives the list → summarise → ask → ingest loop. Requires `Mail.Read`.
102
106
  - **Mail (OTP):** `outlook-mail-otp-extract` polls the inbox for a one-time verification code from a named sender, reading full bodies to extract it — used during service authentication.
103
107
  - **Mail (send / draft):** `outlook-mail-send` sends a new message — via `POST /me/sendMail` with no attachments, or composed as a draft, attached, and sent when `attachments` is present (`/me/sendMail` carries attachments inline and cannot fit a large file); `outlook-draft` creates a draft, attaches any files, and returns its id — either a new message (`to` + `subject` required) or, with `replyToMessageId`, a threaded reply created via `POST /me/messages/{id}/createReply` that lands under the original conversation (the only way to draft a reply without sending it; `outlook-mail-reply` threads but always sends). On the threaded path Graph sets the recipients and `RE:` subject from the original and composes the body above its own quote — the same email `outlook-mail-reply` sends — so that path is plain text only and `to`, `subject`, and `isHtml: true` are refused rather than silently dropped; `replyAll` and `cc`/`bcc` work as on reply. `outlook-draft-edit` updates an existing draft in place (Graph drafts are mutable) and edits its attachments — `attachments` adds files, `removeAttachments` takes them off by name or attachment id, and both together replace a file in one call; `outlook-draft-send` sends an existing draft by id. Recipients are to/cc/bcc arrays; body is HTML or plain text via `isHtml`; `attachments` are file paths inside the account directory, capped at 25 MB per file and validated before any Graph write. Success asserted on Graph 202. Requires `Mail.Send` + `Mail.ReadWrite`; a mailbox on the old read-only consent returns an actionable "re-register to grant send" signal.
104
108
  - **Calendar (read + control):** `outlook-calendar-list` and `outlook-calendar-event` read events; `outlook-calendar-create` books an event; `outlook-calendar-update` reschedules or edits one; `outlook-calendar-cancel` cancels a meeting (notifying attendees when the account organises it) or deletes a personal appointment; `outlook-calendar-respond` accepts, declines, or tentatively accepts an invite; `outlook-calendar-freebusy` reports availability for one or more addresses over a window. Writes require the `Calendars.ReadWrite` consent; a mailbox on stale read-only consent returns a distinct signal naming the scope and `outlook-account-register`.
@@ -108,7 +112,7 @@ Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes. Mail is read (
108
112
  ## Out of scope
109
113
 
110
114
  - Mail move (to arbitrary folders) / flag — write surfaces beyond reply, delete (move to Deleted Items), and compose are not implemented. Hard/permanent delete is deliberately excluded; `outlook-mail-delete` only moves to Deleted Items.
111
- - Ingesting reviewed mail into the business graph — the `outlook-mail-ingest` analogue of `email-ingest` is a follow-up task (adds a Neo4j dependency).
115
+ - Attachment-byte archival into the graph — `outlook-mail-ingest` lands text threads only; the IMAP path also archives attachment bytes as `:DigitalDocument`, and the Outlook equivalent is a separate concern (see out-of-scope).
112
116
  - Contacts write — read-only.
113
117
  - Recurring-series occurrence editing (edit-this-occurrence) beyond whole-series create/update.
114
118
  - The `scheduling` (Neo4j graph) plugin and any auto-sync between it and the Outlook calendar.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dispatch-grouping.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-grouping.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dispatch-grouping.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { groupByThread, buildThreadJson } from "../lib/conversation-archive-dispatch.js";
4
+ const base = {
5
+ fromAddress: "a@x.com",
6
+ fromName: "Ann",
7
+ toAddresses: ["b@x.com"],
8
+ ccAddresses: [],
9
+ subject: "s",
10
+ bodyText: "hi",
11
+ accountId: "acct1",
12
+ };
13
+ test("two messages sharing conversationId group into one thread", () => {
14
+ const msgs = [
15
+ { ...base, messageId: "m1", conversationId: "c1", receivedDateTime: "2026-01-01T10:00:00Z" },
16
+ { ...base, messageId: "m2", conversationId: "c1", receivedDateTime: "2026-01-01T11:00:00Z" },
17
+ ];
18
+ const threads = groupByThread(msgs);
19
+ assert.equal(threads.length, 1);
20
+ assert.equal(threads[0].messages.length, 2);
21
+ });
22
+ test("null conversationId becomes its own thread keyed on messageId", () => {
23
+ const msgs = [
24
+ { ...base, messageId: "m1", conversationId: null, receivedDateTime: "2026-01-01T10:00:00Z" },
25
+ { ...base, messageId: "m2", conversationId: null, receivedDateTime: "2026-01-01T11:00:00Z" },
26
+ ];
27
+ assert.equal(groupByThread(msgs).length, 2);
28
+ });
29
+ test("thread addresses are lowercased union of from/to/cc, sorted", () => {
30
+ const msgs = [
31
+ {
32
+ ...base,
33
+ messageId: "m1",
34
+ conversationId: "c1",
35
+ fromAddress: "A@X.com",
36
+ toAddresses: ["B@X.com"],
37
+ ccAddresses: ["c@x.com"],
38
+ receivedDateTime: "2026-01-01T10:00:00Z",
39
+ },
40
+ ];
41
+ assert.deepEqual(groupByThread(msgs)[0].addresses, ["a@x.com", "b@x.com", "c@x.com"]);
42
+ });
43
+ test("buildThreadJson emits {from:{name,email},date,bodyText} in receivedDateTime order", () => {
44
+ const thread = groupByThread([
45
+ { ...base, messageId: "m2", conversationId: "c1", bodyText: "second", receivedDateTime: "2026-01-01T11:00:00Z" },
46
+ { ...base, messageId: "m1", conversationId: "c1", bodyText: "first", receivedDateTime: "2026-01-01T10:00:00Z" },
47
+ ])[0];
48
+ const json = buildThreadJson(thread);
49
+ assert.deepEqual(json, [
50
+ { from: { name: "Ann", email: "a@x.com" }, date: "2026-01-01T10:00:00Z", bodyText: "first" },
51
+ { from: { name: "Ann", email: "a@x.com" }, date: "2026-01-01T11:00:00Z", bodyText: "second" },
52
+ ]);
53
+ });
54
+ //# sourceMappingURL=dispatch-grouping.test.js.map