@rubytech/create-maxy-code 0.1.502 → 0.1.504

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 (202) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  3. package/payload/platform/plugins/admin/hooks/__tests__/quote-engine-gate.test.sh +98 -0
  4. package/payload/platform/plugins/admin/hooks/quote-engine-gate.sh +68 -0
  5. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +33 -1
  6. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
  7. package/payload/platform/plugins/docs/references/admin-ui.md +32 -0
  8. package/payload/platform/plugins/work/PLUGIN.md +3 -3
  9. package/payload/platform/plugins/work/mcp/dist/__tests__/assignable-admins.test.d.ts +2 -0
  10. package/payload/platform/plugins/work/mcp/dist/__tests__/assignable-admins.test.d.ts.map +1 -0
  11. package/payload/platform/plugins/work/mcp/dist/__tests__/assignable-admins.test.js +55 -0
  12. package/payload/platform/plugins/work/mcp/dist/__tests__/assignable-admins.test.js.map +1 -0
  13. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.js +33 -1
  14. package/payload/platform/plugins/work/mcp/dist/__tests__/work-create-assigned-admin-env.test.js.map +1 -1
  15. package/payload/platform/plugins/work/mcp/dist/__tests__/work-update-assign.test.d.ts +2 -0
  16. package/payload/platform/plugins/work/mcp/dist/__tests__/work-update-assign.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/work/mcp/dist/__tests__/work-update-assign.test.js +73 -0
  18. package/payload/platform/plugins/work/mcp/dist/__tests__/work-update-assign.test.js.map +1 -0
  19. package/payload/platform/plugins/work/mcp/dist/index.js +4 -1
  20. package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/work/mcp/dist/lib/assignable-admins.d.ts +13 -0
  22. package/payload/platform/plugins/work/mcp/dist/lib/assignable-admins.d.ts.map +1 -0
  23. package/payload/platform/plugins/work/mcp/dist/lib/assignable-admins.js +66 -0
  24. package/payload/platform/plugins/work/mcp/dist/lib/assignable-admins.js.map +1 -0
  25. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts +8 -0
  26. package/payload/platform/plugins/work/mcp/dist/tools/work-create.d.ts.map +1 -1
  27. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js +31 -13
  28. package/payload/platform/plugins/work/mcp/dist/tools/work-create.js.map +1 -1
  29. package/payload/platform/plugins/work/mcp/dist/tools/work-list.d.ts +4 -0
  30. package/payload/platform/plugins/work/mcp/dist/tools/work-list.d.ts.map +1 -1
  31. package/payload/platform/plugins/work/mcp/dist/tools/work-list.js +3 -2
  32. package/payload/platform/plugins/work/mcp/dist/tools/work-list.js.map +1 -1
  33. package/payload/platform/plugins/work/mcp/dist/tools/work-update.d.ts +7 -0
  34. package/payload/platform/plugins/work/mcp/dist/tools/work-update.d.ts.map +1 -1
  35. package/payload/platform/plugins/work/mcp/dist/tools/work-update.js +31 -0
  36. package/payload/platform/plugins/work/mcp/dist/tools/work-update.js.map +1 -1
  37. package/payload/platform/scripts/lib/provision-account-dir.sh +2 -1
  38. package/payload/platform/services/telegram-channel/dist/instructions.js +2 -2
  39. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  40. package/payload/platform/services/webchat-channel/dist/instructions.js +2 -2
  41. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  42. package/payload/platform/services/whatsapp-channel/dist/notification.js +2 -2
  43. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  44. package/payload/server/public/activity.html +6 -6
  45. package/payload/server/public/agents.html +18 -0
  46. package/payload/server/public/assets/{AdminLoginScreens-Bv_bKKeL.js → AdminLoginScreens-DWMR4KtI.js} +1 -1
  47. package/payload/server/public/assets/AdminShell-C8hBpL9K.js +2 -0
  48. package/payload/server/public/assets/{activity-DZFYDHNA.js → activity-DH3z4cv3.js} +1 -1
  49. package/payload/server/public/assets/admin-aaGhd2Yh.js +1 -0
  50. package/payload/server/public/assets/agents-SPu-clsf.js +1 -0
  51. package/payload/server/public/assets/{arc-CvlzUHOf.js → arc-DaI5MLTq.js} +1 -1
  52. package/payload/server/public/assets/architecture-YZFGNWBL-DWZIX5aV.js +1 -0
  53. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-I1Hs0RJA.js → architectureDiagram-Q4EWVU46-CBGVUEN9.js} +1 -1
  54. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-Df5jTGtl.js → blockDiagram-DXYQGD6D-DS6dS4GZ.js} +1 -1
  55. package/payload/server/public/assets/{browser-Bt4-LKLU.js → browser-C_MJYN5k.js} +1 -1
  56. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B6WY5IDv.js → c4Diagram-AHTNJAMY-hBSBUZPt.js} +1 -1
  57. package/payload/server/public/assets/calendar-DA0h7dqW.js +1 -0
  58. package/payload/server/public/assets/channel-BEokjAeE.js +1 -0
  59. package/payload/server/public/assets/chat-Bg7dlM0I.js +1 -0
  60. package/payload/server/public/assets/chevron-left-BoDA12H8.js +1 -0
  61. package/payload/server/public/assets/chevron-right-DXjrO6qA.js +1 -0
  62. package/payload/server/public/assets/{chunk-2KRD3SAO-BsIIFDkc.js → chunk-2KRD3SAO-B6koUoB7.js} +1 -1
  63. package/payload/server/public/assets/{chunk-336JU56O-j9z1VtyC.js → chunk-336JU56O-BF6BRh8u.js} +2 -2
  64. package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js +1 -0
  65. package/payload/server/public/assets/{chunk-4BX2VUAB-C9i7prxq.js → chunk-4BX2VUAB-7sO95xR2.js} +1 -1
  66. package/payload/server/public/assets/{chunk-4TB4RGXK-DH0QVGcv.js → chunk-4TB4RGXK-GCTndJYx.js} +1 -1
  67. package/payload/server/public/assets/{chunk-55IACEB6-Dnd_YCPA.js → chunk-55IACEB6-ggIJf_TC.js} +1 -1
  68. package/payload/server/public/assets/{chunk-5FUZZQ4R-D5sSz3yN.js → chunk-5FUZZQ4R-DeGmkimt.js} +1 -1
  69. package/payload/server/public/assets/{chunk-5PVQY5BW-CJrwWjAT.js → chunk-5PVQY5BW-aFIQKk_U.js} +1 -1
  70. package/payload/server/public/assets/{chunk-67CJDMHE-SjP6_hUw.js → chunk-67CJDMHE-LS8rUbii.js} +1 -1
  71. package/payload/server/public/assets/{chunk-7N4EOEYR-fAuhVJD0.js → chunk-7N4EOEYR-JKOVngS0.js} +1 -1
  72. package/payload/server/public/assets/{chunk-AA7GKIK3-DZxa7KVg.js → chunk-AA7GKIK3-CoXnTVK6.js} +1 -1
  73. package/payload/server/public/assets/{chunk-BSJP7CBP-DjeG3eQ-.js → chunk-BSJP7CBP-CcsqObFg.js} +1 -1
  74. package/payload/server/public/assets/{chunk-CIAEETIT-RYQULNJm.js → chunk-CIAEETIT-CpsybR7q.js} +1 -1
  75. package/payload/server/public/assets/{chunk-EDXVE4YY-Cahapnon.js → chunk-EDXVE4YY-BZ2I9PSq.js} +1 -1
  76. package/payload/server/public/assets/{chunk-ENJZ2VHE-1146X1L4.js → chunk-ENJZ2VHE-DbTwt6OZ.js} +1 -1
  77. package/payload/server/public/assets/{chunk-FMBD7UC4-BCFumtcP.js → chunk-FMBD7UC4-D27VnSCa.js} +1 -1
  78. package/payload/server/public/assets/{chunk-FOC6F5B3-CkrtX8Zt.js → chunk-FOC6F5B3-C0tU2UFl.js} +1 -1
  79. package/payload/server/public/assets/{chunk-ICPOFSXX-Ba1y3DaW.js → chunk-ICPOFSXX-UPPu2OIq.js} +1 -1
  80. package/payload/server/public/assets/{chunk-K5T4RW27-CF-PI3uQ.js → chunk-K5T4RW27-DIKxqyYw.js} +1 -1
  81. package/payload/server/public/assets/{chunk-KGLVRYIC-SFFC6ZC-.js → chunk-KGLVRYIC-CiL4YVLo.js} +1 -1
  82. package/payload/server/public/assets/{chunk-LIHQZDEY-DbOLEP4R.js → chunk-LIHQZDEY-ejKezVlD.js} +1 -1
  83. package/payload/server/public/assets/{chunk-ORNJ4GCN-VB963bkN.js → chunk-ORNJ4GCN-Cc0vqc-D.js} +1 -1
  84. package/payload/server/public/assets/{chunk-OYMX7WX6-DWYY1pQa.js → chunk-OYMX7WX6-Drxn1qG0.js} +1 -1
  85. package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js +1 -0
  86. package/payload/server/public/assets/{chunk-U2HBQHQK-DAH1diYP.js → chunk-U2HBQHQK-DnbQIkRP.js} +1 -1
  87. package/payload/server/public/assets/{chunk-X2U36JSP-Dmw9coXz.js → chunk-X2U36JSP-CauTSVWg.js} +1 -1
  88. package/payload/server/public/assets/{chunk-XPW4576I-DaAUON1-.js → chunk-XPW4576I-DZqqB9Zi.js} +1 -1
  89. package/payload/server/public/assets/{chunk-YZCP3GAM-BUcfkEef.js → chunk-YZCP3GAM-BGHo4Li-.js} +1 -1
  90. package/payload/server/public/assets/{chunk-ZZ45TVLE-BLD-vFRO.js → chunk-ZZ45TVLE-DWGJKPd7.js} +1 -1
  91. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js +1 -0
  92. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js +1 -0
  93. package/payload/server/public/assets/clock-BceBgZP1.js +1 -0
  94. package/payload/server/public/assets/clone-Bn7XXVhr.js +1 -0
  95. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CoZX0uki.js → cose-bilkent-S5V4N54A-D-95BzXL.js} +1 -1
  96. package/payload/server/public/assets/{dagre-Bmjs8zJA.js → dagre-BZYNBHvT.js} +1 -1
  97. package/payload/server/public/assets/{dagre-KV5264BT-BZCP2wnq.js → dagre-KV5264BT-DyU4HQLQ.js} +1 -1
  98. package/payload/server/public/assets/data-rNLaurgY.js +1 -0
  99. package/payload/server/public/assets/{diagram-5BDNPKRD-8Nt9a4xX.js → diagram-5BDNPKRD-CCttJ9XA.js} +1 -1
  100. package/payload/server/public/assets/{diagram-G4DWMVQ6-vh5tcYqJ.js → diagram-G4DWMVQ6-DvkpgSLO.js} +1 -1
  101. package/payload/server/public/assets/{diagram-MMDJMWI5-DGf--jc8.js → diagram-MMDJMWI5-DHmW5Y9k.js} +1 -1
  102. package/payload/server/public/assets/{diagram-TYMM5635-CsO7TIdw.js → diagram-TYMM5635-C6T4hwzO.js} +1 -1
  103. package/payload/server/public/assets/{dist-cdnE4DqK.js → dist-QvbmK1IP.js} +1 -1
  104. package/payload/server/public/assets/{erDiagram-SMLLAGMA-4a78yV8_.js → erDiagram-SMLLAGMA-5v_o7s3T.js} +1 -1
  105. package/payload/server/public/assets/{file-text-BpLcZKld.js → file-text-RCoSeOpD.js} +1 -1
  106. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BoQBXs6t.js → flowDiagram-DWJPFMVM-3UhCDsjt.js} +1 -1
  107. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CvzqwCC8.js → ganttDiagram-T4ZO3ILL-F2zgHIIQ.js} +1 -1
  108. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C-FvNTHZ.js +1 -0
  109. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-D_nBxay_.js → gitGraphDiagram-UUTBAWPF-oQc1N3dT.js} +1 -1
  110. package/payload/server/public/assets/{graph-HB0199x-.js → graph-Dm-YZeNP.js} +12 -12
  111. package/payload/server/public/assets/{graph-labels-Bx19ZYvJ.js → graph-labels-DJV0aIG5.js} +1 -1
  112. package/payload/server/public/assets/{graphlib-D0NdsKJI.js → graphlib-DY4-AT2Q.js} +1 -1
  113. package/payload/server/public/assets/info-OMHHGYJF-BjLPgHAC.js +1 -0
  114. package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js +2 -0
  115. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-auW-s0i0.js → ishikawaDiagram-UXIWVN3A-wJhSOkuw.js} +1 -1
  116. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-3Q6AWK4A.js → journeyDiagram-VCZTEJTY-DgadC6Pz.js} +1 -1
  117. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-B4MPFp98.js → kanban-definition-6JOO6SKY-Bs4nQqXI.js} +1 -1
  118. package/payload/server/public/assets/{line-26MdeG5Z.js → line-Bnmxy1hO.js} +1 -1
  119. package/payload/server/public/assets/{linear-Dq9s_bDO.js → linear-RvwZxFzh.js} +1 -1
  120. package/payload/server/public/assets/{maximize-2-BhR-Ec21.js → maximize-2-DcESsKbu.js} +1 -1
  121. package/payload/server/public/assets/{mermaid-parser.core-vLEpOXgf.js → mermaid-parser.core-DeNV8RtA.js} +2 -2
  122. package/payload/server/public/assets/{mermaid.core-Durk4m6D.js → mermaid.core-BU9IiSCP.js} +3 -3
  123. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DOIg-TyG.js → mindmap-definition-QFDTVHPH-D1PMUxxm.js} +1 -1
  124. package/payload/server/public/assets/operator-Dlkw1sXJ.js +1 -0
  125. package/payload/server/public/assets/packet-4T2RLAQJ-BBAZASfb.js +1 -0
  126. package/payload/server/public/assets/page-Dl-mgQUu.js +32 -0
  127. package/payload/server/public/assets/page-DoyJrk8E.js +1 -0
  128. package/payload/server/public/assets/pie-ZZUOXDRM-CCJUnMeG.js +1 -0
  129. package/payload/server/public/assets/{pieDiagram-DEJITSTG-DtgBHZfi.js → pieDiagram-DEJITSTG-BzdNzoKA.js} +1 -1
  130. package/payload/server/public/assets/{public-Be_Galv6.js → public-C-UHWk_S.js} +1 -1
  131. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BTc5Qn2y.js → quadrantDiagram-34T5L4WZ-oFr1MJRO.js} +1 -1
  132. package/payload/server/public/assets/radar-PYXPWWZC-BsRU8Aep.js +1 -0
  133. package/payload/server/public/assets/{requirementDiagram-MS252O5E-BNgOO5YW.js → requirementDiagram-MS252O5E-C9QBQxDh.js} +1 -1
  134. package/payload/server/public/assets/{rotate-ccw-D9qOBLig.js → rotate-ccw-C1nRSAzP.js} +1 -1
  135. package/payload/server/public/assets/routines-qdAZ0sA4.js +1 -0
  136. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-MyUATmVt.js → sankeyDiagram-XADWPNL6-c_y3bvVv.js} +1 -1
  137. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CBJrF5Vi.js → sequenceDiagram-FGHM5R23-YwlCBPHE.js} +1 -1
  138. package/payload/server/public/assets/skills-B78wr822.js +1 -0
  139. package/payload/server/public/assets/{src-DekKlUCp.js → src-C2WwTWSY.js} +1 -1
  140. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-6wbUjn1Q.js → stateDiagram-FHFEXIEX-BFE3e-FM.js} +1 -1
  141. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js +1 -0
  142. package/payload/server/public/assets/tasks-DbIsS-vW.js +1 -0
  143. package/payload/server/public/assets/{time-entry-format-RkCW7hKu.js → time-entry-format-BhlltPP8.js} +1 -1
  144. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BHjTMCGY.js → timeline-definition-GMOUNBTQ-azqhmRwZ.js} +1 -1
  145. package/payload/server/public/assets/treeView-SZITEDCU-BDzhKA6v.js +1 -0
  146. package/payload/server/public/assets/treemap-W4RFUUIX-epHpdeWa.js +1 -0
  147. package/payload/server/public/assets/{triangle-alert-k8ulav-N.js → triangle-alert-rEVGyIhi.js} +1 -1
  148. package/payload/server/public/assets/{useCopyFeedback-DN1WA5vU.js → useCopyFeedback-DzpXjm9d.js} +1 -1
  149. package/payload/server/public/assets/useSubAccountSwitcher-DVmWZxar.js +9 -0
  150. package/payload/server/public/assets/{useSubAccountSwitcher-fvBunpOC.css → useSubAccountSwitcher-wP8cOzPj.css} +1 -1
  151. package/payload/server/public/assets/{useVoiceRecorder-v--TIxaM.js → useVoiceRecorder-CRRmPjYr.js} +1 -1
  152. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-yo3-cf0U.js → vennDiagram-DHZGUBPP-Da2RfFs8.js} +1 -1
  153. package/payload/server/public/assets/wardley-RL74JXVD-DNSZCrxV.js +1 -0
  154. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-DZlyFzrg.js → wardleyDiagram-NUSXRM2D-HdrdBYES.js} +1 -1
  155. package/payload/server/public/assets/{wrench-CKhz8KnQ.js → wrench-oeLti270.js} +1 -1
  156. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-B5HV5h80.js → xychartDiagram-5P7HB3ND-DMk6rTcQ.js} +1 -1
  157. package/payload/server/public/browser.html +5 -5
  158. package/payload/server/public/calendar.html +8 -8
  159. package/payload/server/public/chat.html +14 -15
  160. package/payload/server/public/data.html +12 -12
  161. package/payload/server/public/graph.html +10 -10
  162. package/payload/server/public/index.html +15 -16
  163. package/payload/server/public/operator.html +15 -16
  164. package/payload/server/public/public.html +14 -15
  165. package/payload/server/public/routines.html +7 -7
  166. package/payload/server/public/skills.html +6 -6
  167. package/payload/server/public/tasks.html +7 -7
  168. package/payload/server/server.js +696 -430
  169. package/payload/server/public/assets/AdminShell-Gq8DU_ig.js +0 -2
  170. package/payload/server/public/assets/admin-DLUOixl6.js +0 -1
  171. package/payload/server/public/assets/architecture-YZFGNWBL-BQc779DH.js +0 -1
  172. package/payload/server/public/assets/bot-CrEhKW1l.js +0 -1
  173. package/payload/server/public/assets/calendar-D0qIvLUR.js +0 -1
  174. package/payload/server/public/assets/channel-AqnC3SHu.js +0 -1
  175. package/payload/server/public/assets/chat-geSkx0j2.js +0 -1
  176. package/payload/server/public/assets/chevron-left-CQ8rcBoO.js +0 -1
  177. package/payload/server/public/assets/chevron-right-CSRSqblN.js +0 -1
  178. package/payload/server/public/assets/chunk-426QAEUC-CO02gU5d.js +0 -1
  179. package/payload/server/public/assets/chunk-QZHKN3VN-BiFMbK8t.js +0 -1
  180. package/payload/server/public/assets/classDiagram-6PBFFD2Q-BdRqzScB.js +0 -1
  181. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-ChPLgHbS.js +0 -1
  182. package/payload/server/public/assets/clock-B7Ba8AZL.js +0 -1
  183. package/payload/server/public/assets/clone-TKtrjWBC.js +0 -1
  184. package/payload/server/public/assets/data-BdlGqjKM.js +0 -1
  185. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DfcaTE9L.js +0 -1
  186. package/payload/server/public/assets/info-OMHHGYJF-CD-u6gw9.js +0 -1
  187. package/payload/server/public/assets/infoDiagram-42DDH7IO-C6gPQCvO.js +0 -2
  188. package/payload/server/public/assets/operator-DejfJlvi.js +0 -1
  189. package/payload/server/public/assets/packet-4T2RLAQJ-BaLYeIrU.js +0 -1
  190. package/payload/server/public/assets/page-DomkejEB.js +0 -32
  191. package/payload/server/public/assets/page-FnCmAeHc.js +0 -1
  192. package/payload/server/public/assets/pie-ZZUOXDRM-CL-XJtk-.js +0 -1
  193. package/payload/server/public/assets/radar-PYXPWWZC-Cn-g6F_h.js +0 -1
  194. package/payload/server/public/assets/routines-lGiYaMiX.js +0 -1
  195. package/payload/server/public/assets/skills-D9nWOqoz.js +0 -1
  196. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BdMdfBeC.js +0 -1
  197. package/payload/server/public/assets/tasks-Cyc1EfLA.js +0 -1
  198. package/payload/server/public/assets/treeView-SZITEDCU-CmZoOGlC.js +0 -1
  199. package/payload/server/public/assets/treemap-W4RFUUIX-H27WFS-y.js +0 -1
  200. package/payload/server/public/assets/useSubAccountSwitcher-iQGqNJhI.js +0 -9
  201. package/payload/server/public/assets/wardley-RL74JXVD-DNpWIvU4.js +0 -1
  202. /package/payload/server/public/assets/{chunk-CCr-iYLO.js → chunk-CltuBf4Z.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.502",
3
+ "version": "0.1.504",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -189,6 +189,7 @@ Tools are available via the `admin` MCP server.
189
189
  ## Hooks
190
190
 
191
191
  - `hooks/quote-render-gate.sh` — **PreToolUse matcher=`mcp__plugin_browser_browser__browser-pdf-save`.** Makes the deterministic quote-render path the only route a client-facing quote PDF can reach a customer. Blocks (exit 2) a `browser-pdf-save` whose output path is a client-quote artefact (`memory/users/<phone>/documents/quote-<jobId>.pdf` or `…-breakdown.pdf`) when no render receipt exists at `quoting/jobs/<jobId>/render-receipt.json` — the receipt `quote-render` writes only after its reconcile / margin-leak / link-liveness gates all pass. A hand-authored quote (the SiteDesk 2026-07-14 bypass: `Write` HTML → `browser-pdf-save`, `quote-render` never called, an internal back-test caveat naming another client folded into the free-text region) has no receipt, so the print is refused rather than delivered. **Block message:** `Blocked: this client quote was not produced by the quote-render tool, so its fixed layout and the reconcile, margin-leak and link-liveness gates never ran. Run quote-render for this job (it files the document and its receipt), then print the client HTML path it returns to PDF.` **Log line** (stderr, on a block): `[quote-render] op=bypass jobId=<jobId> detail=client quote PDF has no render receipt` (or `reason=bad-jobid` when the filename does not resolve to a job). Every non-quote PDF — brochures, invoices, the admin-scope internal view — and the deterministic path itself (receipt present) pass at exit 0. **Fail-open** on an uninspectable call (tty / empty stdin / no `python3`): `browser-pdf-save` is a general-purpose tool and the agent never controls this hook's stdin, so failing open opens no bypass while never bricking unrelated PDFs.
192
+ - `hooks/quote-engine-gate.sh` — **PreToolUse matcher=`Bash`** (a third command on the same `Bash` matcher as `archive-ingest-surface-gate` and `fs-schema-guard-bash-pre`, any exit 2 blocks). Makes the deterministic `quote-engine-run` / `quote-render` tools the only route the account's persisted quoting scripts can run. Blocks (exit 2) a `Bash` `command` that executes `quoting/engine.mjs` or `quoting/render.mjs` directly — the pricing and render scripts must run through their MCP tools, which apply the reconcile / margin-leak / link-liveness gates and write the figures and render receipts. A hand-run `node quoting/engine.mjs` (the SiteDesk 2026-07-25 bypass: a repricing dispatched to content-producer that ran the engine by hand and re-rendered off the deterministic path) skips all of that, so it is refused. It fires only on an execution (a JS runtime invoking the script, or the script run at command position); a read or inspect of the file (`cat`/`grep`/`git diff quoting/engine.mjs`) and every unrelated `Bash` command pass at exit 0. A `mode=verify` reproduction is gated too — it must also run through `quote-engine-run`, so a hand-run `node quoting/engine.mjs mode=verify` is refused. Known fail-open gap (accepted, consistent with the soft posture): a `cd quoting && node engine.mjs` drops the `quoting/` prefix and is not matched. **Block message:** `Blocked: the quoting engine and render scripts must run through their tools, not directly. Use the quote-engine-run tool to price a job (it files the figures) and the quote-render tool to produce the documents (it files them behind the reconcile, margin-leak and link-liveness gates). Running quoting/engine.mjs or quoting/render.mjs by hand skips those gates and files no receipt.` **Log line** (stderr, on a block): `[quote-engine] op=bypass reason=direct-script-exec`. **Fail-open** on an uninspectable call (tty / empty stdin / no `python3`): `Bash` is a general-purpose tool and the agent never controls this hook's stdin, so failing open opens no bypass while never bricking unrelated commands.
192
193
  - `hooks/preference-consult-gate.sh` — **PreToolUse gate on customer-facing document deliverables.** Registered against `mcp__plugin_browser_browser__browser-pdf-save` (a second command on the same matcher as `quote-render-gate`, either exit 2 blocks) and against the email/Outlook send tools plus `SendUserFile`. It fires only in the document-deliverable shape: a `browser-pdf-save` or `SendUserFile` whose path is under `memory/users/<phone>/documents/` (or a `SendUserFile` of a `.pdf`/`.html`/`.docx`), and `email-send`/`email-reply`/`email-draft-send`/`outlook-mail-send`/`outlook-mail-reply`/`outlook-draft-send` carrying a non-empty `attachments`. For a gated call it reads the session transcript (`transcript_path`) and blocks (exit 2) when no `profile-read` tool call ran after the last user message, because the account's layer-2 `Preference` records (signature policy, header, naming, styling) were not consulted this turn, the exact miss that dropped a customer signature on a delivered document. **Block message:** `Blocked: this customer document is about to go out without the account's saved preferences being checked this turn. Those records hold the signature, header, naming and styling rules. Run profile-read for this account, apply anything relevant, then send again.` **Log line** (stderr): `[preference-gate] op=bypass tool=<name> detail=no profile-read this turn` on a block, `op=allow tool=<name> consulted=true` on an allowed document deliverable. Every non-document tool/shape, and every casual channel reply or plain-text email, passes at exit 0. **Fail-open** on an uninspectable call (tty / empty stdin / no `python3` / transcript missing or unreadable). Scope decision: the gate fires on document deliverables only, not on every send, so it never blocks routine chat. The standing reconcile counterpart for stranded `output/` deliverables is filed as a follow-up task.
193
194
  - `hooks/quote-render-pdf-conformance.sh` — **PostToolUse matcher=`mcp__plugin_browser_browser__browser-pdf-save`.** The PDF-time companion to the PreToolUse `quote-render-gate`: the HTML gates run inside `quote-render` before the receipt, but the two PDF-only conformance checks cannot, because the PDF does not exist until `browser-pdf-save` runs. For a gated client-quote PDF (`memory/users/<phone>/documents/quote-<jobId>.pdf` or `…-breakdown.pdf`) whose render receipt names the source HTML, it blocks (exit 2) when the printed PDF's `/URI` annotation count is below the source HTML's absolute-link count (a rasterised or flattened print that dropped links) or when an embedded `https://` link does not answer 200 (a Sign-Online link a pending redeploy would 404). The offline `/URI`-count check runs first and short-circuits, so a link-loss regression never depends on the network. **Block message:** `Blocked: the printed quote PDF is missing links the document declared, or an embedded link is not live (…). Do not send it. If a Sign-Online link is not live yet, deploy the signing page first; then re-render and re-print so the PDF carries every link the document declares.` **Log line** (stderr, on a block): `[quote-render] op=bypass jobId=<jobId> reason=pdf-link-conformance detail=<uri-count …|link <url> -> <code>>`. Every non-quote PDF, and any call with no receipt (the PreToolUse gate owns that block), passes at exit 0. **Fail-open** on an uninspectable call (tty / empty stdin / no `python3` / receipt or PDF absent). **Reach on upgrade:** `provision-account-dir.sh` writes this PostToolUse matcher only at account-provision time, so accounts provisioned before it landed keep a stale matcher and need a settings backfill (tracked as a follow-up task), the same reach limit the AskUserQuestion carrier gate hit.
194
195
  - `hooks/webfetch-preflight.mjs` — short-circuits WebFetch on JS-SPA shells with a structured `WEBFETCH_CANNOT_READ_JS_SPA` error so the agent surfaces a loud failure to the owner instead of paying the 60s extraction timeout. Fail-open on any internal error.
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # Regression test for quote-engine-gate.sh.
3
+ #
4
+ # The gate is a PreToolUse hook on Bash. It refuses a Bash command that runs the
5
+ # persisted quoting engine or render script directly (quoting/engine.mjs or
6
+ # quoting/render.mjs), because those scripts must run only through the
7
+ # deterministic quote-engine-run / quote-render MCP tools (which apply the gates
8
+ # and write receipts). A mode=verify engine run and every unrelated command pass.
9
+ #
10
+ # Covers:
11
+ # A. Bash running quoting/engine.mjs → BLOCKED (exit 2)
12
+ # B. Bash running quoting/render.mjs → BLOCKED
13
+ # C. engine.mjs run with mode=verify by hand → BLOCKED (verify runs through the tool too)
14
+ # D. engine.mjs run with a `# mode=verify` tail → BLOCKED (no token-anywhere escape)
15
+ # E. unrelated Bash (ls quoting) → ALLOWED
16
+ # F. a quote-engine-run MCP tool call → ALLOWED (not Bash)
17
+ # G. empty stdin → ALLOWED (fail-open)
18
+ # H. a non-Bash tool → ALLOWED (matcher defence-in-depth)
19
+ # I. a block emits the op=bypass fault line
20
+ # J. reading the script (cat) is not execution → ALLOWED
21
+ # K. grepping the script is not execution → ALLOWED
22
+ # L. the script run at command position → BLOCKED
23
+
24
+ set -u
25
+
26
+ HOOK="$(cd "$(dirname "$0")/.." && pwd)/quote-engine-gate.sh"
27
+ if [[ ! -x "$HOOK" ]]; then
28
+ echo "FAIL: $HOOK not executable" >&2
29
+ exit 1
30
+ fi
31
+
32
+ PASS=0
33
+ FAIL=0
34
+
35
+ run_case() {
36
+ local name="$1" stdin="$2" expected_exit="$3"
37
+ local actual_exit
38
+ ( printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>/dev/null )
39
+ actual_exit=$?
40
+ if [[ "$actual_exit" -eq "$expected_exit" ]]; then
41
+ echo "PASS: $name (exit=$actual_exit)"
42
+ PASS=$((PASS + 1))
43
+ else
44
+ echo "FAIL: $name (expected exit=$expected_exit, got=$actual_exit)" >&2
45
+ FAIL=$((FAIL + 1))
46
+ fi
47
+ }
48
+
49
+ bash_env() { # command string (JSON-escaped by caller)
50
+ printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"%s"}}' "$1"
51
+ }
52
+
53
+ run_case "A: Bash runs quoting/engine.mjs → BLOCKED" \
54
+ "$(bash_env "node quoting/engine.mjs < quoting/job-1854.json")" 2
55
+
56
+ run_case "B: Bash runs quoting/render.mjs → BLOCKED" \
57
+ "$(bash_env "node quoting/render.mjs")" 2
58
+
59
+ run_case "C: engine.mjs mode=verify by hand → BLOCKED" \
60
+ "$(bash_env "node quoting/engine.mjs mode=verify")" 2
61
+
62
+ run_case "D: engine.mjs with # mode=verify tail → BLOCKED" \
63
+ "$(bash_env "node quoting/engine.mjs < job.json # mode=verify")" 2
64
+
65
+ run_case "E: unrelated Bash → ALLOWED" \
66
+ "$(bash_env "ls quoting")" 0
67
+
68
+ run_case "J: reading the script (cat) → ALLOWED" \
69
+ "$(bash_env "cat quoting/engine.mjs")" 0
70
+
71
+ run_case "K: grepping the script → ALLOWED" \
72
+ "$(bash_env "grep foo quoting/render.mjs")" 0
73
+
74
+ run_case "L: script at command position → BLOCKED" \
75
+ "$(bash_env "./quoting/engine.mjs < job.json")" 2
76
+
77
+ run_case "F: quote-engine-run MCP call → ALLOWED" \
78
+ '{"hook_event_name":"PreToolUse","tool_name":"mcp__plugin_sitedesk-job_sitedesk-job__quote-engine-run","tool_input":{"job":"1854","mode":"quote"}}' 0
79
+
80
+ run_case "G: empty stdin → ALLOWED (fail-open)" \
81
+ "" 0
82
+
83
+ run_case "H: non-Bash tool → ALLOWED" \
84
+ '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/x/quoting/engine.mjs"}}' 0
85
+
86
+ # I: a block emits the op=bypass fault line on stderr.
87
+ STDERR=$( printf '%s' "$(bash_env "node quoting/engine.mjs")" | bash "$HOOK" 2>&1 >/dev/null )
88
+ if printf '%s' "$STDERR" | grep -q '\[quote-engine\] op=bypass reason=direct-script-exec'; then
89
+ echo "PASS: I: block emits op=bypass reason=direct-script-exec"
90
+ PASS=$((PASS + 1))
91
+ else
92
+ echo "FAIL: I: op=bypass line absent (got: $STDERR)" >&2
93
+ FAIL=$((FAIL + 1))
94
+ fi
95
+
96
+ echo "──────── quote-engine-gate test summary ────────"
97
+ echo "PASS=$PASS FAIL=$FAIL"
98
+ [[ "$FAIL" -eq 0 ]]
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # quote-engine-gate — PreToolUse gate on Bash.
3
+ #
4
+ # The account's quoting method is applied by two persisted scripts,
5
+ # quoting/engine.mjs (pricing) and quoting/render.mjs (documents). They must run
6
+ # ONLY through their MCP tools — quote-engine-run and quote-render — which apply
7
+ # the reconcile / margin-leak / link-liveness gates and write the figures and
8
+ # render receipts. Running either script directly from a Bash command bypasses
9
+ # all of that (the SiteDesk 2026-07-25 bypass: a repricing dispatched to
10
+ # content-producer that ran `node quoting/engine.mjs` and re-rendered by hand).
11
+ # This gate refuses that Bash call and points the agent at the tools. It fires
12
+ # only on an execution of the persisted script (a JS runtime invoking it, or the
13
+ # script run at command position), never on a read like `cat quoting/engine.mjs`.
14
+ # A mode=verify reproduction must also run through the quote-engine-run tool, so
15
+ # a hand-run `node quoting/engine.mjs mode=verify` is gated too.
16
+ #
17
+ # Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail OPEN on an
18
+ # uninspectable call (Bash is a general-purpose tool and the agent never controls
19
+ # this hook's stdin, so failing open opens no bypass while never bricking
20
+ # unrelated commands). No task numbers or internal refs in operator-visible text.
21
+
22
+ set -uo pipefail
23
+
24
+ # No envelope (tty or empty stdin) → allow: cannot inspect, must not brick Bash.
25
+ if [ -t 0 ]; then exit 0; fi
26
+ INPUT=$(cat)
27
+ [ -z "$INPUT" ] && exit 0
28
+
29
+ # Print "block" when the Bash command runs a persisted quoting script directly
30
+ # (and is not a mode=verify run), or nothing for any other call.
31
+ DECISION=$(INPUT="$INPUT" python3 - <<'PY' 2>/dev/null || true
32
+ import os, json, re, sys
33
+ try:
34
+ d = json.loads(os.environ["INPUT"])
35
+ except Exception:
36
+ sys.exit(0)
37
+ if (d.get("tool_name", "") or "") != "Bash":
38
+ sys.exit(0)
39
+ cmd = (d.get("tool_input", {}) or {}).get("command", "") or ""
40
+ if not cmd:
41
+ sys.exit(0)
42
+ script = r'quoting/(engine|render)\.mjs\b'
43
+ # Block only an EXECUTION of the persisted script, not a read/inspect of it:
44
+ # - a JS runtime invoking it: `node|nodejs|bun|deno|ts-node ... quoting/engine.mjs`
45
+ # - the script run directly at command position: `./quoting/engine.mjs`, or the
46
+ # path as a bare command after a separator.
47
+ # A read (`cat|grep|git diff|head|wc quoting/engine.mjs`) leaves the path as an
48
+ # argument, not at command position, so it is not matched and passes.
49
+ # Verify runs are NOT carved out: `mode=verify` must also go through the
50
+ # quote-engine-run tool, never a hand-run of the persisted script.
51
+ executed = (
52
+ re.search(r'\b(?:node|nodejs|bun|deno|ts-node)\b[^;&|\n]*' + script, cmd)
53
+ or re.search(r'(?:^|[;&|]\s*)\.?/?' + script, cmd)
54
+ )
55
+ # Known fail-open gap (accepted, consistent with this gate's soft posture): a
56
+ # cd-relative run like `cd quoting && node engine.mjs` drops the `quoting/`
57
+ # prefix and is not matched.
58
+ if not executed:
59
+ sys.exit(0)
60
+ print("block")
61
+ PY
62
+ )
63
+
64
+ [ "$DECISION" = "block" ] || exit 0
65
+
66
+ echo "[quote-engine] op=bypass reason=direct-script-exec" >&2
67
+ echo "Blocked: the quoting engine and render scripts must run through their tools, not directly. Use the quote-engine-run tool to price a job (it files the figures) and the quote-render tool to produce the documents (it files them behind the reconcile, margin-leak and link-liveness gates). Running quoting/engine.mjs or quoting/render.mjs by hand skips those gates and files no receipt." >&2
68
+ exit 2
@@ -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:f78dfe81d9832fd7197627e1cf770c730bb2d16b4b640f1a47558e86acfe0db8
4
+ content-hash: sha256:11d9347727ac157b2724bab8fb4011912235c2395a787ca178277396029eb14a
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -3076,6 +3076,38 @@ skillDirGone=<bool> pluginDirRemoved=<bool>` (`skillDirGone=false` with
3076
3076
  `outcome=ok` is a named failure) / `outcome=rejected reason=<invalid-name|
