@rubytech/create-maxy-code 0.1.427 → 0.1.431

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 (250) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/embed-client/dist/index.d.ts +5 -1
  3. package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/embed-client/dist/index.js +114 -36
  5. package/payload/platform/lib/embed-client/dist/index.js.map +1 -1
  6. package/payload/platform/lib/embed-client/src/index.ts +141 -34
  7. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +72 -4
  8. package/payload/platform/plugins/docs/PLUGIN.md +1 -0
  9. package/payload/platform/plugins/docs/references/admin-ui.md +2 -0
  10. package/payload/platform/plugins/docs/references/internals.md +3 -3
  11. package/payload/platform/plugins/docs/references/workflows.md +61 -0
  12. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.d.ts +2 -0
  13. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.d.ts.map +1 -0
  14. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.js +92 -0
  15. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.js.map +1 -0
  16. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.d.ts +2 -0
  17. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.d.ts.map +1 -0
  18. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.js +56 -0
  19. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.js.map +1 -0
  20. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js +56 -0
  21. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js.map +1 -1
  22. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts +8 -1
  23. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.d.ts.map +1 -1
  24. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js +8 -1
  25. package/payload/platform/plugins/whatsapp/mcp/dist/resolve-read-account.js.map +1 -1
  26. package/payload/platform/plugins/work/mcp/dist/lib/embeddings.d.ts +9 -3
  27. package/payload/platform/plugins/work/mcp/dist/lib/embeddings.d.ts.map +1 -1
  28. package/payload/platform/plugins/work/mcp/dist/lib/embeddings.js +19 -15
  29. package/payload/platform/plugins/work/mcp/dist/lib/embeddings.js.map +1 -1
  30. package/payload/platform/plugins/workflows/PLUGIN.md +29 -9
  31. package/payload/platform/plugins/workflows/mcp/dist/index.js +34 -0
  32. package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts +1 -1
  34. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.d.ts.map +1 -1
  35. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js +7 -18
  36. package/payload/platform/plugins/workflows/mcp/dist/lib/embeddings.js.map +1 -1
  37. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts +2 -2
  38. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts.map +1 -1
  39. package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js.map +1 -1
  40. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts +25 -0
  41. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts.map +1 -1
  42. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +14 -1
  43. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -1
  44. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts +25 -1
  45. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts.map +1 -1
  46. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +79 -15
  47. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
  48. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts +2 -0
  49. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.d.ts.map +1 -1
  50. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js +4 -0
  51. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-get.js.map +1 -1
  52. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts +3 -1
  53. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts.map +1 -1
  54. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js +8 -1
  55. package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js.map +1 -1
  56. package/payload/platform/plugins/workflows/skills/workflow-manager/SKILL.md +24 -3
  57. package/payload/platform/services/telegram-channel/dist/instructions.js +1 -1
  58. package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
  59. package/payload/platform/services/telegram-channel/dist/notification.d.ts +10 -5
  60. package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
  61. package/payload/platform/services/telegram-channel/dist/notification.js +9 -6
  62. package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
  63. package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
  64. package/payload/platform/services/telegram-channel/dist/server.js +15 -5
  65. package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
  66. package/payload/platform/services/webchat-channel/dist/instructions.js +1 -1
  67. package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
  68. package/payload/platform/services/webchat-channel/dist/notification.d.ts +12 -10
  69. package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
  70. package/payload/platform/services/webchat-channel/dist/notification.js +12 -11
  71. package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
  72. package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
  73. package/payload/platform/services/webchat-channel/dist/server.js +12 -1
  74. package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
  75. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +11 -6
  76. package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
  77. package/payload/platform/services/whatsapp-channel/dist/notification.js +11 -8
  78. package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
  79. package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
  80. package/payload/platform/services/whatsapp-channel/dist/server.js +39 -14
  81. package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
  82. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts +17 -2
  83. package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
  84. package/payload/platform/services/whatsapp-channel/dist/targets.js +37 -2
  85. package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
  86. package/payload/server/{chunk-3Z5WE53X.js → chunk-UUG6ZSO5.js} +65 -9
  87. package/payload/server/maxy-edge.js +1 -1
  88. package/payload/server/public/assets/AdminLoginScreens-CcW3pAAZ.js +1 -0
  89. package/payload/server/public/assets/AdminShell-BfLQQ27Z.js +2 -0
  90. package/payload/server/public/assets/{Checkbox-B_lfjsqd.js → Checkbox-BC0c_gbn.js} +1 -1
  91. package/payload/server/public/assets/admin-Co3hOTNp.js +1 -0
  92. package/payload/server/public/assets/{arc-dVLpR-ec.js → arc-qKfXIt8v.js} +1 -1
  93. package/payload/server/public/assets/architecture-YZFGNWBL-FPHT7s-C.js +1 -0
  94. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-C6u_CVzb.js → architectureDiagram-Q4EWVU46-DkVrGn7G.js} +1 -1
  95. package/payload/server/public/assets/audio-attachment-mime-CwYrV1pg.js +2 -0
  96. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-Cy8F5b8O.js → blockDiagram-DXYQGD6D-DqavswGH.js} +1 -1
  97. package/payload/server/public/assets/{browser-CCWufeXr.js → browser-CvZxNdKw.js} +1 -1
  98. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-DRXrlepj.js → c4Diagram-AHTNJAMY-B7fyKiXe.js} +1 -1
  99. package/payload/server/public/assets/calendar-DXxLMF2J.js +1 -0
  100. package/payload/server/public/assets/channel-j70eMvcL.js +1 -0
  101. package/payload/server/public/assets/chat-rxbGimwf.js +1 -0
  102. package/payload/server/public/assets/chevron-left-CLPxfjDV.js +1 -0
  103. package/payload/server/public/assets/{chunk-2KRD3SAO-fEyfhRLz.js → chunk-2KRD3SAO-DsgMtGkv.js} +1 -1
  104. package/payload/server/public/assets/{chunk-336JU56O-NUWO4y7q.js → chunk-336JU56O-CkdISfNt.js} +2 -2
  105. package/payload/server/public/assets/chunk-426QAEUC-D-NcAlTW.js +1 -0
  106. package/payload/server/public/assets/{chunk-4BX2VUAB-BnfiNR3l.js → chunk-4BX2VUAB-CYpiF2bl.js} +1 -1
  107. package/payload/server/public/assets/{chunk-4TB4RGXK-Bj_aJOz8.js → chunk-4TB4RGXK-CWyc_F7v.js} +1 -1
  108. package/payload/server/public/assets/{chunk-55IACEB6-aK0DNSKb.js → chunk-55IACEB6-BbzKMw2Z.js} +1 -1
  109. package/payload/server/public/assets/{chunk-5FUZZQ4R-DeqAkq5C.js → chunk-5FUZZQ4R-u19Nai9e.js} +1 -1
  110. package/payload/server/public/assets/{chunk-5PVQY5BW-SShlujAQ.js → chunk-5PVQY5BW-Ba8by6Uy.js} +1 -1
  111. package/payload/server/public/assets/{chunk-67CJDMHE-CZpzQ0DL.js → chunk-67CJDMHE-BwUuxtyb.js} +1 -1
  112. package/payload/server/public/assets/{chunk-7N4EOEYR-COnjO6jy.js → chunk-7N4EOEYR-BRH9iM9L.js} +1 -1
  113. package/payload/server/public/assets/{chunk-AA7GKIK3-C9V31L2z.js → chunk-AA7GKIK3-BGoztgLL.js} +1 -1
  114. package/payload/server/public/assets/{chunk-BSJP7CBP-DSwnSGe3.js → chunk-BSJP7CBP-C5GDAZTl.js} +1 -1
  115. package/payload/server/public/assets/{chunk-CIAEETIT-CnRUTuta.js → chunk-CIAEETIT-ChY32VPS.js} +1 -1
  116. package/payload/server/public/assets/{chunk-EDXVE4YY-BexiL2LH.js → chunk-EDXVE4YY-CPSD3p6p.js} +1 -1
  117. package/payload/server/public/assets/{chunk-ENJZ2VHE-aTklWdJQ.js → chunk-ENJZ2VHE-D1oL-iUr.js} +1 -1
  118. package/payload/server/public/assets/{chunk-FMBD7UC4-svcEBQzs.js → chunk-FMBD7UC4-CPvYEr54.js} +1 -1
  119. package/payload/server/public/assets/{chunk-FOC6F5B3-CG7R7Xri.js → chunk-FOC6F5B3-9Yyj7Vbz.js} +1 -1
  120. package/payload/server/public/assets/{chunk-ICPOFSXX-CGN7Z4x9.js → chunk-ICPOFSXX-BaYgh8FR.js} +2 -2
  121. package/payload/server/public/assets/{chunk-K5T4RW27-CWBh78jw.js → chunk-K5T4RW27-5ucpeRvm.js} +1 -1
  122. package/payload/server/public/assets/{chunk-KGLVRYIC-Ctiq4q-5.js → chunk-KGLVRYIC-DW-FHk7T.js} +1 -1
  123. package/payload/server/public/assets/{chunk-LIHQZDEY-BII77Q7K.js → chunk-LIHQZDEY-CGGVfAyG.js} +1 -1
  124. package/payload/server/public/assets/{chunk-ORNJ4GCN-DT7R01H8.js → chunk-ORNJ4GCN-Bv8EUa5d.js} +1 -1
  125. package/payload/server/public/assets/{chunk-OYMX7WX6-y-Jkbd4C.js → chunk-OYMX7WX6-C1mXJnyw.js} +1 -1
  126. package/payload/server/public/assets/chunk-QZHKN3VN-CAwbQBtf.js +1 -0
  127. package/payload/server/public/assets/{chunk-U2HBQHQK-DzFo6KbQ.js → chunk-U2HBQHQK-PwSQz6EP.js} +1 -1
  128. package/payload/server/public/assets/{chunk-X2U36JSP-CYSwgtLV.js → chunk-X2U36JSP--NPHTBlU.js} +1 -1
  129. package/payload/server/public/assets/{chunk-XPW4576I-DHlM4tOn.js → chunk-XPW4576I-Bbc7Da32.js} +1 -1
  130. package/payload/server/public/assets/{chunk-YZCP3GAM-B20rTmLl.js → chunk-YZCP3GAM-D-9v8e--.js} +1 -1
  131. package/payload/server/public/assets/{chunk-ZZ45TVLE-C7hP4J8m.js → chunk-ZZ45TVLE-CMPg670Q.js} +1 -1
  132. package/payload/server/public/assets/classDiagram-6PBFFD2Q-Ct6Rn0vt.js +1 -0
  133. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-B_2XCIFv.js +1 -0
  134. package/payload/server/public/assets/clone-DjPWA_NH.js +1 -0
  135. package/payload/server/public/assets/{cose-bilkent-S5V4N54A-nKjTv9sR.js → cose-bilkent-S5V4N54A-NVSb1Bhy.js} +1 -1
  136. package/payload/server/public/assets/{dagre-KV5264BT-BNJYTnls.js → dagre-KV5264BT-V2Ai1DFl.js} +1 -1
  137. package/payload/server/public/assets/{dagre-Cx8LiiUZ.js → dagre-QfLcO-1j.js} +1 -1
  138. package/payload/server/public/assets/data-D0YFYP1i.js +1 -0
  139. package/payload/server/public/assets/{diagram-5BDNPKRD-CoAN5_f9.js → diagram-5BDNPKRD-Caw21693.js} +1 -1
  140. package/payload/server/public/assets/{diagram-G4DWMVQ6-pRmsN4_x.js → diagram-G4DWMVQ6-CgwVkT2q.js} +1 -1
  141. package/payload/server/public/assets/{diagram-MMDJMWI5-CRoylyp4.js → diagram-MMDJMWI5-DFUrLZhs.js} +1 -1
  142. package/payload/server/public/assets/{diagram-TYMM5635-BT3ZtG5b.js → diagram-TYMM5635-CEEiEuZJ.js} +1 -1
  143. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DSKws1ZV.js → erDiagram-SMLLAGMA-CG10NrT3.js} +1 -1
  144. package/payload/server/public/assets/file-download-B2B2DTar.js +1 -0
  145. package/payload/server/public/assets/{flatten-jn8bZgcI.js → flatten-DAO0sfCM.js} +1 -1
  146. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-C2L38ZE5.js → flowDiagram-DWJPFMVM-BqkV9Okv.js} +1 -1
  147. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-Bm2h-vjm.js → ganttDiagram-T4ZO3ILL-DRiCDQF6.js} +1 -1
  148. package/payload/server/public/assets/gitGraph-7Q5UKJZL-B8Hx7f-D.js +1 -0
  149. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BoowbF7u.js → gitGraphDiagram-UUTBAWPF-BZV2voyy.js} +1 -1
  150. package/payload/server/public/assets/graph-DRNzDBTV.js +51 -0
  151. package/payload/server/public/assets/{graph-labels-BLx-0vyT.js → graph-labels-DxqDdO7G.js} +1 -1
  152. package/payload/server/public/assets/{graphlib-BvT2s_o1.js → graphlib-DGPThX8M.js} +1 -1
  153. package/payload/server/public/assets/info-OMHHGYJF-BicCJTqU.js +1 -0
  154. package/payload/server/public/assets/infoDiagram-42DDH7IO-tf-8rOez.js +2 -0
  155. package/payload/server/public/assets/{isEmpty-Dkk6_L-B.js → isEmpty-Cb_U81C6.js} +1 -1
  156. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-nxcmj-IX.js → ishikawaDiagram-UXIWVN3A-BKsAxSvp.js} +1 -1
  157. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-q5SNIvmS.js → journeyDiagram-VCZTEJTY-CmliuOd-.js} +1 -1
  158. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BugG9Q1o.js → kanban-definition-6JOO6SKY-DAlElMb-.js} +1 -1
  159. package/payload/server/public/assets/{line-MZCQLR-q.js → line-DKZqLKAC.js} +1 -1
  160. package/payload/server/public/assets/{linear-DmQGNk-g.js → linear-CbSGdh1x.js} +1 -1
  161. package/payload/server/public/assets/{maximize-2-CnNQnj_v.js → maximize-2-DKx6OBKv.js} +1 -1
  162. package/payload/server/public/assets/{mermaid-parser.core-DvoeqLxw.js → mermaid-parser.core-DFvbao9C.js} +2 -2
  163. package/payload/server/public/assets/{mermaid.core-BLaw7iG4.js → mermaid.core-C0vEBxDm.js} +3 -3
  164. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DMkt10Vc.js → mindmap-definition-QFDTVHPH-CShASUKd.js} +1 -1
  165. package/payload/server/public/assets/operator-Djv3MdcL.js +1 -0
  166. package/payload/server/public/assets/{ordinal-DuJYM7p1.js → ordinal-H0tVmBDP.js} +1 -1
  167. package/payload/server/public/assets/packet-4T2RLAQJ-Ckn_5hq0.js +1 -0
  168. package/payload/server/public/assets/page-Ci27UrJz.js +32 -0
  169. package/payload/server/public/assets/page-D1EAVC_9.js +1 -0
  170. package/payload/server/public/assets/{pdf-render-riHufgrn.js → pdf-render-jAQU0nXt.js} +1 -1
  171. package/payload/server/public/assets/pie-ZZUOXDRM-DzlGQW3J.js +1 -0
  172. package/payload/server/public/assets/{pieDiagram-DEJITSTG-DRp4lD2B.js → pieDiagram-DEJITSTG-CBZHpDAJ.js} +1 -1
  173. package/payload/server/public/assets/{play-yEkd7ouW.js → play-DHttF4fl.js} +1 -1
  174. package/payload/server/public/assets/public-C6k-fPfx.js +1 -0
  175. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DkT63Dn4.js → quadrantDiagram-34T5L4WZ-n3bB3K91.js} +1 -1
  176. package/payload/server/public/assets/radar-PYXPWWZC-BHo2YD8b.js +1 -0
  177. package/payload/server/public/assets/{reduce-COyotL2V.js → reduce-DAvwat-a.js} +1 -1
  178. package/payload/server/public/assets/{requirementDiagram-MS252O5E-65GGtDkc.js → requirementDiagram-MS252O5E-DiDvQK3c.js} +1 -1
  179. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-D1vRwLWd.js → sankeyDiagram-XADWPNL6-DWDQQZmd.js} +1 -1
  180. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-Bp47ZSN2.js → sequenceDiagram-FGHM5R23-DmAqRlBT.js} +1 -1
  181. package/payload/server/public/assets/square-CHcQHtK8.js +1 -0
  182. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-BZHHcP_e.js → stateDiagram-FHFEXIEX-D4R-n6bZ.js} +1 -1
  183. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-BcFRbGRu.js +1 -0
  184. package/payload/server/public/assets/tasks-ClfJU1CK.js +1 -0
  185. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-DZ1wUyj-.js → timeline-definition-GMOUNBTQ-CjiHGP4l.js} +1 -1
  186. package/payload/server/public/assets/treeView-SZITEDCU-BtRr5vXp.js +1 -0
  187. package/payload/server/public/assets/treemap-W4RFUUIX-D8p0sbyo.js +1 -0
  188. package/payload/server/public/assets/useCopyFeedback-CPEOUSGU.js +1 -0
  189. package/payload/server/public/assets/{useSubAccountSwitcher-ayajZz8T.css → useSubAccountSwitcher-CdS1CorH.css} +1 -1
  190. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CttBahsn.js → vennDiagram-DHZGUBPP-CBdDQ7c-.js} +1 -1
  191. package/payload/server/public/assets/wardley-RL74JXVD-DgMn-9ZJ.js +1 -0
  192. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-Y5tWieZg.js → wardleyDiagram-NUSXRM2D-BlIQPKLb.js} +1 -1
  193. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-C78A9qbe.js → xychartDiagram-5P7HB3ND-CslKgh9B.js} +1 -1
  194. package/payload/server/public/browser.html +6 -6
  195. package/payload/server/public/calendar.html +7 -7
  196. package/payload/server/public/chat.html +13 -14
  197. package/payload/server/public/data.html +10 -10
  198. package/payload/server/public/graph.html +11 -11
  199. package/payload/server/public/index.html +14 -15
  200. package/payload/server/public/operator.html +15 -16
  201. package/payload/server/public/public.html +13 -14
  202. package/payload/server/public/tasks.html +5 -5
  203. package/payload/server/server.js +321 -32
  204. package/payload/server/public/assets/AdminLoginScreens-yAIhWcGc.js +0 -1
  205. package/payload/server/public/assets/AdminShell-Bk1AFQ4D.js +0 -2
  206. package/payload/server/public/assets/Transcript-D67OZnVM.js +0 -2
  207. package/payload/server/public/assets/admin-DuOhkvrZ.js +0 -1
  208. package/payload/server/public/assets/admin-types-ClHBm0ni.js +0 -1
  209. package/payload/server/public/assets/architecture-YZFGNWBL-CgeA3HPD.js +0 -1
  210. package/payload/server/public/assets/calendar-CNk1iPrA.js +0 -1
  211. package/payload/server/public/assets/channel-B04VBWf7.js +0 -1
  212. package/payload/server/public/assets/chat-7qB-dwBq.js +0 -1
  213. package/payload/server/public/assets/chevron-left-CpcDbHIz.js +0 -1
  214. package/payload/server/public/assets/chunk-426QAEUC-DZkHcXRA.js +0 -1
  215. package/payload/server/public/assets/chunk-QZHKN3VN-D9Mwgzti.js +0 -1
  216. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CIo_TzfF.js +0 -1
  217. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-SzQYvAUZ.js +0 -1
  218. package/payload/server/public/assets/clone-BVjcjsBA.js +0 -1
  219. package/payload/server/public/assets/data-BnHcS27X.js +0 -1
  220. package/payload/server/public/assets/file-download-Hf_UILaA.js +0 -1
  221. package/payload/server/public/assets/gitGraph-7Q5UKJZL-DzQ18i0T.js +0 -1
  222. package/payload/server/public/assets/graph-klLugHEK.js +0 -51
  223. package/payload/server/public/assets/info-OMHHGYJF-CwsoaVzV.js +0 -1
  224. package/payload/server/public/assets/infoDiagram-42DDH7IO-ieNsH2bV.js +0 -2
  225. package/payload/server/public/assets/operator-CRcySYv9.js +0 -1
  226. package/payload/server/public/assets/packet-4T2RLAQJ-DxB-bO4L.js +0 -1
  227. package/payload/server/public/assets/page-CVo3CWIY.js +0 -1
  228. package/payload/server/public/assets/page-DPTbZRwl.js +0 -32
  229. package/payload/server/public/assets/pie-ZZUOXDRM-PSdKvB8-.js +0 -1
  230. package/payload/server/public/assets/public-Fi6ccYaE.js +0 -1
  231. package/payload/server/public/assets/radar-PYXPWWZC-D8FqRiWq.js +0 -1
  232. package/payload/server/public/assets/rotate-ccw-xHksz9mr.js +0 -1
  233. package/payload/server/public/assets/square-BZqaxg7D.js +0 -1
  234. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-qVtoGx56.js +0 -1
  235. package/payload/server/public/assets/tasks-COWmfts8.js +0 -1
  236. package/payload/server/public/assets/treeView-SZITEDCU-i-w4rJmQ.js +0 -1
  237. package/payload/server/public/assets/treemap-W4RFUUIX-Dij6AlAj.js +0 -1
  238. package/payload/server/public/assets/wardley-RL74JXVD-BJhOQVqW.js +0 -1
  239. /package/payload/server/public/assets/{_baseFor-Bjj9It3Q.js → _baseFor-D3BqAOBr.js} +0 -0
  240. /package/payload/server/public/assets/{array-BrrxEo12.js → array-iYP9fzJp.js} +0 -0
  241. /package/payload/server/public/assets/{cytoscape.esm-CTWJxUxo.js → cytoscape.esm-BQSBzPsB.js} +0 -0
  242. /package/payload/server/public/assets/{defaultLocale-CFDbRV4k.js → defaultLocale-BrdA1gDi.js} +0 -0
  243. /package/payload/server/public/assets/{dist-CRpLAnCP.js → dist-Cvgxm7qv.js} +0 -0
  244. /package/payload/server/public/assets/{init-dXCzGgLP.js → init-Bx0S6pkp.js} +0 -0
  245. /package/payload/server/public/assets/{katex-CHA1JFYJ.js → katex-BsiH_a6d.js} +0 -0
  246. /package/payload/server/public/assets/{path-CCRpdXhK.js → path-BLSgTy3w.js} +0 -0
  247. /package/payload/server/public/assets/{preload-helper-CH6UZRzu.js → preload-helper-ClBMtM1g.js} +0 -0
  248. /package/payload/server/public/assets/{rough.esm-Bud4g4fh.js → rough.esm-BMOtDaaS.js} +0 -0
  249. /package/payload/server/public/assets/{src-xN1oULc8.js → src-DV4LKv2E.js} +0 -0
  250. /package/payload/server/public/assets/{useSubAccountSwitcher-DADvGnr1.js → useSubAccountSwitcher-DyjTbzQR.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.427",
3
+ "version": "0.1.431",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1,6 +1,10 @@
1
- export declare const EMBED_INPUT_MAX_CHARS = 8000;
1
+ export declare const EMBED_INPUT_MAX_CHARS: number;
2
2
  export declare const EMBED_SUBBATCH_SIZE: number;
3
3
  export declare const EMBED_SUBBATCH_COOLDOWN_MS: number;
4
+ export declare const EMBED_MS_PER_INPUT: number;
5
+ export declare const EMBED_REQUEST_TIMEOUT_MS: number;
6
+ export declare const EMBED_KEEP_ALIVE: number | string;
4
7
  export declare function embed(text: string): Promise<number[]>;
8
+ export declare function effectiveSubBatchSize(): number;
5
9
  export declare function embedBatch(texts: string[]): Promise<number[][]>;
6
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAmB1C,eAAO,MAAM,mBAAmB,QAA6D,CAAC;AAC9F,eAAO,MAAM,0BAA0B,QAGtC,CAAC;AA4BF,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAa3D;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAoBrE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,qBAAqB,QASjC,CAAC;AAaF,eAAO,MAAM,mBAAmB,QAA6D,CAAC;AAC9F,eAAO,MAAM,0BAA0B,QAGtC,CAAC;AAWF,eAAO,MAAM,kBAAkB,QAA8D,CAAC;AAQ9F,eAAO,MAAM,wBAAwB,QAGpC,CAAC;AAcF,eAAO,MAAM,gBAAgB,EAAE,MAAM,GAAG,MAEhB,CAAC;AA8EzB,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG3D;AAKD,wBAAgB,qBAAqB,IAAI,MAAM,CAM9C;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAqBrE"}
@@ -6,16 +6,32 @@
6
6
  // and one POST shape across the platform (Task 636). fetch-only — no node:fs —
7
7
  // so it is safe to bundle into the ESM payload.
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.EMBED_SUBBATCH_COOLDOWN_MS = exports.EMBED_SUBBATCH_SIZE = exports.EMBED_INPUT_MAX_CHARS = void 0;
9
+ exports.EMBED_KEEP_ALIVE = exports.EMBED_REQUEST_TIMEOUT_MS = exports.EMBED_MS_PER_INPUT = exports.EMBED_SUBBATCH_COOLDOWN_MS = exports.EMBED_SUBBATCH_SIZE = exports.EMBED_INPUT_MAX_CHARS = void 0;
10
10
  exports.embed = embed;
11
+ exports.effectiveSubBatchSize = effectiveSubBatchSize;
11
12
  exports.embedBatch = embedBatch;
12
13
  const OLLAMA_URL = process.env.OLLAMA_URL ?? "http://localhost:11434";
13
14
  const EMBED_MODEL = process.env.EMBED_MODEL ?? "nomic-embed-text";
14
- // nomic-embed-text has a 2048-token context (~8k chars at ~4 chars/token).
15
- // An over-limit input makes the Ollama embed endpoint error; cap defensively so
16
- // an oversized body embeds (truncated) instead of throwing and aborting the
17
- // caller.
18
- exports.EMBED_INPUT_MAX_CHARS = 8000;
15
+ function envInt(name, def) {
16
+ const raw = process.env[name];
17
+ if (raw === undefined || raw === "")
18
+ return def;
19
+ const n = Number(raw);
20
+ return Number.isFinite(n) ? n : def;
21
+ }
22
+ // nomic-embed-text's usable embed context is 2048 tokens. The old 8000-char cap
23
+ // assumed ~4 chars/token, but dense emoji/markup content runs richer, so a
24
+ // section that passed the char cap still errored with "input length exceeds the
25
+ // context length". Size the default against a conservative 3 chars/token worst
26
+ // case, reserving a small token margin for the tokenizer's specials, so a capped
27
+ // input is provably under context: (2048 - 48) * 3 = 6000. Context is
28
+ // env-overridable so raising it raises the cap; EMBED_INPUT_MAX_CHARS is also
29
+ // directly overridable.
30
+ const EMBED_MODEL_CONTEXT_TOKENS = Math.max(1, Math.floor(envInt("EMBED_MODEL_CONTEXT_TOKENS", 2048)));
31
+ const EMBED_CONTEXT_MARGIN_TOKENS = 48;
32
+ const EMBED_WORST_CASE_CHARS_PER_TOKEN = 3;
33
+ exports.EMBED_INPUT_MAX_CHARS = Math.max(1, Math.floor(envInt("EMBED_INPUT_MAX_CHARS", (EMBED_MODEL_CONTEXT_TOKENS - EMBED_CONTEXT_MARGIN_TOKENS) *
34
+ EMBED_WORST_CASE_CHARS_PER_TOKEN)));
19
35
  // Batch pacing (Task 960). On a passively-cooled Pi, embedding a whole
20
36
  // document's sections in one `/api/embed` POST pins ollama's llama-server
21
37
  // across every core for the entire batch with no idle gaps — the SoC climbs
@@ -27,15 +43,39 @@ exports.EMBED_INPUT_MAX_CHARS = 8000;
27
43
  // this pacing is defence-in-depth and faster recovery. Both knobs are
28
44
  // env-overridable so an operator can tune them from on-device thermal
29
45
  // measurement without a code change.
30
- function envInt(name, def) {
31
- const raw = process.env[name];
32
- if (raw === undefined || raw === "")
33
- return def;
34
- const n = Number(raw);
35
- return Number.isFinite(n) ? n : def;
36
- }
37
46
  exports.EMBED_SUBBATCH_SIZE = Math.max(1, Math.floor(envInt("EMBED_SUBBATCH_SIZE", 32)));
38
47
  exports.EMBED_SUBBATCH_COOLDOWN_MS = Math.max(0, envInt("EMBED_SUBBATCH_COOLDOWN_MS", 1000));
48
+ // Throughput-aware sub-batch sizing (Task 1533). On a CPU-only embedder each
49
+ // input takes ~EMBED_MS_PER_INPUT ms; a fixed 32-input POST at the char cap is
50
+ // ~128 s, past the 120 s request timeout, so a full-document batch aborts and
51
+ // the atomic ingest write lands nothing. Bound each POST to 80% of the timeout
52
+ // so it provably completes: derive a duration ceiling and take the smaller of
53
+ // it and the count ceiling (EMBED_SUBBATCH_SIZE, Task 960's thermal cap).
54
+ // EMBED_MS_PER_INPUT is the budgeted worst-case wall-time for one input at the
55
+ // EMBED_INPUT_MAX_CHARS cap on the measured device; env-tunable for other
56
+ // hardware.
57
+ exports.EMBED_MS_PER_INPUT = Math.max(1, Math.floor(envInt("EMBED_MS_PER_INPUT", 6000)));
58
+ const EMBED_TIMEOUT_UTILISATION = 0.8;
59
+ // Explicit per-request timeout, well under undici's 300000ms headers default.
60
+ // Without it a slow or wedged ollama returns no headers and the caller hangs a
61
+ // silent 5 minutes before a bare `fetch failed`. With it the request aborts in
62
+ // seconds and throws a cause-naming error instead. Env-overridable for
63
+ // on-device tuning.
64
+ exports.EMBED_REQUEST_TIMEOUT_MS = Math.max(1, Math.floor(envInt("EMBED_REQUEST_TIMEOUT_MS", 120000)));
65
+ // Model warmth (Task 1533). Keep the embed model resident so an interactive
66
+ // graph query-embed never cold-loads (~10 s) and blows its 5 s timeout, and so
67
+ // a query issued while an ingest runs embeds warm instead of degrading to BM25.
68
+ // Ollama's keep_alive accepts a number (seconds; -1 = keep forever) or a
69
+ // duration string ("30m"). Pass a numeric value through as a number so "-1" is
70
+ // the keep-forever sentinel rather than an unparseable duration string.
71
+ // Treat an empty-string env as unset (matches envInt above), so
72
+ // `EMBED_KEEP_ALIVE=` does not send an empty keep_alive that Ollama rejects.
73
+ const EMBED_KEEP_ALIVE_RAW = process.env.EMBED_KEEP_ALIVE === undefined || process.env.EMBED_KEEP_ALIVE === ""
74
+ ? "-1"
75
+ : process.env.EMBED_KEEP_ALIVE;
76
+ exports.EMBED_KEEP_ALIVE = /^-?\d+$/.test(EMBED_KEEP_ALIVE_RAW)
77
+ ? Number(EMBED_KEEP_ALIVE_RAW)
78
+ : EMBED_KEEP_ALIVE_RAW;
39
79
  function capInput(text) {
40
80
  if (text.length <= exports.EMBED_INPUT_MAX_CHARS)
41
81
  return text;
@@ -43,49 +83,87 @@ function capInput(text) {
43
83
  return text.slice(0, exports.EMBED_INPUT_MAX_CHARS);
44
84
  }
45
85
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
46
- // POST one already-capped sub-batch of inputs to the embed endpoint.
47
- async function postBatch(inputs) {
48
- const res = await fetch(`${OLLAMA_URL}/api/embed`, {
49
- method: "POST",
50
- headers: { "Content-Type": "application/json" },
51
- body: JSON.stringify({ model: EMBED_MODEL, input: inputs }),
52
- });
86
+ // One POST to the embed endpoint for an already-capped input (a single string
87
+ // for embed(), an array for a sub-batch). Both paths route through here so the
88
+ // timeout and the cause-naming error apply on every embed call. The wire shape
89
+ // of `input` is preserved (string vs array) so the ollama request is unchanged.
90
+ // On timeout or failure the error's first line names the cause (timeout vs HTTP
91
+ // status), the input count, and the largest input size, so a slow or failing
92
+ // ollama is legible immediately rather than after a 5-minute silent hang.
93
+ async function embedRequest(input) {
94
+ const inputs = Array.isArray(input) ? input : [input];
95
+ const maxChars = inputs.reduce((m, s) => Math.max(m, s.length), 0);
96
+ const target = `${OLLAMA_URL}/api/embed`;
97
+ const startedAt = Date.now();
98
+ let res;
99
+ try {
100
+ res = await fetch(target, {
101
+ method: "POST",
102
+ headers: { "Content-Type": "application/json" },
103
+ body: JSON.stringify({ model: EMBED_MODEL, input, keep_alive: exports.EMBED_KEEP_ALIVE }),
104
+ signal: AbortSignal.timeout(exports.EMBED_REQUEST_TIMEOUT_MS),
105
+ });
106
+ }
107
+ catch (err) {
108
+ const name = err instanceof Error ? err.name : "";
109
+ if (name === "TimeoutError" || name === "AbortError") {
110
+ process.stderr.write(`[embed] op=timeout ms=${exports.EMBED_REQUEST_TIMEOUT_MS} inputs=${inputs.length} maxChars=${maxChars}\n`);
111
+ throw new Error(`Ollama embed request timed out after ${exports.EMBED_REQUEST_TIMEOUT_MS}ms (inputs=${inputs.length}, largestInputChars=${maxChars}) at ${target}; ollama did not respond in time (slow, cold-loading, or CPU-saturated). Raise EMBED_REQUEST_TIMEOUT_MS or check ollama.`);
112
+ }
113
+ // undici throws TypeError("fetch failed", { cause }) on a connection error
114
+ // (ollama down, refused, DNS). The legible cause is on err.cause, not the
115
+ // "fetch failed" message, so name it so "network is down" reads distinctly
116
+ // from "ollama is slow".
117
+ let detail = err instanceof Error ? err.message : String(err);
118
+ const cause = err instanceof Error ? err.cause : undefined;
119
+ if (cause !== undefined) {
120
+ detail += `: ${cause instanceof Error ? cause.message : String(cause)}`;
121
+ }
122
+ throw new Error(`Ollama embed request failed (inputs=${inputs.length}, largestInputChars=${maxChars}) at ${target}: ${detail}`);
123
+ }
53
124
  if (!res.ok) {
54
125
  const body = await res.text();
55
- throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
126
+ throw new Error(`Ollama embed request failed (HTTP ${res.status}, inputs=${inputs.length}, largestInputChars=${maxChars}): ${body}`);
56
127
  }
57
128
  const data = (await res.json());
129
+ // Per-POST breadcrumb (Task 1533). One line per successful POST so an
130
+ // oversized or slow sub-batch is visible before it times out and the
131
+ // throughput-aware sizing is verifiable from the log. inputs is the POST's
132
+ // sub-batch size; maxChars its largest input.
133
+ process.stderr.write(`[embed] op=post inputs=${inputs.length} maxChars=${maxChars} ms=${Date.now() - startedAt}\n`);
58
134
  return data.embeddings;
59
135
  }
136
+ // POST one already-capped sub-batch of inputs to the embed endpoint.
137
+ async function postBatch(inputs) {
138
+ return embedRequest(inputs);
139
+ }
60
140
  async function embed(text) {
61
- const input = capInput(text);
62
- const res = await fetch(`${OLLAMA_URL}/api/embed`, {
63
- method: "POST",
64
- headers: { "Content-Type": "application/json" },
65
- body: JSON.stringify({ model: EMBED_MODEL, input }),
66
- });
67
- if (!res.ok) {
68
- const body = await res.text();
69
- throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
70
- }
71
- const data = (await res.json());
72
- return data.embeddings[0];
141
+ const data = await embedRequest(capInput(text));
142
+ return data[0];
143
+ }
144
+ // The POST size actually used by embedBatch: the smaller of the count ceiling
145
+ // (thermal, Task 960) and the duration ceiling (throughput, Task 1533). At
146
+ // defaults: min(32, floor(120000*0.8/6000)) = min(32, 16) = 16.
147
+ function effectiveSubBatchSize() {
148
+ const durationCap = Math.max(1, Math.floor((exports.EMBED_REQUEST_TIMEOUT_MS * EMBED_TIMEOUT_UTILISATION) / exports.EMBED_MS_PER_INPUT));
149
+ return Math.min(exports.EMBED_SUBBATCH_SIZE, durationCap);
73
150
  }
74
151
  async function embedBatch(texts) {
75
152
  const inputs = texts.map(capInput);
153
+ const subBatch = effectiveSubBatchSize();
76
154
  // Common case — an array within the sub-batch size is one POST, unchanged.
77
- if (inputs.length <= exports.EMBED_SUBBATCH_SIZE) {
155
+ if (inputs.length <= subBatch) {
78
156
  return postBatch(inputs);
79
157
  }
80
158
  // Oversized: split into sub-batches, cooling between each (not after the
81
159
  // last). Vectors are concatenated in input order so callers can keep their
82
160
  // index-aligned `embeddings[i]` ↔ `texts[i]` contract.
83
161
  const out = [];
84
- for (let i = 0; i < inputs.length; i += exports.EMBED_SUBBATCH_SIZE) {
162
+ for (let i = 0; i < inputs.length; i += subBatch) {
85
163
  if (i > 0 && exports.EMBED_SUBBATCH_COOLDOWN_MS > 0) {
86
164
  await sleep(exports.EMBED_SUBBATCH_COOLDOWN_MS);
87
165
  }
88
- const chunk = inputs.slice(i, i + exports.EMBED_SUBBATCH_SIZE);
166
+ const chunk = inputs.slice(i, i + subBatch);
89
167
  out.push(...(await postBatch(chunk)));
90
168
  }
91
169
  return out;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -8,11 +8,34 @@
8
8
  const OLLAMA_URL = process.env.OLLAMA_URL ?? "http://localhost:11434";
9
9
  const EMBED_MODEL = process.env.EMBED_MODEL ?? "nomic-embed-text";
10
10
 
11
- // nomic-embed-text has a 2048-token context (~8k chars at ~4 chars/token).
12
- // An over-limit input makes the Ollama embed endpoint error; cap defensively so
13
- // an oversized body embeds (truncated) instead of throwing and aborting the
14
- // caller.
15
- export const EMBED_INPUT_MAX_CHARS = 8000;
11
+ function envInt(name: string, def: number): number {
12
+ const raw = process.env[name];
13
+ if (raw === undefined || raw === "") return def;
14
+ const n = Number(raw);
15
+ return Number.isFinite(n) ? n : def;
16
+ }
17
+
18
+ // nomic-embed-text's usable embed context is 2048 tokens. The old 8000-char cap
19
+ // assumed ~4 chars/token, but dense emoji/markup content runs richer, so a
20
+ // section that passed the char cap still errored with "input length exceeds the
21
+ // context length". Size the default against a conservative 3 chars/token worst
22
+ // case, reserving a small token margin for the tokenizer's specials, so a capped
23
+ // input is provably under context: (2048 - 48) * 3 = 6000. Context is
24
+ // env-overridable so raising it raises the cap; EMBED_INPUT_MAX_CHARS is also
25
+ // directly overridable.
26
+ const EMBED_MODEL_CONTEXT_TOKENS = Math.max(1, Math.floor(envInt("EMBED_MODEL_CONTEXT_TOKENS", 2048)));
27
+ const EMBED_CONTEXT_MARGIN_TOKENS = 48;
28
+ const EMBED_WORST_CASE_CHARS_PER_TOKEN = 3;
29
+ export const EMBED_INPUT_MAX_CHARS = Math.max(
30
+ 1,
31
+ Math.floor(
32
+ envInt(
33
+ "EMBED_INPUT_MAX_CHARS",
34
+ (EMBED_MODEL_CONTEXT_TOKENS - EMBED_CONTEXT_MARGIN_TOKENS) *
35
+ EMBED_WORST_CASE_CHARS_PER_TOKEN,
36
+ ),
37
+ ),
38
+ );
16
39
 
17
40
  // Batch pacing (Task 960). On a passively-cooled Pi, embedding a whole
18
41
  // document's sections in one `/api/embed` POST pins ollama's llama-server
@@ -25,18 +48,50 @@ export const EMBED_INPUT_MAX_CHARS = 8000;
25
48
  // this pacing is defence-in-depth and faster recovery. Both knobs are
26
49
  // env-overridable so an operator can tune them from on-device thermal
27
50
  // measurement without a code change.
28
- function envInt(name: string, def: number): number {
29
- const raw = process.env[name];
30
- if (raw === undefined || raw === "") return def;
31
- const n = Number(raw);
32
- return Number.isFinite(n) ? n : def;
33
- }
34
51
  export const EMBED_SUBBATCH_SIZE = Math.max(1, Math.floor(envInt("EMBED_SUBBATCH_SIZE", 32)));
35
52
  export const EMBED_SUBBATCH_COOLDOWN_MS = Math.max(
36
53
  0,
37
54
  envInt("EMBED_SUBBATCH_COOLDOWN_MS", 1000),
38
55
  );
39
56
 
57
+ // Throughput-aware sub-batch sizing (Task 1533). On a CPU-only embedder each
58
+ // input takes ~EMBED_MS_PER_INPUT ms; a fixed 32-input POST at the char cap is
59
+ // ~128 s, past the 120 s request timeout, so a full-document batch aborts and
60
+ // the atomic ingest write lands nothing. Bound each POST to 80% of the timeout
61
+ // so it provably completes: derive a duration ceiling and take the smaller of
62
+ // it and the count ceiling (EMBED_SUBBATCH_SIZE, Task 960's thermal cap).
63
+ // EMBED_MS_PER_INPUT is the budgeted worst-case wall-time for one input at the
64
+ // EMBED_INPUT_MAX_CHARS cap on the measured device; env-tunable for other
65
+ // hardware.
66
+ export const EMBED_MS_PER_INPUT = Math.max(1, Math.floor(envInt("EMBED_MS_PER_INPUT", 6000)));
67
+ const EMBED_TIMEOUT_UTILISATION = 0.8;
68
+
69
+ // Explicit per-request timeout, well under undici's 300000ms headers default.
70
+ // Without it a slow or wedged ollama returns no headers and the caller hangs a
71
+ // silent 5 minutes before a bare `fetch failed`. With it the request aborts in
72
+ // seconds and throws a cause-naming error instead. Env-overridable for
73
+ // on-device tuning.
74
+ export const EMBED_REQUEST_TIMEOUT_MS = Math.max(
75
+ 1,
76
+ Math.floor(envInt("EMBED_REQUEST_TIMEOUT_MS", 120000)),
77
+ );
78
+
79
+ // Model warmth (Task 1533). Keep the embed model resident so an interactive
80
+ // graph query-embed never cold-loads (~10 s) and blows its 5 s timeout, and so
81
+ // a query issued while an ingest runs embeds warm instead of degrading to BM25.
82
+ // Ollama's keep_alive accepts a number (seconds; -1 = keep forever) or a
83
+ // duration string ("30m"). Pass a numeric value through as a number so "-1" is
84
+ // the keep-forever sentinel rather than an unparseable duration string.
85
+ // Treat an empty-string env as unset (matches envInt above), so
86
+ // `EMBED_KEEP_ALIVE=` does not send an empty keep_alive that Ollama rejects.
87
+ const EMBED_KEEP_ALIVE_RAW =
88
+ process.env.EMBED_KEEP_ALIVE === undefined || process.env.EMBED_KEEP_ALIVE === ""
89
+ ? "-1"
90
+ : process.env.EMBED_KEEP_ALIVE;
91
+ export const EMBED_KEEP_ALIVE: number | string = /^-?\d+$/.test(EMBED_KEEP_ALIVE_RAW)
92
+ ? Number(EMBED_KEEP_ALIVE_RAW)
93
+ : EMBED_KEEP_ALIVE_RAW;
94
+
40
95
  function capInput(text: string): string {
41
96
  if (text.length <= EMBED_INPUT_MAX_CHARS) return text;
42
97
  process.stderr.write(
@@ -48,41 +103,93 @@ function capInput(text: string): string {
48
103
  const sleep = (ms: number): Promise<void> =>
49
104
  new Promise((resolve) => setTimeout(resolve, ms));
50
105
 
51
- // POST one already-capped sub-batch of inputs to the embed endpoint.
52
- async function postBatch(inputs: string[]): Promise<number[][]> {
53
- const res = await fetch(`${OLLAMA_URL}/api/embed`, {
54
- method: "POST",
55
- headers: { "Content-Type": "application/json" },
56
- body: JSON.stringify({ model: EMBED_MODEL, input: inputs }),
57
- });
106
+ // One POST to the embed endpoint for an already-capped input (a single string
107
+ // for embed(), an array for a sub-batch). Both paths route through here so the
108
+ // timeout and the cause-naming error apply on every embed call. The wire shape
109
+ // of `input` is preserved (string vs array) so the ollama request is unchanged.
110
+ // On timeout or failure the error's first line names the cause (timeout vs HTTP
111
+ // status), the input count, and the largest input size, so a slow or failing
112
+ // ollama is legible immediately rather than after a 5-minute silent hang.
113
+ async function embedRequest(input: string | string[]): Promise<number[][]> {
114
+ const inputs = Array.isArray(input) ? input : [input];
115
+ const maxChars = inputs.reduce((m, s) => Math.max(m, s.length), 0);
116
+ const target = `${OLLAMA_URL}/api/embed`;
117
+ const startedAt = Date.now();
118
+ let res: Response;
119
+ try {
120
+ res = await fetch(target, {
121
+ method: "POST",
122
+ headers: { "Content-Type": "application/json" },
123
+ body: JSON.stringify({ model: EMBED_MODEL, input, keep_alive: EMBED_KEEP_ALIVE }),
124
+ signal: AbortSignal.timeout(EMBED_REQUEST_TIMEOUT_MS),
125
+ });
126
+ } catch (err) {
127
+ const name = err instanceof Error ? err.name : "";
128
+ if (name === "TimeoutError" || name === "AbortError") {
129
+ process.stderr.write(
130
+ `[embed] op=timeout ms=${EMBED_REQUEST_TIMEOUT_MS} inputs=${inputs.length} maxChars=${maxChars}\n`,
131
+ );
132
+ throw new Error(
133
+ `Ollama embed request timed out after ${EMBED_REQUEST_TIMEOUT_MS}ms (inputs=${inputs.length}, largestInputChars=${maxChars}) at ${target}; ollama did not respond in time (slow, cold-loading, or CPU-saturated). Raise EMBED_REQUEST_TIMEOUT_MS or check ollama.`,
134
+ );
135
+ }
136
+ // undici throws TypeError("fetch failed", { cause }) on a connection error
137
+ // (ollama down, refused, DNS). The legible cause is on err.cause, not the
138
+ // "fetch failed" message, so name it so "network is down" reads distinctly
139
+ // from "ollama is slow".
140
+ let detail = err instanceof Error ? err.message : String(err);
141
+ const cause = err instanceof Error ? (err as { cause?: unknown }).cause : undefined;
142
+ if (cause !== undefined) {
143
+ detail += `: ${cause instanceof Error ? cause.message : String(cause)}`;
144
+ }
145
+ throw new Error(
146
+ `Ollama embed request failed (inputs=${inputs.length}, largestInputChars=${maxChars}) at ${target}: ${detail}`,
147
+ );
148
+ }
58
149
  if (!res.ok) {
59
150
  const body = await res.text();
60
- throw new Error(`Ollama batch embedding failed (${res.status}): ${body}`);
151
+ throw new Error(
152
+ `Ollama embed request failed (HTTP ${res.status}, inputs=${inputs.length}, largestInputChars=${maxChars}): ${body}`,
153
+ );
61
154
  }
62
155
  const data = (await res.json()) as { embeddings: number[][] };
156
+ // Per-POST breadcrumb (Task 1533). One line per successful POST so an
157
+ // oversized or slow sub-batch is visible before it times out and the
158
+ // throughput-aware sizing is verifiable from the log. inputs is the POST's
159
+ // sub-batch size; maxChars its largest input.
160
+ process.stderr.write(
161
+ `[embed] op=post inputs=${inputs.length} maxChars=${maxChars} ms=${Date.now() - startedAt}\n`,
162
+ );
63
163
  return data.embeddings;
64
164
  }
65
165
 
166
+ // POST one already-capped sub-batch of inputs to the embed endpoint.
167
+ async function postBatch(inputs: string[]): Promise<number[][]> {
168
+ return embedRequest(inputs);
169
+ }
170
+
66
171
  export async function embed(text: string): Promise<number[]> {
67
- const input = capInput(text);
68
- const res = await fetch(`${OLLAMA_URL}/api/embed`, {
69
- method: "POST",
70
- headers: { "Content-Type": "application/json" },
71
- body: JSON.stringify({ model: EMBED_MODEL, input }),
72
- });
73
- if (!res.ok) {
74
- const body = await res.text();
75
- throw new Error(`Ollama embedding failed (${res.status}): ${body}`);
76
- }
77
- const data = (await res.json()) as { embeddings: number[][] };
78
- return data.embeddings[0];
172
+ const data = await embedRequest(capInput(text));
173
+ return data[0];
174
+ }
175
+
176
+ // The POST size actually used by embedBatch: the smaller of the count ceiling
177
+ // (thermal, Task 960) and the duration ceiling (throughput, Task 1533). At
178
+ // defaults: min(32, floor(120000*0.8/6000)) = min(32, 16) = 16.
179
+ export function effectiveSubBatchSize(): number {
180
+ const durationCap = Math.max(
181
+ 1,
182
+ Math.floor((EMBED_REQUEST_TIMEOUT_MS * EMBED_TIMEOUT_UTILISATION) / EMBED_MS_PER_INPUT),
183
+ );
184
+ return Math.min(EMBED_SUBBATCH_SIZE, durationCap);
79
185
  }
80
186
 
81
187
  export async function embedBatch(texts: string[]): Promise<number[][]> {
82
188
  const inputs = texts.map(capInput);
189
+ const subBatch = effectiveSubBatchSize();
83
190
 
84
191
  // Common case — an array within the sub-batch size is one POST, unchanged.
85
- if (inputs.length <= EMBED_SUBBATCH_SIZE) {
192
+ if (inputs.length <= subBatch) {
86
193
  return postBatch(inputs);
87
194
  }
88
195
 
@@ -90,11 +197,11 @@ export async function embedBatch(texts: string[]): Promise<number[][]> {
90
197
  // last). Vectors are concatenated in input order so callers can keep their
91
198
  // index-aligned `embeddings[i]` ↔ `texts[i]` contract.
92
199
  const out: number[][] = [];
93
- for (let i = 0; i < inputs.length; i += EMBED_SUBBATCH_SIZE) {
200
+ for (let i = 0; i < inputs.length; i += subBatch) {
94
201
  if (i > 0 && EMBED_SUBBATCH_COOLDOWN_MS > 0) {
95
202
  await sleep(EMBED_SUBBATCH_COOLDOWN_MS);
96
203
  }
97
- const chunk = inputs.slice(i, i + EMBED_SUBBATCH_SIZE);
204
+ const chunk = inputs.slice(i, i + subBatch);
98
205
  out.push(...(await postBatch(chunk)));
99
206
  }
100
207
  return out;
@@ -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:a557577a5292537ca408682f87fd5ab625194fc595602a4f2e886088d1ae40c1
4
+ content-hash: sha256:a90edd8c9297c7c033814a6b4892c5d2550ffbda700d27f74a6ee58e70101af2
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -554,6 +554,72 @@ Omit the target and each tool acts on the session's own account, exactly like th
554
554
 
555
555
  A standing background check reconciles live sessions against the account roles and raises an alarm if any session still holds house authority for an account that is no longer the house account, so a stale grant cannot sit unnoticed. Each cross-account tool call also records its decision — who called, which account it targeted, whether it was allowed, and why — before it touches the graph or disk, and a rejected filesystem path escape is recorded on its own line. In normal operation neither an allowed cross-account call from a non-house session nor a path escape ever appears.
556
556
 
557
+ ---
558
+ # Workflows Guide
559
+ Source: https://docs.getmaxy.com/workflows.md
560
+
561
+ # Workflows Guide
562
+
563
+ ## What a Workflow Is
564
+
565
+ A workflow is a saved, named sequence of steps that Maxy runs for you on demand. Each step calls one tool, and steps can pass their results to later steps. Use a workflow when you have a recurring process with two or more steps that you would otherwise ask for by hand every time. A single one-off action does not need a workflow.
566
+
567
+ Examples of good workflows:
568
+
569
+ - "When a new lead comes in, create a contact, log it, and send me a notification."
570
+ - "Every time I book a viewing, add the calendar event and message the client the details."
571
+
572
+ ## Creating a Workflow
573
+
574
+ Describe the process to Maxy naturally:
575
+
576
+ - "Set up a workflow: when I get a new lead, create the contact and send a Telegram notification."
577
+ - "Make a follow-up workflow that adds a calendar reminder and drafts an email."
578
+
579
+ Maxy will confirm the name, the steps, and how data flows between them before saving. If a step needs a tool from a plugin you have not installed yet, the workflow is saved as a draft and activates automatically once that plugin is in place.
580
+
581
+ ## Declared Inputs
582
+
583
+ Most workflows need a few values that are specific to each run, for example a customer name or a chosen option. A workflow can declare these up front as its inputs. Declaring them means Maxy asks you the same questions every time it runs the workflow, so nothing is forgotten and nothing is guessed.
584
+
585
+ Each declared input has a question to ask you, a type, whether it is required, and an optional default. When a value has a default, Maxy offers it and you can accept or change it.
586
+
587
+ You do not manage inputs by hand. When you describe a workflow that clearly needs a per-run value, Maxy proposes the input and confirms it with you. At run time it reads the list and asks you each question before starting.
588
+
589
+ ## Running a Workflow
590
+
591
+ Ask Maxy to run it:
592
+
593
+ - "Run the new-lead workflow."
594
+ - "Start the viewing follow-up for the Cardinal Roofing booking."
595
+
596
+ Before it starts, Maxy gathers every required input by asking you. If you leave a required value out, the workflow does not start and no steps run. Maxy tells you which value is missing and asks for it, then runs the workflow with the complete set. This is deliberate: the workflow refuses to run half-finished rather than failing partway through.
597
+
598
+ ## Seeing What Ran
599
+
600
+ Ask for the history:
601
+
602
+ - "Show me recent workflow runs."
603
+ - "Did the new-lead workflow run today?"
604
+
605
+ You get each run's status, timing, and how many steps completed, were skipped, or failed. If a step failed, Maxy can show you which one and why.
606
+
607
+ ## Managing Workflows
608
+
609
+ Tell Maxy naturally:
610
+
611
+ - "List my workflows."
612
+ - "Show me the follow-up workflow."
613
+ - "Pause the new-lead workflow." or "Turn the new-lead workflow back on."
614
+ - "Change the follow-up workflow so it also messages me."
615
+ - "Delete the old onboarding workflow."
616
+
617
+ Pausing keeps a workflow but stops it running. Editing replaces the steps or the inputs with the new version you describe. Deleting removes the workflow and its run history, so Maxy confirms with you first.
618
+
619
+ ## Scheduling a Workflow
620
+
621
+ A workflow can run on a schedule instead of on demand. Tell Maxy when you want it to run, for example "run the daily summary workflow every morning at eight," and it sets up the schedule. Scheduled runs happen with no one present, so a workflow that needs you to supply inputs is best run on demand rather than scheduled.
622
+
557
623
  ---
558
624
  # Install Overview
559
625
  Source: https://docs.getmaxy.com/install.md
@@ -2690,6 +2756,8 @@ either is a regression.
2690
2756
 
2691
2757
  **`/chat` Claude-desktop transcript presentation.** The admin webchat keeps the shared `Transcript` shell (stream, follow-tail) but injects a /chat-only item renderer via the component's optional `renderItems` prop: `renderChatTimeline` in `app/chat/transcript-render.tsx`. Presentation: operator turns render as a right-aligned grey bubble showing the message text and, beneath it, a subtle always-visible time-of-day stamp (no label); delivered agent replies render as plain prose with the same time-of-day stamp beneath (the reply-document filename line stays, as prose); the stamp is HH:MM from the turn's `ts` (locale-formatted, right-aligned inside the operator bubble, left-aligned under agent prose), and a turn whose `ts` is null/unparseable shows no stamp, never an empty line — so only delivered operator and agent-reply turns are stamped, while tool runs, the collapsed "Thinking" block, and the agent-error banner stay time-free; a maximal consecutive run of `tool-call`/`tool-result` turns renders as one collapsed grey one-liner ("Used N tools ›") whose expansion shows every call and result payload — a lone call still gets the one-liner, and prose or a directive row ends a run. The WhatsApp/operator reader (`/whatsapp`, `OperatorConversations`) omits the prop and keeps the default `renderTimeline`, so its bubble/DM chrome is unchanged — except that since the attachment-render extraction the default renderer draws inbound `operator-inbound` uploads and `agent-file` deliveries inline (image thumbnail / pdf.js preview / audio player / download chip) via the attachment renderer shared with `/chat`, extracted to `app/chat/attachment-render.tsx` to break the cycle with `transcript-render.tsx`; `renderChatTimeline` is untouched. Both presentations are test-pinned (`app/whatsapp/__tests__/Transcript-*.test.tsx`, `app/chat/__tests__/transcript-render.test.tsx`, `app/chat/__tests__/pdf-preview.test.tsx`). **Day-divider:** both renderers insert a centered `.day-divider` row between two consecutive timeline items on different local calendar days (label `Today`/`Yesterday`, else `Sat 14 Jun 2026`), so a thread spanning midnight is never an ambiguous run of HH:MM; the first dated item gets a leading divider, a null/unparseable `ts` marks no boundary, dividers are computed over the filtered `visibleItems`, and in `/chat` a day crossover flushes any open tool/think run first so a collapsed run never spans it. Per-bubble HH:MM stamps are unchanged. Shared helpers `dayKey`/`dayLabel`/`itemTs`/`DayDivider` live in `app/whatsapp/Transcript.tsx`.
2692
2758
 
2759
+ **WhatsApp reader is read/write (WhatsApp-Web surface).** A WhatsApp store conversation in the operator Conversations reader (`OperatorConversations`) and the admin root channel viewer (`app/page.tsx`) no longer opens the plain read-only DM chrome — it opens `app/whatsapp/WhatsappWebConversation.tsx`, a WhatsApp-Web-styled surface: a conversation header, the delivered-only store transcript reskinned green/white under a `.wa-web` root class, and a reply composer (`app/whatsapp/ReplyComposer.tsx`). The operator can type text and attach files (the webchat allowlist: `SUPPORTED_MIME_SET` / 50 MB / 5 files) and send them into that WhatsApp conversation. **Send is reply-only and DM-only:** `POST /api/whatsapp-reader/reply` (`requireAdminSession`) refuses any target with no existing stored conversation (`reason=no-existing-conversation`, HTTP 403) — a cold first-contact send bans the account, so the composer is never a cold-contact path, enforced server-side — and refuses `@g.us` groups (`reason=group-send-blocked`), matching the agent's observe-only group invariant; a group thread shows a read-only note in place of the composer. The endpoint is the sole store writer for a manual send: right after each send resolves it appends the record itself, stamped `origin:'operator-manual'` (attributed "You", distinct from an assistant reply), because the Baileys echo that normally persists the store is gated on message text (`manager.ts:906`) and would leave an uncaptioned media send delivered-but-invisible. The record's `messageId` matches the echo's derivation so the async text echo's own persist dedups to a no-op; the send is confirmed by re-read (`op=recorded present=<bool>`). A sent send does not spawn an assistant turn (echo dispatch stays suppressed). Attachments deliver as WhatsApp media/documents and appear in the reader as an attributed text line (`[sent <file>]`) — inline media rendering in the store reader is a follow-up. The `.wa-web` skin is scoped to WhatsApp store threads only, so `/chat`, the admin agent session view, and the public visitor webchat are unchanged; the visitor webchat gains no send capability. The header call/video/search icons and the in-bubble delivered-tick are cosmetic (no presence or read-receipt data is stored); inbound bubbles keep their sender name so group threads stay attributable. Non-WhatsApp reader rows (Telegram/webchat session JSONLs) keep the plain read-only `Transcript`. Observability rides `[wa-manual-send]` lines correlated by a per-request `sendId`.
2760
+
2693
2761
  **`/chat` live activity line.** While a turn is in flight the transcript tail shows one ephemeral `ChatActivity` line (mounted only while `busy`, admin/operator only). It tracks real activity, not a timer: while a `Task` subagent runs it shows that subagent's headline (`agentType · description`, prefixed `N agents ·` when ≥2 are concurrent), sourced from `agent-<hex>.meta.json` via named `activity` SSE events the admin reader pushes from the session's `subagents/` dir; with no subagent active it shows a neutral word that advances only on a real turn arrival. The line carries a turn-elapsed clock and flips to a `stalled` state once nothing has been written for 5 minutes (`now − lastEmitAt`), so the operator can tell a wedge from progress without SSH. It is never added to the persisted timeline. Detail and the `[webchat-activity]` observability live in [`admin-webchat-native-channel.md`](../../../.docs/admin-webchat-native-channel.md).
2694
2762
 
2695
2763
  **maxy title for public sessions.** A `role=public` webchat spawn never produces a useful Claude Code `ai-title` (an anonymous one-line visitor turn), so every public row would otherwise read identically. The webchat route (`chat.ts`) composes a deterministic title — `Web · <senderId[:8]>[ · <personId>] · <UTC YYYY-MM-DD HH:mm>` (personId present only for gated visitors) — and threads it through the native webchat gateway's public spawn (`handleInbound` → `buildPublicWebchatSpawnRequest` → `managerSpawn`) to the manager `POST /public-spawn` body as `name`. `/spawn` validates it with `validateUserTitle` and, for `role=public` only, writes it into `UserTitleStore` so it occupies the operator-rename tier and wins over `ai-title`. Admin (`/rc-spawn`) and WhatsApp titling are unchanged. Observability: every public spawn logs `[spawn] role=public … title="…"` (or `title=missing`); the manager's row builder emits `[public-title] sessionId=… unexpected titleSource=<ai|null>` on the next list read for any public row that did not resolve from the user tier.
@@ -3498,7 +3566,7 @@ If Ollama is unavailable at write time, the node is stored without an embedding
3498
3566
 
3499
3567
  The `memory-reindex` tool backfills missing embeddings by iterating nodes where `embedding IS NULL`. It uses the bounded representation (the `summary` for document/archive labels, never the raw body), embeds in batches with a per-node fallback on batch error, and **skips and continues** past a failing node — keeping skipped nodes out of the re-fetch via an exclude set — instead of aborting the whole run. `embed()`/`embedBatch()` cap oversized input (`EMBED_INPUT_MAX_CHARS`) before the Ollama POST, so a large body embeds truncated rather than throwing. Per-node `[reindex] op=embedded|skip-error` lines and a terminal `[reindex] op=done processed=N skipped=M`.
3500
3568
 
3501
- `embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits an input array longer than `EMBED_SUBBATCH_SIZE` (default 32) into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. Both knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
3569
+ `embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits an input array longer than the effective sub-batch size into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. The effective sub-batch size is the smaller of two ceilings: the thermal count ceiling `EMBED_SUBBATCH_SIZE` (default 32) and a throughput ceiling `floor(EMBED_REQUEST_TIMEOUT_MS * 0.8 / EMBED_MS_PER_INPUT)`. The throughput ceiling exists because on a CPU-only embedder a fixed 32-input POST can exceed `EMBED_REQUEST_TIMEOUT_MS` and abort the atomic ingest write, so `EMBED_MS_PER_INPUT` (the budgeted worst-case wall-time for one capped input, default 6000 ms, env-tunable) bounds each POST to 80 % of the request timeout. Every `/api/embed` POST also carries `keep_alive` (`EMBED_KEEP_ALIVE`, default `-1` = keep the model resident forever) so an interactive graph query-embed never cold-loads and blows its 5 s timeout. All knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
3502
3570
 
3503
3571
  ---
3504
3572
 
@@ -3803,9 +3871,9 @@ Standing rules the operator gives over a channel are `Preference` nodes. Two mec
3803
3871
 
3804
3872
  **Reconcile by meaning on write (`profile-update`).** The existence check is two-tier. First an exact `(category, key)` match — the classic fast path, unchanged. When that misses, a semantic-neighbour lookup runs over the `preference_embedding` vector index, scoped to the same `accountId` and `scope:'admin'`, non-trashed. If the nearest neighbour is at or above `SAME_SUBJECT_COSINE` (0.85), the write lands on that node through the existing reinforce/update/contradict curve instead of minting a duplicate; the matched neighbours are returned in the tool result so the agent can classify future writes against real prior state. The confidence constants are unchanged — only the join key widens, so a rephrasing under a new `key` no longer forks the rule and a correction demotes the prior node rather than adding a contradictory one.
3805
3873
 
3806
- **Inject the active rule-set every turn (channel gateways).** The one definition of "this account's active rules" is `@maxy/active-rules`: a bounded, floor-filtered (`confidence >= 0.4`), confidence-ordered candidate query, then semantic de-confliction (one winner per subject at the same 0.85 cosine), capped to `INJECTION_RULE_CAP` (12). `profile-read` exposes this set as `activeRules`, and the three inbound gateways (`wa-gateway`, `telegram-gateway`, `webchat-gateway`) fetch it for the effective session account and attach it as `standingRules` on the inbound frame; the channel builders prepend it as a `## Standing rules` block above `## Context`. The read is by code, not agent discretion, so a rule set last week is applied this week regardless of how many times the session compacted. Telegram and webchat inject for `role:'admin'` only — a public visitor never receives operator rules.
3874
+ **Inject the active rule-set every turn (channel gateways).** The one definition of "this account's active rules" is `@maxy/active-rules`: a bounded, floor-filtered (`confidence >= 0.4`), confidence-ordered candidate query, then semantic de-confliction (one winner per subject at the same 0.85 cosine), capped to `INJECTION_RULE_CAP` (12). `profile-read` exposes this set as `activeRules`, and the three inbound gateways (`wa-gateway`, `telegram-gateway`, `webchat-gateway`) fetch it for the effective session account and attach it as `standingRules` on the inbound frame; the channel builders prepend it as a `## Standing rules` block above `## Context`. Both channel-server inbound paths carry the block: the single-target SSE loop parses the whole frame, and the unified multi-target door reader (`runDoorStream`,) reconstructs it through the shared `doorInboundPayload` seam whose `parseFrame` closures read `standingRules` off the frame. The read is by code, not agent discretion, so a rule set last week is applied this week regardless of how many times the session compacted. Telegram and webchat inject for `role:'admin'` only — a public visitor never receives operator rules.
3807
3875
 
3808
- **Trust a scheduled turn's provenance, never an inbound's claim (channel-agent grounding).** A cron-dispatched agent turn reaches the same channel builders as a real inbound, so its own authorisation text would otherwise land inside `## Context` — the zone `REFRAME_INSTRUCTION` demotes to untrusted — and a security-cautious agent would distrust its own automation (the self-cancel loop). The dispatcher therefore stamps a `ScheduleProvenance` marker (owning account, event, owner userId, dispatch time) that the channel builders render as a `## Scheduled automation (platform-verified)` preamble **above** `## Context` (and above the rules block); the whatsapp wrap tag names the door `scheduler`. Because the marker is dispatcher-stamped and never derived from payload text, an inbound message cannot forge it — placement above `## Context` alone is not the trust signal, the dispatcher provenance is. The grounding binds to it in two places: one recipient-neutral clause added byte-identical to the three reframe instructions (a marker-bearing turn is authorised standing automation; a reconfiguration or re-enable is not intrusion), and the admin `IDENTITY.md` "Untrusted input" rule that the agent never states a platform-level authenticity or impersonation verdict it was not given (an unsourced "this is not from you" is its own inference, flagged, never asserted as the platform's). The amplifier that let one misread harden was the accreting seat: a recurring event resumed one long-lived session every firing. A subsequent change removes it — each firing runs a fresh per-firing seat (`scheduledSeatIdFor`, salted with the firing's eventId + dispatch time), so distrust cannot accrete across firings, and the self-cancel guard compares the cancelling `SESSION_ID` to the node's dispatcher-stamped `lastDispatchSessionId` rather than a recomputed deterministic seat. See `.docs/scheduling-agent-dispatch.md § Operator-provenance marker` and `§ Session model — fresh per firing`.
3876
+ **Trust a scheduled turn's provenance, never an inbound's claim (channel-agent grounding).** A cron-dispatched agent turn reaches the same channel builders as a real inbound, so its own authorisation text would otherwise land inside `## Context` — the zone `REFRAME_INSTRUCTION` demotes to untrusted — and a security-cautious agent would distrust its own automation (the self-cancel loop). The dispatcher therefore stamps a `ScheduleProvenance` marker (owning account, event, owner userId, dispatch time) that the channel builders render as a `## Scheduled automation (platform-verified)` preamble **above** `## Context` (and above the rules block); the whatsapp wrap tag names the door `scheduler`. Both channel-server paths carry the marker — the single-target loop and the unified multi-target door reader (via the same `doorInboundPayload` seam that carries `standingRules`); the scheduler only dispatches to the whatsapp/telegram door, so a webchat frame never bears one. Because the marker is dispatcher-stamped and never derived from payload text, an inbound message cannot forge it — placement above `## Context` alone is not the trust signal, the dispatcher provenance is. The grounding binds to it in two places: one recipient-neutral clause added byte-identical to the three reframe instructions (a marker-bearing turn is authorised standing automation; a reconfiguration or re-enable is not intrusion), and the admin `IDENTITY.md` "Untrusted input" rule that the agent never states a platform-level authenticity or impersonation verdict it was not given (an unsourced "this is not from you" is its own inference, flagged, never asserted as the platform's). The amplifier that let one misread harden was the accreting seat: a recurring event resumed one long-lived session every firing. A subsequent change removes it — each firing runs a fresh per-firing seat (`scheduledSeatIdFor`, salted with the firing's eventId + dispatch time), so distrust cannot accrete across firings, and the self-cancel guard compares the cancelling `SESSION_ID` to the node's dispatcher-stamped `lastDispatchSessionId` rather than a recomputed deterministic seat. See `.docs/scheduling-agent-dispatch.md § Operator-provenance marker` and `§ Session model — fresh per firing`.
3809
3877
 
3810
3878
  **Observability.** `[preference-write] op=reconcile … mode=<reinforce|update|contradict|new> similarity=…` — a stream of `mode=new` for subjects that already exist is the duplicate-minting signature. `[preference-inject] op=inject channel=<wa|tg|web> injected=<N>` — `injected=0` on an account with stored rules is the write-only regression re-appearing. `[preference-audit] op=drift accountId=… storedActive=… injectedActive=… duplicateClusters=…` — the standing reconciliation audit (claude-session-manager, 5-minute tick) flags any account whose store holds more active rows than injection would surface, the no-event detector for drift.
3811
3879