3077
3077
  not-found|rm-error>`.
3078
3078
 
3079
+ ### Agents
3080
+
3081
+ The Agents surface (`/agents`) is the admin view of the account's public agents —
3082
+ the per-agent directories under `<accountDir>/agents/<slug>/`, each carrying a
3083
+ `config.json` (`displayName`, `status`, `model`, `knowledgeKeywords`) plus the
3084
+ four owned docs `IDENTITY.md`, `SOUL.md`, `KNOWLEDGE.md`, `KNOWLEDGE-SUMMARY.md`.
3085
+ It renders one card per agent (display name, `/slug`, status dot); a card opens a
3086
+ **read-only** detail modal showing the status, model, whichever owned docs are
3087
+ present on disk, the knowledge keywords, and an open-agent link to the agent's
3088
+ public URL (computed client-side: an `admin.` host maps to the `public.` host).
3089
+ Read-only by design — agents are authored and edited through chat — with one
3090
+ exception: a **delete** behind an explicit confirm in the modal, which runs the
3091
+ existing loud-fail `DELETE` (graph projection cleanup precedes file removal).
3092
+ This page is the sole agent surface; the former "Public" flyout toggle in the
3093
+ account menu was removed.
3094
+
3095
+ The route (`server/routes/admin/agents.ts`) resolves the current account via
3096
+ `resolveAccount`; `:slug` params reject `/`, `..`, `\` (the traversal guard).
3097
+
3098
+ | Route | Behaviour |
3099
+ |-------|-----------|
3100
+ | `GET /api/admin/agents` | Lists `agents/*/` dirs with a valid `config.json` (never the `admin` agent), returns `{agents, skipped}` where `skipped` counts dirs whose `config.json` failed to parse — surfacing the drop the list used to make silently. |
3101
+ | `GET /api/admin/agents/:slug` | One agent's config fields plus the four owned docs and a `present` map. A missing doc is an empty string with `present.<role>=false`, never a 500; an unknown agent 404s. |
3102
+ | `DELETE /api/admin/agents/:slug` | Removes the agent dir after `deleteAgentProjection`; refuses the `admin` slug. Loud-fail: a graph-cleanup throw aborts the file removal. |
3103
+
3104
+ Observability rides `[admin/agents]`: `op=list agents=<n> skipped=<m>` (the
3105
+ surface heartbeat — `skipped>0` is a malformed-config signal the page renders as
3106
+ a banner), `op=detail slug=<…> present=<comma-list|none>` (a role absent from
3107
+ `present=` for an agent expected complete is the missing-doc signature), and the
3108
+ existing delete lines (`deleted agent "<slug>"` / `graph cleanup failed …
3109
+ aborting file removal`).
3110
+
3079
3111
  ### Graph
3080
3112
 
3081
3113
  | Mount | Purpose |
@@ -9,6 +9,18 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-07-25 (0.1.504)
13
+
14
+ - Tasks can now be assigned to a person directly from the picker in the Tasks view.
15
+ - The Agents page is back as its own admin surface, with a card grid and a detail view for each agent.
16
+ - The loading screen now covers every screen's first load, not just when switching accounts.
17
+
18
+ ## 2026-07-25 (0.1.503)
19
+
20
+ - The "stalled" indicator in chat now only shows when a turn is genuinely stuck, not during a normal pause.
21
+ - Quote price changes always go through the proper pricing engine now, closing a path that could skip its checks.
22
+ - The Skills and Calendar detail views are wider and no longer show an unnecessary scrollbar.
23
+
12
24
  ## 2026-07-25 (0.1.502)
13
25
 
14
26
  - Uploading a file from the phone or desktop app now shows as a proper thumbnail in chat, not a raw file path.
@@ -149,6 +149,38 @@ skillDirGone=<bool> pluginDirRemoved=<bool>` (`skillDirGone=false` with
149
149
  `outcome=ok` is a named failure) / `outcome=rejected reason=<invalid-name|
150
150
  not-found|rm-error>`.
151
151
 
152
+ ### Agents
153
+
154
+ The Agents surface (`/agents`) is the admin view of the account's public agents —
155
+ the per-agent directories under `<accountDir>/agents/<slug>/`, each carrying a
156
+ `config.json` (`displayName`, `status`, `model`, `knowledgeKeywords`) plus the
157
+ four owned docs `IDENTITY.md`, `SOUL.md`, `KNOWLEDGE.md`, `KNOWLEDGE-SUMMARY.md`.
158
+ It renders one card per agent (display name, `/slug`, status dot); a card opens a
159
+ **read-only** detail modal showing the status, model, whichever owned docs are
160
+ present on disk, the knowledge keywords, and an open-agent link to the agent's
161
+ public URL (computed client-side: an `admin.` host maps to the `public.` host).
162
+ Read-only by design — agents are authored and edited through chat — with one
163
+ exception: a **delete** behind an explicit confirm in the modal, which runs the
164
+ existing loud-fail `DELETE` (graph projection cleanup precedes file removal).
165
+ This page is the sole agent surface; the former "Public" flyout toggle in the
166
+ account menu was removed.
167
+
168
+ The route (`server/routes/admin/agents.ts`) resolves the current account via
169
+ `resolveAccount`; `:slug` params reject `/`, `..`, `\` (the traversal guard).
170
+
171
+ | Route | Behaviour |
172
+ |-------|-----------|
173
+ | `GET /api/admin/agents` | Lists `agents/*/` dirs with a valid `config.json` (never the `admin` agent), returns `{agents, skipped}` where `skipped` counts dirs whose `config.json` failed to parse — surfacing the drop the list used to make silently. |
174
+ | `GET /api/admin/agents/:slug` | One agent's config fields plus the four owned docs and a `present` map. A missing doc is an empty string with `present.<role>=false`, never a 500; an unknown agent 404s. |
175
+ | `DELETE /api/admin/agents/:slug` | Removes the agent dir after `deleteAgentProjection`; refuses the `admin` slug. Loud-fail: a graph-cleanup throw aborts the file removal. |
176
+
177
+ Observability rides `[admin/agents]`: `op=list agents=<n> skipped=<m>` (the
178
+ surface heartbeat — `skipped>0` is a malformed-config signal the page renders as
179
+ a banner), `op=detail slug=<…> present=<comma-list|none>` (a role absent from
180
+ `present=` for an agent expected complete is the missing-doc signature), and the
181
+ existing delete lines (`deleted agent "<slug>"` / `graph cleanup failed …
182
+ aborting file removal`).
183
+
152
184
  ### Graph
153
185
 
154
186
  | Mount | Purpose |
@@ -106,9 +106,9 @@ Run `work-ready` and surface the ready set if the user has open work. This answe
106
106
 
107
107
  ## Tools
108
108
 
109
- - `work-create` — Create a work item, optionally linked to a person (RAISED_BY) and entities (AFFECTS). For durable-action records, pass `kind` (e.g. `"onboarding-establish-owner"`, `"cloudflare-tunnel-login"`) to set status `running`; pass `inputs` with the form payload and `inputSchema` declaring any secret fields — values land on the Task as `inputs.<field>` props after `redactSecrets` strips schema-tagged keys; `inputsProvided` records the call-shape (names submitted) alongside the value-bearing `inputs.*`. Pass `raisedDuringConversationKey` to write a `:RAISED_DURING` edge to the originating Conversation when it differs from the current session. For estate-agent client work surfaced on the `/tasks` operator page, pass `clientName`, `propertyAddress`, and `fee` (GBP major units, e.g. `10` = £10) — all optional, recorded on the Task; the platform performs no charge computation.
110
- - `work-update` — Update name, description, status, priority, due date, or append a note (append-only). Pass `appendStep` to atomically append one entry to `steps[]` (call once per action phase); `errorMessage` is required when status transitions to `failed`; `completed` and `failed` transitions auto-set `completedAt`.
111
- - `work-list` — List work items filtered by status, priority, or related entity
109
+ - `work-create` — Create a work item, optionally linked to a person (RAISED_BY) and entities (AFFECTS). For durable-action records, pass `kind` (e.g. `"onboarding-establish-owner"`, `"cloudflare-tunnel-login"`) to set status `running`; pass `inputs` with the form payload and `inputSchema` declaring any secret fields — values land on the Task as `inputs.<field>` props after `redactSecrets` strips schema-tagged keys; `inputsProvided` records the call-shape (names submitted) alongside the value-bearing `inputs.*`. Pass `raisedDuringConversationKey` to write a `:RAISED_DURING` edge to the originating Conversation when it differs from the current session. For estate-agent client work surfaced on the `/tasks` operator page, pass `clientName`, `propertyAddress`, and `fee` (GBP major units, e.g. `10` = £10) — all optional, recorded on the Task; the platform performs no charge computation. Pass `assigneeUserId` to assign the task to a house/platform admin or this account's business owner (validated against the assignable set — house admins ∪ owner; an out-of-set userId assigns no one).
110
+ - `work-update` — Update name, description, status, priority, due date, or append a note (append-only). Pass `appendStep` to atomically append one entry to `steps[]` (call once per action phase); `errorMessage` is required when status transitions to `failed`; `completed` and `failed` transitions auto-set `completedAt`. Pass `assigneeUserId` to reassign (a house-admin/owner userId), `null` to unassign, or omit to leave assignment unchanged.
111
+ - `work-list` — List work items filtered by status, priority, or related entity. Each row includes the assignee userId (or `unassigned`).
112
112
  - `work-get` — Fetch a work item with full details and all relationships
113
113
  - `work-relate` — Add AFFECTS, BLOCKS, PART_OF, or RAISED_BY relationships. For Task targets, pass the taskId (UUID) from work-create. For other labels, pass the elementId from memory-search.
114
114
  - `work-complete` — Mark done, re-embed, surface newly-unblocked work
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=assignable-admins.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignable-admins.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/assignable-admins.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ // @vitest-environment node
2
+ //
3
+ // Task 1982 — readHouseAdminUserIds reads the house account.json admins[] from
4
+ // the accounts root; accountsRootFromEnv derives it from ACCOUNT_DIR.
5
+ import { describe, it, expect, afterEach } from "vitest";
6
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { readHouseAdminUserIds, accountsRootFromEnv } from "../lib/assignable-admins.js";
10
+ let roots = [];
11
+ function makeAccounts(accounts) {
12
+ const root = mkdtempSync(join(tmpdir(), "acct-root-"));
13
+ roots.push(root);
14
+ for (const a of accounts) {
15
+ mkdirSync(join(root, a.id));
16
+ writeFileSync(join(root, a.id, "account.json"), JSON.stringify(a.cfg));
17
+ }
18
+ return root;
19
+ }
20
+ afterEach(() => {
21
+ for (const r of roots)
22
+ rmSync(r, { recursive: true, force: true });
23
+ roots = [];
24
+ delete process.env.ACCOUNT_DIR;
25
+ });
26
+ describe("readHouseAdminUserIds (Task 1982)", () => {
27
+ it("returns the house admins[] userIds in file order", () => {
28
+ const root = makeAccounts([
29
+ { id: "client-1", cfg: { role: "client", accountId: "client-1" } },
30
+ { id: "house-1", cfg: { role: "house", accountId: "house-1", admins: [{ userId: "primary" }, { userId: "second" }] } },
31
+ ]);
32
+ expect(readHouseAdminUserIds(root)).toEqual(["primary", "second"]);
33
+ });
34
+ it("returns [] when there is no house account", () => {
35
+ const root = makeAccounts([{ id: "client-1", cfg: { role: "client", accountId: "client-1" } }]);
36
+ expect(readHouseAdminUserIds(root)).toEqual([]);
37
+ });
38
+ it("returns [] when the house admins[] is absent", () => {
39
+ const root = makeAccounts([{ id: "house-1", cfg: { role: "house", accountId: "house-1" } }]);
40
+ expect(readHouseAdminUserIds(root)).toEqual([]);
41
+ });
42
+ it("returns [] for an unreadable accounts root", () => {
43
+ expect(readHouseAdminUserIds("/no/such/dir/anywhere")).toEqual([]);
44
+ });
45
+ });
46
+ describe("accountsRootFromEnv (Task 1982)", () => {
47
+ it("is the parent of ACCOUNT_DIR", () => {
48
+ process.env.ACCOUNT_DIR = "/srv/data/accounts/acct-9";
49
+ expect(accountsRootFromEnv()).toBe("/srv/data/accounts");
50
+ });
51
+ it("is null when ACCOUNT_DIR is absent", () => {
52
+ expect(accountsRootFromEnv()).toBeNull();
53
+ });
54
+ });
55
+ //# sourceMappingURL=assignable-admins.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignable-admins.test.js","sourceRoot":"","sources":["../../src/__tests__/assignable-admins.test.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,9 @@
8
8
  // userId that resolves to no AdminUser, writes no ASSIGNED_TO edge.
9
9
  import { describe, it, expect, vi, afterEach } from "vitest";
10
10
  const { runMock, closeMock, writeMock } = vi.hoisted(() => ({
11
- runMock: vi.fn(async () => ({ records: [] })),
11
+ runMock: vi.fn(async (_cypher, _params) => ({
12
+ records: [],
13
+ })),
12
14
  closeMock: vi.fn(async () => { }),
13
15
  writeMock: vi.fn(async (_arg) => ({})),
14
16
  }));
@@ -69,4 +71,34 @@ describe("work-create ASSIGNED_TO env (Task 1500)", () => {
69
71
  expect(rels.some((r) => r.type === "ASSIGNED_TO")).toBe(false);
70
72
  });
71
73
  });
74
+ // Task 1982 — an agent may pass `assigneeUserId` explicitly. The param path is
75
+ // validated against the assignable set (house admins ∪ owner). With no
76
+ // ACCOUNT_DIR the house set is empty, so the owner (resolved from the graph) is
77
+ // the sole in-roster identity in these tests. The two run calls per assign are
78
+ // [owner-query, AdminUser-MATCH].
79
+ describe("work-create assigneeUserId param (Task 1982)", () => {
80
+ it("writes an ASSIGNED_TO edge for the account owner passed as assigneeUserId", async () => {
81
+ runMock
82
+ .mockResolvedValueOnce({ records: [{ get: (k) => (k === "userId" ? "owner-1" : null) }] }) // owner query
83
+ .mockResolvedValueOnce({ records: [{ get: () => "4:owner:9" }] }); // AdminUser MATCH
84
+ await workCreate({ ...baseParams, assigneeUserId: "owner-1" });
85
+ const assigned = relationshipsFromWrite().filter((r) => r.type === "ASSIGNED_TO");
86
+ expect(assigned).toEqual([{ type: "ASSIGNED_TO", direction: "outgoing", targetNodeId: "4:owner:9" }]);
87
+ });
88
+ it("param wins over the passive-intake env var", async () => {
89
+ process.env.INTAKE_ASSIGNED_ADMIN_USER_ID = "env-admin";
90
+ runMock
91
+ .mockResolvedValueOnce({ records: [{ get: (k) => (k === "userId" ? "owner-1" : null) }] }) // owner query
92
+ .mockResolvedValueOnce({ records: [{ get: () => "4:owner:9" }] }); // AdminUser MATCH
93
+ await workCreate({ ...baseParams, assigneeUserId: "owner-1" });
94
+ // The AdminUser MATCH must resolve the PARAM userId, not the env one.
95
+ const matchCall = runMock.mock.calls.find((c) => String(c[0]).includes("MATCH (au:AdminUser {userId: $userId})"));
96
+ expect(matchCall?.[1]).toMatchObject({ userId: "owner-1" });
97
+ });
98
+ it("writes no ASSIGNED_TO edge when the param userId is not in the roster", async () => {
99
+ runMock.mockResolvedValueOnce({ records: [{ get: (k) => (k === "userId" ? "owner-1" : null) }] }); // owner query only
100
+ await workCreate({ ...baseParams, assigneeUserId: "stranger" });
101
+ expect(relationshipsFromWrite().some((r) => r.type === "ASSIGNED_TO")).toBe(false);
102
+ });
103
+ });
72
104
  //# sourceMappingURL=work-create-assigned-admin-env.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"work-create-assigned-admin-env.test.js","sourceRoot":"","sources":["../../src/__tests__/work-create-assigned-admin-env.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"work-create-assigned-admin-env.test.js","sourceRoot":"","sources":["../../src/__tests__/work-create-assigned-admin-env.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=work-update-assign.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-update-assign.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/work-update-assign.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ // @vitest-environment node
2
+ //
3
+ // Task 1982 — work-update accepts `assigneeUserId`: a non-empty string reassigns
4
+ // (validated against the assignable set), null unassigns, absent leaves it. Any
5
+ // prior ASSIGNED_TO is cleared first. With no ACCOUNT_DIR the house set is empty,
6
+ // so the account owner is the sole in-roster identity here.
7
+ import { describe, it, expect, vi, afterEach } from "vitest";
8
+ const { runMock, closeMock } = vi.hoisted(() => ({
9
+ runMock: vi.fn(),
10
+ closeMock: vi.fn(async () => { }),
11
+ }));
12
+ vi.mock("../lib/neo4j.js", () => ({
13
+ getSession: () => ({ run: runMock, close: closeMock }),
14
+ }));
15
+ vi.mock("../lib/embeddings.js", () => ({
16
+ computeEmbedding: async () => [],
17
+ }));
18
+ import { workUpdate } from "../tools/work-update.js";
19
+ const rec = (fields) => ({ get: (k) => fields[k] ?? null });
20
+ // One mock that answers by query shape, so the test is order-independent.
21
+ function installRun(opts) {
22
+ runMock.mockImplementation(async (query) => {
23
+ if (query.includes("RETURN t.name AS name")) {
24
+ return { records: [rec({ name: "n", description: "d", notes: "", status: "pending", priority: "normal", kind: null, stepsCount: 0 })] };
25
+ }
26
+ if (query.includes("role: 'owner'")) {
27
+ return { records: opts.ownerId ? [rec({ userId: opts.ownerId })] : [] };
28
+ }
29
+ if (query.includes("MERGE (t)-[:ASSIGNED_TO]->(au)")) {
30
+ return { records: opts.adminNodeExists === false ? [] : [rec({ userId: "x" })] };
31
+ }
32
+ // main SET update, clear DELETE, anything else
33
+ return { records: [rec({ stepsCount: 0 })] };
34
+ });
35
+ }
36
+ function assignCalls() {
37
+ return runMock.mock.calls.filter((c) => String(c[0]).includes("MERGE (t)-[:ASSIGNED_TO]->(au)"));
38
+ }
39
+ function clearCalls() {
40
+ return runMock.mock.calls.filter((c) => String(c[0]).includes("OPTIONAL MATCH (t)-[r:ASSIGNED_TO]->(:AdminUser) DELETE r"));
41
+ }
42
+ afterEach(() => { runMock.mockReset(); });
43
+ const base = { taskId: "t-1", accountId: "acct-1" };
44
+ describe("work-update assigneeUserId (Task 1982)", () => {
45
+ it("sets the edge for an in-roster owner (clear then MERGE)", async () => {
46
+ installRun({ ownerId: "owner-1", adminNodeExists: true });
47
+ await workUpdate({ ...base, assigneeUserId: "owner-1" });
48
+ expect(clearCalls().length).toBe(1);
49
+ expect(assignCalls().length).toBe(1);
50
+ expect(assignCalls()[0][1]).toMatchObject({ target: "owner-1" });
51
+ });
52
+ it("unassigns on null: clears the edge and creates no new one", async () => {
53
+ installRun({ ownerId: "owner-1" });
54
+ await workUpdate({ ...base, assigneeUserId: null });
55
+ expect(clearCalls().length).toBe(1);
56
+ expect(assignCalls().length).toBe(0);
57
+ });
58
+ it("leaves assignment untouched when assigneeUserId is absent", async () => {
59
+ installRun({ ownerId: "owner-1" });
60
+ await workUpdate({ ...base, note: "just a note" });
61
+ expect(clearCalls().length).toBe(0);
62
+ expect(assignCalls().length).toBe(0);
63
+ });
64
+ it("leaves the existing edge untouched for a userId outside the roster (validate-first)", async () => {
65
+ installRun({ ownerId: "owner-1" });
66
+ await workUpdate({ ...base, assigneeUserId: "stranger" });
67
+ // Validated before any write, so the old assignment is neither cleared nor
68
+ // replaced — matches task-assign.ts's 403 guarantee.
69
+ expect(clearCalls().length).toBe(0);
70
+ expect(assignCalls().length).toBe(0);
71
+ });
72
+ });
73
+ //# sourceMappingURL=work-update-assign.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-update-assign.test.js","sourceRoot":"","sources":["../../src/__tests__/work-update-assign.test.ts"],"names":[],"mappings":"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"}
@@ -87,6 +87,7 @@ eagerTool(server, "work-create", "Create a Task node in the graph. Every Task mu
87
87
  .optional()
88
88
  .describe("Form schema declaring secret-tagged fields. Omit when the action records no secrets."),
89
89
  raisedDuringConversationKey: z.string().optional().describe("Conversation sessionKey for the RAISED_DURING edge. Defaults to the current session. Use this when the Task is raised by an out-of-band surface (e.g. an HTTP endpoint) but should still link to a specific conversation."),
90
+ assigneeUserId: z.string().optional().describe("userId of a house/platform admin or this account's business owner to assign the task to. Validated against the assignable set; an out-of-set userId assigns no one."),
90
91
  }, async (params) => {
91
92
  if (!accountId)
92
93
  return refuseNoAccount("work-create");
@@ -126,6 +127,7 @@ eagerTool(server, "work-update", "Update a task's name, description, status, pri
126
127
  note: z.string().optional().describe("Progress note to append. Timestamped and appended to history — never replaces existing notes."),
127
128
  appendStep: z.string().optional().describe("Append a single step label to the Task's `steps[]` array. Atomic — one Cypher round-trip; safe under concurrent calls. Use one call per action phase."),
128
129
  errorMessage: z.string().optional().describe("Error message stored on the Task when status transitions to `failed`. Required for the `failed` transition; non-empty errorMessage strings only."),
130
+ assigneeUserId: z.string().nullable().optional().describe("Reassign the task: userId of a house/platform admin or this account's owner to assign to, `null` to unassign, or omit to leave assignment unchanged. Validated against the assignable set."),
129
131
  }, async (params) => {
130
132
  if (!accountId)
131
133
  return refuseNoAccount("work-update");
@@ -158,7 +160,8 @@ eagerTool(server, "work-list", "List tasks filtered by status, priority, or rela
158
160
  }
159
161
  const lines = tasks.map((t) => {
160
162
  const due = t.dueDate ? ` due:${t.dueDate.slice(0, 10)}` : "";
161
- return `[${t.priority.toUpperCase()}] ${t.status} ${t.name}${due} (${t.taskId})`;
163
+ const assignee = t.assigneeUserId ? ` assignee:${t.assigneeUserId}` : " assignee:unassigned";
164
+ return `[${t.priority.toUpperCase()}] ${t.status} — ${t.name}${due}${assignee} (${t.taskId})`;
162
165
  });
163
166
  return {
164
167
  content: [{ type: "text", text: `${tasks.length} task(s):\n\n${lines.join("\n")}` }],