@rubytech/create-realagent-code 0.1.604 → 0.1.606

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 (256) hide show
  1. package/dist/__tests__/claude-trust-seed.test.js +125 -0
  2. package/dist/claude-trust-seed.js +52 -0
  3. package/dist/index.js +68 -33
  4. package/package.json +1 -1
  5. package/payload/platform/lib/routine-templates/dist/roster.d.ts +30 -2
  6. package/payload/platform/lib/routine-templates/dist/roster.d.ts.map +1 -1
  7. package/payload/platform/lib/routine-templates/dist/roster.js +76 -32
  8. package/payload/platform/lib/routine-templates/dist/roster.js.map +1 -1
  9. package/payload/platform/lib/routine-templates/src/__tests__/roster.test.ts +0 -0
  10. package/payload/platform/lib/routine-templates/src/__tests__/seed.test.ts +22 -1
  11. package/payload/platform/lib/routine-templates/src/roster.ts +108 -35
  12. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -3
  13. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
  14. package/payload/platform/plugins/connector/PLUGIN.md +9 -1
  15. package/payload/platform/plugins/connector/mcp/dist/__tests__/queue-descriptor.test.d.ts +2 -0
  16. package/payload/platform/plugins/connector/mcp/dist/__tests__/queue-descriptor.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/connector/mcp/dist/__tests__/queue-descriptor.test.js +67 -0
  18. package/payload/platform/plugins/connector/mcp/dist/__tests__/queue-descriptor.test.js.map +1 -0
  19. package/payload/platform/plugins/connector/mcp/dist/index.js +36 -5
  20. package/payload/platform/plugins/connector/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/connector/mcp/dist/lib/store.d.ts +35 -0
  22. package/payload/platform/plugins/connector/mcp/dist/lib/store.d.ts.map +1 -1
  23. package/payload/platform/plugins/connector/mcp/dist/lib/store.js +33 -0
  24. package/payload/platform/plugins/connector/mcp/dist/lib/store.js.map +1 -1
  25. package/payload/platform/plugins/connector/mcp/package.json +4 -2
  26. package/payload/platform/plugins/connector/mcp/vitest.config.ts +8 -0
  27. package/payload/platform/plugins/docs/references/admin-ui.md +15 -2
  28. package/payload/platform/plugins/docs/references/platform.md +2 -0
  29. package/payload/platform/plugins/email/PLUGIN.md +4 -0
  30. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/retention.test.d.ts +2 -0
  31. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/retention.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/retention.test.js +233 -0
  33. package/payload/platform/plugins/email/mcp/dist/sampler/__tests__/retention.test.js.map +1 -0
  34. package/payload/platform/plugins/email/mcp/dist/sampler/main.js +19 -0
  35. package/payload/platform/plugins/email/mcp/dist/sampler/main.js.map +1 -1
  36. package/payload/platform/plugins/email/mcp/dist/sampler/retention-sources.d.ts +3 -0
  37. package/payload/platform/plugins/email/mcp/dist/sampler/retention-sources.d.ts.map +1 -0
  38. package/payload/platform/plugins/email/mcp/dist/sampler/retention-sources.js +146 -0
  39. package/payload/platform/plugins/email/mcp/dist/sampler/retention-sources.js.map +1 -0
  40. package/payload/platform/plugins/email/mcp/dist/sampler/retention.d.ts +150 -0
  41. package/payload/platform/plugins/email/mcp/dist/sampler/retention.d.ts.map +1 -0
  42. package/payload/platform/plugins/email/mcp/dist/sampler/retention.js +199 -0
  43. package/payload/platform/plugins/email/mcp/dist/sampler/retention.js.map +1 -0
  44. package/payload/platform/plugins/scheduling/PLUGIN.md +8 -1
  45. package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
  46. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  47. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js +40 -0
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-gate-write.test.js.map +1 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js +322 -4
  50. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/scheduling-gate.test.js.map +1 -1
  51. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  52. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +21 -1
  53. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  54. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts +236 -6
  55. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.d.ts.map +1 -1
  56. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js +452 -7
  57. package/payload/platform/plugins/scheduling/mcp/dist/tools/scheduling-gate.js.map +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/claude-trust-census.d.ts +34 -0
  59. package/payload/platform/services/claude-session-manager/dist/claude-trust-census.d.ts.map +1 -0
  60. package/payload/platform/services/claude-session-manager/dist/claude-trust-census.js +83 -0
  61. package/payload/platform/services/claude-session-manager/dist/claude-trust-census.js.map +1 -0
  62. package/payload/platform/services/claude-session-manager/dist/index.js +13 -0
  63. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  64. package/payload/server/public/activity.html +6 -6
  65. package/payload/server/public/agents.html +5 -5
  66. package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js +1 -0
  67. package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.br +0 -0
  68. package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.gz +0 -0
  69. package/payload/server/public/assets/AdminShell-1HyATV_t.js +3 -0
  70. package/payload/server/public/assets/AdminShell-1HyATV_t.js.br +0 -0
  71. package/payload/server/public/assets/AdminShell-1HyATV_t.js.gz +0 -0
  72. package/payload/server/public/assets/activity-C2qWlcJ5.js +1 -0
  73. package/payload/server/public/assets/activity-C2qWlcJ5.js.br +0 -0
  74. package/payload/server/public/assets/activity-C2qWlcJ5.js.gz +0 -0
  75. package/payload/server/public/assets/admin-DqpfY8-t.js +1 -0
  76. package/payload/server/public/assets/admin-DqpfY8-t.js.br +0 -0
  77. package/payload/server/public/assets/admin-DqpfY8-t.js.gz +0 -0
  78. package/payload/server/public/assets/agents-DoGVAjIT.js +1 -0
  79. package/payload/server/public/assets/agents-DoGVAjIT.js.br +0 -0
  80. package/payload/server/public/assets/agents-DoGVAjIT.js.gz +0 -0
  81. package/payload/server/public/assets/{browser-CqDBcKwU.js → browser-DjqS897k.js} +1 -1
  82. package/payload/server/public/assets/browser-DjqS897k.js.br +0 -0
  83. package/payload/server/public/assets/browser-DjqS897k.js.gz +0 -0
  84. package/payload/server/public/assets/calendar-DJeOm0WS.js +1 -0
  85. package/payload/server/public/assets/calendar-DJeOm0WS.js.br +0 -0
  86. package/payload/server/public/assets/calendar-DJeOm0WS.js.gz +0 -0
  87. package/payload/server/public/assets/chat-BrRE0Dck.js +1 -0
  88. package/payload/server/public/assets/chat-BrRE0Dck.js.br +0 -0
  89. package/payload/server/public/assets/chat-BrRE0Dck.js.gz +0 -0
  90. package/payload/server/public/assets/chevron-left-D9z1aK3p.js +1 -0
  91. package/payload/server/public/assets/chevron-left-D9z1aK3p.js.br +0 -0
  92. package/payload/server/public/assets/clock-BqHWMICF.js +1 -0
  93. package/payload/server/public/assets/clock-BqHWMICF.js.br +0 -0
  94. package/payload/server/public/assets/clock-BqHWMICF.js.gz +0 -0
  95. package/payload/server/public/assets/copy-p9ZM9fnn.js +1 -0
  96. package/payload/server/public/assets/copy-p9ZM9fnn.js.br +0 -0
  97. package/payload/server/public/assets/copy-p9ZM9fnn.js.gz +0 -0
  98. package/payload/server/public/assets/data-CPsqW0qW.js +1 -0
  99. package/payload/server/public/assets/data-CPsqW0qW.js.br +0 -0
  100. package/payload/server/public/assets/data-CPsqW0qW.js.gz +0 -0
  101. package/payload/server/public/assets/{field-X4OVA3vx.js → field-WRk67lvr.js} +1 -1
  102. package/payload/server/public/assets/field-WRk67lvr.js.br +0 -0
  103. package/payload/server/public/assets/field-WRk67lvr.js.gz +0 -0
  104. package/payload/server/public/assets/file-text-BJDCb334.js +1 -0
  105. package/payload/server/public/assets/file-text-BJDCb334.js.br +0 -0
  106. package/payload/server/public/assets/file-text-BJDCb334.js.gz +0 -0
  107. package/payload/server/public/assets/{graph-l7tzHqvm.js → graph-CAU2xh2Z.js} +2 -2
  108. package/payload/server/public/assets/graph-CAU2xh2Z.js.br +0 -0
  109. package/payload/server/public/assets/graph-CAU2xh2Z.js.gz +0 -0
  110. package/payload/server/public/assets/graph-labels-CEDmX7qW.js +1 -0
  111. package/payload/server/public/assets/graph-labels-CEDmX7qW.js.br +0 -0
  112. package/payload/server/public/assets/graph-labels-CEDmX7qW.js.gz +0 -0
  113. package/payload/server/public/assets/{jsx-runtime-bq0g4NlA.css → jsx-runtime-hPFGo4rC.css} +1 -1
  114. package/payload/server/public/assets/jsx-runtime-hPFGo4rC.css.br +0 -0
  115. package/payload/server/public/assets/{jsx-runtime-bq0g4NlA.css.gz → jsx-runtime-hPFGo4rC.css.gz} +0 -0
  116. package/payload/server/public/assets/{operator-Dg-dwf7a.js → operator-1WFztEQA.js} +1 -1
  117. package/payload/server/public/assets/operator-1WFztEQA.js.br +0 -0
  118. package/payload/server/public/assets/operator-1WFztEQA.js.gz +0 -0
  119. package/payload/server/public/assets/page-B72XigcU.js +32 -0
  120. package/payload/server/public/assets/page-B72XigcU.js.br +0 -0
  121. package/payload/server/public/assets/page-B72XigcU.js.gz +0 -0
  122. package/payload/server/public/assets/page-nT8l_cX1.js +1 -0
  123. package/payload/server/public/assets/page-nT8l_cX1.js.br +0 -0
  124. package/payload/server/public/assets/page-nT8l_cX1.js.gz +0 -0
  125. package/payload/server/public/assets/play-CYqOCqm6.js +1 -0
  126. package/payload/server/public/assets/play-CYqOCqm6.js.br +0 -0
  127. package/payload/server/public/assets/play-CYqOCqm6.js.gz +0 -0
  128. package/payload/server/public/assets/public-DAea6w7w.js +1 -0
  129. package/payload/server/public/assets/public-DAea6w7w.js.br +0 -0
  130. package/payload/server/public/assets/public-DAea6w7w.js.gz +0 -0
  131. package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js +1 -0
  132. package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.br +0 -0
  133. package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.gz +0 -0
  134. package/payload/server/public/assets/{routines-6fxZNacQ.js → routines-vy5xsyqx.js} +2 -2
  135. package/payload/server/public/assets/routines-vy5xsyqx.js.br +0 -0
  136. package/payload/server/public/assets/routines-vy5xsyqx.js.gz +0 -0
  137. package/payload/server/public/assets/{skills-51mqx-s5.js → skills-X7FTilVy.js} +1 -1
  138. package/payload/server/public/assets/skills-X7FTilVy.js.br +0 -0
  139. package/payload/server/public/assets/skills-X7FTilVy.js.gz +0 -0
  140. package/payload/server/public/assets/tasks-BXx_jk_p.js +3 -0
  141. package/payload/server/public/assets/tasks-BXx_jk_p.js.br +0 -0
  142. package/payload/server/public/assets/tasks-BXx_jk_p.js.gz +0 -0
  143. package/payload/server/public/assets/triangle-alert-CuWcGaRo.js +1 -0
  144. package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.br +0 -0
  145. package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.gz +0 -0
  146. package/payload/server/public/assets/{useCopyFeedback-BG7h37Rx.js → useCopyFeedback-DLSKs6r2.js} +1 -1
  147. package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.br +0 -0
  148. package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.gz +0 -0
  149. package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js +6 -0
  150. package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.br +0 -0
  151. package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.gz +0 -0
  152. package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js +2 -0
  153. package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.br +0 -0
  154. package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.gz +0 -0
  155. package/payload/server/public/assets/wrench-nDMQ6x-6.js +1 -0
  156. package/payload/server/public/assets/wrench-nDMQ6x-6.js.br +0 -0
  157. package/payload/server/public/assets/wrench-nDMQ6x-6.js.gz +0 -0
  158. package/payload/server/public/browser.html +5 -5
  159. package/payload/server/public/calendar.html +8 -8
  160. package/payload/server/public/chat.html +14 -14
  161. package/payload/server/public/data.html +12 -12
  162. package/payload/server/public/field.html +3 -3
  163. package/payload/server/public/graph.html +10 -10
  164. package/payload/server/public/index.html +14 -14
  165. package/payload/server/public/operator.html +15 -15
  166. package/payload/server/public/public.html +14 -14
  167. package/payload/server/public/routines.html +8 -8
  168. package/payload/server/public/skills.html +6 -6
  169. package/payload/server/public/tasks.html +7 -7
  170. package/payload/server/server.js +1 -1
  171. package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js +0 -1
  172. package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js.br +0 -0
  173. package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js.gz +0 -0
  174. package/payload/server/public/assets/AdminShell-B-iX9ahJ.js +0 -3
  175. package/payload/server/public/assets/AdminShell-B-iX9ahJ.js.br +0 -0
  176. package/payload/server/public/assets/AdminShell-B-iX9ahJ.js.gz +0 -0
  177. package/payload/server/public/assets/activity-CqgFwANo.js +0 -1
  178. package/payload/server/public/assets/activity-CqgFwANo.js.br +0 -0
  179. package/payload/server/public/assets/activity-CqgFwANo.js.gz +0 -0
  180. package/payload/server/public/assets/admin-K63n6ybW.js +0 -1
  181. package/payload/server/public/assets/admin-K63n6ybW.js.br +0 -0
  182. package/payload/server/public/assets/admin-K63n6ybW.js.gz +0 -0
  183. package/payload/server/public/assets/agents-cue5Zf8X.js +0 -1
  184. package/payload/server/public/assets/agents-cue5Zf8X.js.br +0 -0
  185. package/payload/server/public/assets/agents-cue5Zf8X.js.gz +0 -0
  186. package/payload/server/public/assets/browser-CqDBcKwU.js.br +0 -0
  187. package/payload/server/public/assets/browser-CqDBcKwU.js.gz +0 -0
  188. package/payload/server/public/assets/calendar-BgzuW5Rt.js +0 -1
  189. package/payload/server/public/assets/calendar-BgzuW5Rt.js.br +0 -0
  190. package/payload/server/public/assets/calendar-BgzuW5Rt.js.gz +0 -0
  191. package/payload/server/public/assets/chat-HYZViu2u.js +0 -1
  192. package/payload/server/public/assets/chat-HYZViu2u.js.br +0 -3
  193. package/payload/server/public/assets/chat-HYZViu2u.js.gz +0 -0
  194. package/payload/server/public/assets/chevron-left-C5FXH9r_.js +0 -1
  195. package/payload/server/public/assets/chevron-left-C5FXH9r_.js.br +0 -0
  196. package/payload/server/public/assets/clock-DxPtyejC.js +0 -1
  197. package/payload/server/public/assets/clock-DxPtyejC.js.br +0 -0
  198. package/payload/server/public/assets/clock-DxPtyejC.js.gz +0 -0
  199. package/payload/server/public/assets/copy-BNhZ0flH.js +0 -1
  200. package/payload/server/public/assets/copy-BNhZ0flH.js.br +0 -0
  201. package/payload/server/public/assets/copy-BNhZ0flH.js.gz +0 -0
  202. package/payload/server/public/assets/data-kCBB8Z4Z.js +0 -1
  203. package/payload/server/public/assets/data-kCBB8Z4Z.js.br +0 -0
  204. package/payload/server/public/assets/data-kCBB8Z4Z.js.gz +0 -0
  205. package/payload/server/public/assets/field-X4OVA3vx.js.br +0 -0
  206. package/payload/server/public/assets/field-X4OVA3vx.js.gz +0 -0
  207. package/payload/server/public/assets/file-text-BddO_2EI.js +0 -1
  208. package/payload/server/public/assets/file-text-BddO_2EI.js.br +0 -0
  209. package/payload/server/public/assets/file-text-BddO_2EI.js.gz +0 -0
  210. package/payload/server/public/assets/graph-l7tzHqvm.js.br +0 -0
  211. package/payload/server/public/assets/graph-l7tzHqvm.js.gz +0 -0
  212. package/payload/server/public/assets/graph-labels-CtFFq5W2.js +0 -1
  213. package/payload/server/public/assets/graph-labels-CtFFq5W2.js.br +0 -0
  214. package/payload/server/public/assets/graph-labels-CtFFq5W2.js.gz +0 -0
  215. package/payload/server/public/assets/jsx-runtime-bq0g4NlA.css.br +0 -0
  216. package/payload/server/public/assets/operator-Dg-dwf7a.js.br +0 -7
  217. package/payload/server/public/assets/operator-Dg-dwf7a.js.gz +0 -0
  218. package/payload/server/public/assets/page-Ci1bfIbs.js +0 -1
  219. package/payload/server/public/assets/page-Ci1bfIbs.js.br +0 -0
  220. package/payload/server/public/assets/page-Ci1bfIbs.js.gz +0 -0
  221. package/payload/server/public/assets/page-Y9JmFq0Q.js +0 -32
  222. package/payload/server/public/assets/page-Y9JmFq0Q.js.br +0 -0
  223. package/payload/server/public/assets/page-Y9JmFq0Q.js.gz +0 -0
  224. package/payload/server/public/assets/play-BSZVGpQT.js +0 -1
  225. package/payload/server/public/assets/play-BSZVGpQT.js.br +0 -0
  226. package/payload/server/public/assets/play-BSZVGpQT.js.gz +0 -0
  227. package/payload/server/public/assets/public-m66FAza_.js +0 -1
  228. package/payload/server/public/assets/public-m66FAza_.js.br +0 -0
  229. package/payload/server/public/assets/public-m66FAza_.js.gz +0 -0
  230. package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js +0 -1
  231. package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js.br +0 -0
  232. package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js.gz +0 -0
  233. package/payload/server/public/assets/routines-6fxZNacQ.js.br +0 -0
  234. package/payload/server/public/assets/routines-6fxZNacQ.js.gz +0 -0
  235. package/payload/server/public/assets/skills-51mqx-s5.js.br +0 -0
  236. package/payload/server/public/assets/skills-51mqx-s5.js.gz +0 -0
  237. package/payload/server/public/assets/tasks-D4y-Cwa3.js +0 -3
  238. package/payload/server/public/assets/tasks-D4y-Cwa3.js.br +0 -0
  239. package/payload/server/public/assets/tasks-D4y-Cwa3.js.gz +0 -0
  240. package/payload/server/public/assets/triangle-alert-BzCjDFts.js +0 -1
  241. package/payload/server/public/assets/triangle-alert-BzCjDFts.js.br +0 -0
  242. package/payload/server/public/assets/triangle-alert-BzCjDFts.js.gz +0 -0
  243. package/payload/server/public/assets/useCopyFeedback-BG7h37Rx.js.br +0 -2
  244. package/payload/server/public/assets/useCopyFeedback-BG7h37Rx.js.gz +0 -0
  245. package/payload/server/public/assets/useMediaQuery-CqwktQr_.js +0 -6
  246. package/payload/server/public/assets/useMediaQuery-CqwktQr_.js.br +0 -0
  247. package/payload/server/public/assets/useMediaQuery-CqwktQr_.js.gz +0 -0
  248. package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js +0 -2
  249. package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js.br +0 -0
  250. package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js.gz +0 -0
  251. package/payload/server/public/assets/wrench-B2WKLLQG.js +0 -1
  252. package/payload/server/public/assets/wrench-B2WKLLQG.js.br +0 -0
  253. package/payload/server/public/assets/wrench-B2WKLLQG.js.gz +0 -0
  254. /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js → jsx-runtime-CvVXOszc.js} +0 -0
  255. /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js.br → jsx-runtime-CvVXOszc.js.br} +0 -0
  256. /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js.gz → jsx-runtime-CvVXOszc.js.gz} +0 -0
@@ -1,5 +1,35 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
+ const QUEUE_KINDS = ["operational", "accounting"];
4
+ /**
5
+ * Validate a queue descriptor arriving off tool arguments.
6
+ *
7
+ * Returns `{}` for an absent queue, `{ queue }` for a valid one, and
8
+ * `{ refusal }` for anything else. A refusal string rather than a throw,
9
+ * because the tool answers a refusal as text and never as an exception.
10
+ */
11
+ export function queueFromArgs(raw) {
12
+ if (raw === undefined || raw === null)
13
+ return {};
14
+ const q = raw;
15
+ if (!QUEUE_KINDS.includes(q.kind)) {
16
+ return { refusal: "queue.kind must be operational or accounting." };
17
+ }
18
+ for (const field of ["path", "changedSinceParam", "itemsField"]) {
19
+ const v = q[field];
20
+ if (typeof v !== "string" || v.trim() === "") {
21
+ return { refusal: `queue.${field} must not be blank.` };
22
+ }
23
+ }
24
+ return {
25
+ queue: {
26
+ kind: q.kind,
27
+ path: q.path,
28
+ changedSinceParam: q.changedSinceParam,
29
+ itemsField: q.itemsField,
30
+ },
31
+ };
32
+ }
3
33
  /** Per-account secrets dir — the same location joblogic/quickbooks/cloudflare use. */
4
34
  function secretsDir(platformRoot, accountId) {
5
35
  return join(resolve(platformRoot, "..", "data/accounts"), accountId, "secrets");
@@ -58,6 +88,9 @@ export function listConnectors(platformRoot, accountId) {
58
88
  return Object.entries(readRegistry(platformRoot, accountId)).map(([name, r]) => ({
59
89
  name,
60
90
  baseUrl: r.baseUrl,
91
+ // Task 2688 — the descriptor carries no secret, and the scheduling gate
92
+ // reads it here rather than opening the registry file itself.
93
+ ...(r.queue ? { queue: r.queue } : {}),
61
94
  }));
62
95
  }
63
96
  //# sourceMappingURL=store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/lib/store.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,8 @@
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
- "start": "node dist/index.js"
9
+ "start": "node dist/index.js",
10
+ "test": "vitest run"
10
11
  },
11
12
  "dependencies": {
12
13
  "@modelcontextprotocol/sdk": "^1.12.1",
@@ -15,6 +16,7 @@
15
16
  },
16
17
  "devDependencies": {
17
18
  "@types/node": "^22.0.0",
18
- "typescript": "^5.7.0"
19
+ "typescript": "^5.7.0",
20
+ "vitest": "^4.1.2"
19
21
  }
20
22
  }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["src/**/__tests__/**/*.test.ts"],
7
+ },
8
+ });
@@ -37,6 +37,19 @@ which fails `prebuild` if any emoji codepoint appears in a `.ts`/`.tsx` file
37
37
  under `app/` (tests excluded). Inline an icon at the surrounding text size and
38
38
  colour (`size={14}` matches body text; Lucide inherits `currentColor`).
39
39
 
40
+ **A long message shows its first 8 lines.** On `/chat`, the WhatsApp and
41
+ Telegram readers and the public visitor chat, a message body taller than 8 lines
42
+ is cut at 8 and carries a "Show more…" control that reveals the rest; "Show
43
+ less" puts it back. The control appears only when there is genuinely more to
44
+ see, so a short message looks exactly as it did before. Expanding one message
45
+ expands only that message, and scrolling back through older messages does not
46
+ carry an expansion onto a different one. Two things are never cut: an error the
47
+ agent reports, and the map on a shared location. The reader's search also opens
48
+ any message whose match would otherwise sit in the hidden part, so a search
49
+ never finds something it then hides. See
50
+ [`.docs/message-body-clamp.md`](../../../.docs/message-body-clamp.md) for the
51
+ render sites and the census that watches it.
52
+
40
53
  ## Admin Hono routes
41
54
 
42
55
  Every admin sub-app is mounted by
@@ -294,9 +307,9 @@ one, over the same automation set the routes list. The roster seed's own
294
307
  `descNull` counter is its narrow twin, scoped to seeded rows and so blind to
295
308
  exactly the operator-created ones most likely to carry no caption.
296
309
 
297
- **The shipped roster.** Every account starts with fourteen routines —
310
+ **The shipped roster.** Every account starts with thirteen routines —
298
311
  `start-of-day`, `inbound-check`, `telegram-inbound-check`, `end-of-day`,
299
- `appointment-reminder`, `commitment-chase`, `calendar-reconcile`,
312
+ `commitment-chase`, `calendar-reconcile`,
300
313
  `contact-reconcile`, `crm-reconcile`, `finance-reconcile`, `weekly-digest`,
301
314
  `channel-link-check`, `graph-health`, `filesystem-health`, in `/routines`
302
315
  display order. `ROUTINE_ROSTER_KEYS.length`
@@ -198,4 +198,6 @@ If you suspect background processes are piling up, run `grep '\[reaper\]' ~/.{br
198
198
 
199
199
  Every install seeds `permissions.allow:[]` plus `defaultMode:"bypassPermissions"` into both the brand-scoped settings file (`~/.{brand}/.claude/settings.json`) and every account-scoped one (`<install>/data/accounts/<id>/.claude/settings.json`). `bypassPermissions` alone stops Claude Code from sending tool calls to its remote auto-classifier (it skips all permission checks), which would otherwise surface a permission prompt in the chat that an unattended session never answers. The `allow` array stays empty and **must never contain `"*"`**: Claude Code ≥ 2.1.167 rejects `"*"` as an allow token and renders a blocking Settings Warning that, under `--remote-control`, stops every `/rc-spawn` from binding. What each subagent is allowed to use is still controlled by the `tools:` line in its agent file, not by a top-level allowlist. To verify after an install: `cat ~/.{brand}/.claude/settings.json | jq '.permissions'` (expect `allow: []`). To repair an install seeded before this change: `bash <install>/platform/scripts/backfill-bypass-permissions.sh ~/.{brand}`.
200
200
 
201
+ **Workspace trust.** Separate from permissions, and its own way to stop a spawn dead. Claude Code refuses to start in a directory it does not trust, and it scopes that trust to the **git repository root** of the working directory rather than to any trusted ancestor. Every account directory is its own repository (`provision-account-dir.sh` runs `git init` there so Claude Code finds the account's own `.claude/` hook set), so trusting the home directory does not reach them. Every install therefore seeds `projects[<dir>].hasTrustDialogAccepted: true` into `~/.{brand}/.claude/.claude.json` for the home directory **and for every directory under `<install>/data/accounts`**, preserving all other fields the CLI wrote. Without it the PTY-spawned session halts on "Quick safety check: Is this a project you created or one you trust?" and never binds, which surfaces as a session that starts and answers nothing, with no error in the interface. The seeding runs twice per install and both passes matter: the `deploy` pass trusts the accounts an upgrade already has, and the `post-account` pass, which runs straight after account setup, trusts the account a brand-new install has only just created. The install line is `[install] op=claude-trust-seed phase=<deploy|post-account> file=… accounts=<N> seeded=<n> alreadyTrusted=<n> stale=<n> error=<none|msg>`; `seeded + alreadyTrusted` equals `accounts`, and a re-run reads `seeded=0`. Because an account added *after* an install has an untrusted repository root that no event announces, the session manager reconciles the two every hour and logs `[claude-trust-census] accounts=<N> trusted=<n> untrusted=<n> untrustedDirs=<csv|none> homeTrusted=<bool> onboarded=<bool>` — a non-zero `untrusted` names the directories whose next session will hang. To verify after an install: `node -e 'const j=require(process.env.HOME+"/.{brand}/.claude/.claude.json");const p=j.projects||{};for(const k of Object.keys(p))if(k.includes("/data/accounts/"))console.log(k,p[k].hasTrustDialogAccepted)'` (expect `true` for every account directory that exists).
202
+
201
203
  **`autoMode` trust block.** The same brand-scoped writer ([`permissions-seed.ts`](../../../packages/create-maxy-code/src/permissions-seed.ts), `AUTO_MODE_BLOCK`) also seeds the harness `autoMode` block. `bypassPermissions` skips the auto-mode classifier entirely, so the block is **belt-and-suspenders**: it only matters when a session is toggled into `auto` mode, where the classifier otherwise soft-denies the doctrine-sanctioned Cloudflare scoped-token mint ("permission/credential escalation") because the install's own Cloudflare infra is not in the classifier's default trusted environment. The block keeps `"$defaults"` first in `environment` and `allow` (so the built-in Data-Exfiltration / force-push / prod-deploy guards stay intact) and adds prose declaring `api.cloudflare.com` the install's own provider endpoint plus an allow for minting/persisting narrow per-scope tokens. It is seeded **only** into the brand/user settings (`~/.{brand}/.claude/settings.json`) — the classifier never reads `autoMode` from the shared project (account-scoped) settings, so `setup-account.sh` does not carry it. An auto-mode agent cannot seed this itself (the default `hard_deny` "Auto-Mode Bypass" blocks any agent editing `.claude/settings*.json`); installer seed is the only path. To verify after an install: `CLAUDE_CONFIG_DIR=~/.{brand}/.claude claude auto-mode config | jq '.environment, .allow'` (expect the Cloudflare entries, `$defaults` expanded) and `jq '.autoMode' ~/.{brand}/.claude/settings.json`.
@@ -156,6 +156,10 @@ There are two independent paths into the graph, both writing `:ConversationArchi
156
156
 
157
157
  A mailbox with no cursor backfills 7 days on its first cycle, on both arms. Mail older than that window is never sampled. The Outlook arm is not yet proven on a live install.
158
158
 
159
+ **Retention.** The same service sweeps once per cycle, after both arms. It deletes a `:Section {source:'email'}` only when every routine on that account holding a `graph-unflagged` check has flagged it processed and it is older than `EMAIL_RETENTION_DAYS` whole days, measured from ingest. Two rules keep the sweep safe rather than destructive: an unset, zero, negative or unparseable `EMAIL_RETENTION_DAYS` prunes nothing, and an account where NO routine holds such a check prunes nothing, because an empty interested set means nobody has consented to the data being processed rather than everybody. A routine counts whether it is enabled or suspended, so enabling a parked routine never finds its backlog deleted. `:ConversationArchive` nodes are never pruned: they are merged per correspondent set and a reply bumps them, so they never age past a cutoff. A period above 36500 days is refused like any other unreadable value, because a larger one overflows the cutoff date. One line per account per cycle, `op=email-retention account=<id> interested=<k> pruned=<n> kept-outstanding=<m> repaired=<r> ageDays=<N|unset>`, with `capped=true` when the candidate batch hit its 5000-row ceiling. `pruned=0` is healthy; `interested>0` with `kept-outstanding` rising across cycles is a routine that is not processing its backlog, and `pruned>0 repaired=0` on a span taken from the middle of a thread is a `:NEXT` chain left broken. No shipped routine carries a `graph-unflagged` check yet, so every install currently logs `interested=0` and deletes nothing.
160
+
161
+ The chain repair and the delete run in ONE write transaction. Between them the surviving predecessor holds both its old edge to the doomed section and its new edge to the target, so a failure landing between the two would commit that fork, and a routine added before the next cycle would make those sections outstanding again with nothing left to repair them.
162
+
159
163
  - **Operator-initiated ingest — human-in-the-loop.** When the operator says "check my emails", `email-fetch` lists new messages and the operator approves each one; only the approved subset is written via `email-ingest`, which also fetches and archives attachment bytes. See `skills/email-ingest/SKILL.md` for the conversation flow and `skills/email-composition/SKILL.md` for drafting replies once a thread is stored.
160
164
 
161
165
  The two watermarks are independent by design, so a message may be seen by both; `dispatch` uses MERGE, so a double write is idempotent and never duplicates a node.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=retention.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.test.d.ts","sourceRoot":"","sources":["../../../src/sampler/__tests__/retention.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,233 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { resolveRetentionDays, cutoffIso, interestedSets, partitionAged, runRetention, sweepAccount, RETENTION_CANDIDATE_CAP, RETENTION_MAX_DAYS, } from "../retention.js";
3
+ describe("resolveRetentionDays", () => {
4
+ it("returns null when unset", () => {
5
+ expect(resolveRetentionDays({})).toBeNull();
6
+ });
7
+ it("returns null for zero, negative, fractional and unparseable values", () => {
8
+ for (const v of ["0", "-1", "30.5", "abc", "", " "]) {
9
+ expect(resolveRetentionDays({ EMAIL_RETENTION_DAYS: v })).toBeNull();
10
+ }
11
+ });
12
+ it("returns the positive integer day count", () => {
13
+ expect(resolveRetentionDays({ EMAIL_RETENTION_DAYS: "30" })).toBe(30);
14
+ });
15
+ it("refuses a period large enough to overflow the cutoff date", () => {
16
+ expect(resolveRetentionDays({ EMAIL_RETENTION_DAYS: "100000000000" })).toBeNull();
17
+ expect(resolveRetentionDays({ EMAIL_RETENTION_DAYS: String(RETENTION_MAX_DAYS + 1) })).toBeNull();
18
+ expect(resolveRetentionDays({ EMAIL_RETENTION_DAYS: String(RETENTION_MAX_DAYS) })).toBe(RETENTION_MAX_DAYS);
19
+ });
20
+ it("keeps every accepted period inside the range cutoffIso can express", () => {
21
+ expect(() => cutoffIso(new Date("2026-08-13T09:00:00.000Z"), RETENTION_MAX_DAYS)).not.toThrow();
22
+ });
23
+ });
24
+ describe("cutoffIso", () => {
25
+ it("subtracts whole days from the clock in the ISO shape createdAt uses", () => {
26
+ expect(cutoffIso(new Date("2026-08-13T09:00:00.000Z"), 30)).toBe("2026-07-14T09:00:00.000Z");
27
+ });
28
+ });
29
+ describe("interestedSets", () => {
30
+ const args = (checks) => JSON.stringify({ eventId: "ignored", checks });
31
+ it("splits routines by the label their graph-unflagged check names", () => {
32
+ const rows = [
33
+ { eventId: "ev-sec", gateArgs: args([{ type: "graph-unflagged", label: "Section" }]) },
34
+ { eventId: "ev-arc", gateArgs: args([{ type: "graph-unflagged", label: "ConversationArchive" }]) },
35
+ ];
36
+ expect(interestedSets(rows)).toEqual({ section: ["ev-sec"], archive: ["ev-arc"] });
37
+ });
38
+ it("puts a routine holding both checks in both sets", () => {
39
+ const rows = [
40
+ {
41
+ eventId: "ev-both",
42
+ gateArgs: args([
43
+ { type: "graph-unflagged", label: "Section" },
44
+ { type: "graph-unflagged", label: "ConversationArchive" },
45
+ ]),
46
+ },
47
+ ];
48
+ expect(interestedSets(rows)).toEqual({ section: ["ev-both"], archive: ["ev-both"] });
49
+ });
50
+ it("ignores checks of other types and labels outside the accepted pair", () => {
51
+ const rows = [
52
+ { eventId: "ev-mail", gateArgs: args([{ type: "mail", mailbox: "a@b.c" }]) },
53
+ { eventId: "ev-odd", gateArgs: args([{ type: "graph-unflagged", label: "Person" }]) },
54
+ ];
55
+ expect(interestedSets(rows)).toEqual({ section: [], archive: [] });
56
+ });
57
+ it("contributes nothing and does not throw on absent or malformed gateArgs", () => {
58
+ const rows = [
59
+ { eventId: "ev-null", gateArgs: null },
60
+ { eventId: "ev-bad", gateArgs: "{not json" },
61
+ { eventId: "ev-nochecks", gateArgs: JSON.stringify({ eventId: "x" }) },
62
+ { eventId: "ev-checksnotarray", gateArgs: JSON.stringify({ checks: "Section" }) },
63
+ ];
64
+ expect(interestedSets(rows)).toEqual({ section: [], archive: [] });
65
+ });
66
+ it("names a routine once per set even when it repeats the same check", () => {
67
+ const rows = [
68
+ {
69
+ eventId: "ev-dup",
70
+ gateArgs: args([
71
+ { type: "graph-unflagged", label: "Section" },
72
+ { type: "graph-unflagged", label: "Section" },
73
+ ]),
74
+ },
75
+ ];
76
+ expect(interestedSets(rows)).toEqual({ section: ["ev-dup"], archive: [] });
77
+ });
78
+ });
79
+ describe("partitionAged", () => {
80
+ const aged = (over = {}) => ({
81
+ id: "s1",
82
+ processedBy: [],
83
+ parentProcessedBy: [],
84
+ ...over,
85
+ });
86
+ it("prunes a section every interested routine has flagged", () => {
87
+ const rows = [aged({ processedBy: ["ev-a", "ev-b"], parentProcessedBy: ["ev-c"] })];
88
+ expect(partitionAged(rows, { section: ["ev-a", "ev-b"], archive: ["ev-c"] })).toEqual({
89
+ prunable: ["s1"],
90
+ keptOutstanding: 0,
91
+ });
92
+ });
93
+ it("keeps a section with one section-gated routine still outstanding", () => {
94
+ const rows = [aged({ processedBy: ["ev-a"] })];
95
+ expect(partitionAged(rows, { section: ["ev-a", "ev-b"], archive: [] })).toEqual({
96
+ prunable: [],
97
+ keptOutstanding: 1,
98
+ });
99
+ });
100
+ it("keeps a fully section-flagged node whose parent archive is outstanding", () => {
101
+ const rows = [aged({ processedBy: ["ev-a"], parentProcessedBy: [] })];
102
+ expect(partitionAged(rows, { section: ["ev-a"], archive: ["ev-c"] })).toEqual({
103
+ prunable: [],
104
+ keptOutstanding: 1,
105
+ });
106
+ });
107
+ it("keeps a section that has no parent archive", () => {
108
+ const rows = [aged({ processedBy: ["ev-a"], parentProcessedBy: null })];
109
+ expect(partitionAged(rows, { section: ["ev-a"], archive: [] })).toEqual({
110
+ prunable: [],
111
+ keptOutstanding: 1,
112
+ });
113
+ });
114
+ it("ignores a flag from a routine that is not interested", () => {
115
+ const rows = [aged({ processedBy: ["ev-a", "ev-stale"] })];
116
+ expect(partitionAged(rows, { section: ["ev-a"], archive: [] })).toEqual({
117
+ prunable: ["s1"],
118
+ keptOutstanding: 0,
119
+ });
120
+ });
121
+ });
122
+ const NOW = new Date("2026-08-13T09:00:00.000Z");
123
+ function deps(over = {}) {
124
+ return {
125
+ listEmailAccountIds: vi.fn(async () => ["acc-1"]),
126
+ listGateArgs: vi.fn(async () => []),
127
+ listAgedSections: vi.fn(async () => []),
128
+ pruneSections: vi.fn(async () => ({ repaired: 0, pruned: 0 })),
129
+ now: () => NOW,
130
+ log: vi.fn(),
131
+ ...over,
132
+ };
133
+ }
134
+ const lines = (d) => d.log.mock.calls.map((c) => c[0]);
135
+ const sectionGate = (eventId) => ({
136
+ eventId,
137
+ gateArgs: JSON.stringify({ checks: [{ type: "graph-unflagged", label: "Section" }] }),
138
+ });
139
+ describe("runRetention", () => {
140
+ it("prunes nothing and names the period unset when EMAIL_RETENTION_DAYS is absent", async () => {
141
+ const d = deps();
142
+ await runRetention(d, {});
143
+ expect(d.listEmailAccountIds).not.toHaveBeenCalled();
144
+ expect(lines(d)).toEqual([
145
+ "op=email-retention account=- interested=0 pruned=0 kept-outstanding=0 repaired=0 ageDays=unset",
146
+ ]);
147
+ });
148
+ it("sweeps every account holding email sections when a period is set", async () => {
149
+ const d = deps({ listEmailAccountIds: vi.fn(async () => ["acc-1", "acc-2"]) });
150
+ await runRetention(d, { EMAIL_RETENTION_DAYS: "30" });
151
+ expect(d.listGateArgs).toHaveBeenCalledTimes(2);
152
+ });
153
+ it("reports one account's failure and continues to the next", async () => {
154
+ const d = deps({
155
+ listEmailAccountIds: vi.fn(async () => ["acc-1", "acc-2"]),
156
+ listGateArgs: vi.fn(async (accountId) => {
157
+ if (accountId === "acc-1")
158
+ throw new Error("neo4j down");
159
+ return [];
160
+ }),
161
+ });
162
+ await runRetention(d, { EMAIL_RETENTION_DAYS: "30" });
163
+ expect(lines(d)[0]).toBe("op=email-retention-error account=acc-1 detail=neo4j down");
164
+ expect(lines(d)[1]).toBe("op=email-retention account=acc-2 interested=0 pruned=0 kept-outstanding=0 repaired=0 ageDays=30");
165
+ });
166
+ });
167
+ describe("sweepAccount", () => {
168
+ it("deletes nothing and reports interested=0 when no routine holds the check", async () => {
169
+ const d = deps({ listGateArgs: vi.fn(async () => [{ eventId: "ev-1", gateArgs: null }]) });
170
+ await sweepAccount(d, "acc-1", 30);
171
+ expect(d.listAgedSections).not.toHaveBeenCalled();
172
+ expect(d.pruneSections).not.toHaveBeenCalled();
173
+ expect(lines(d)).toEqual([
174
+ "op=email-retention account=acc-1 interested=0 pruned=0 kept-outstanding=0 repaired=0 ageDays=30",
175
+ ]);
176
+ });
177
+ it("prunes only the flagged section and reports every count", async () => {
178
+ const d = deps({
179
+ listGateArgs: vi.fn(async () => [sectionGate("ev-a")]),
180
+ listAgedSections: vi.fn(async () => [
181
+ { id: "s1", processedBy: ["ev-a"], parentProcessedBy: [] },
182
+ { id: "s2", processedBy: [], parentProcessedBy: [] },
183
+ ]),
184
+ pruneSections: vi.fn(async () => ({ repaired: 2, pruned: 1 })),
185
+ });
186
+ await sweepAccount(d, "acc-1", 30);
187
+ expect(d.listAgedSections.mock.calls[0]).toEqual([
188
+ "acc-1",
189
+ "2026-07-14T09:00:00.000Z",
190
+ RETENTION_CANDIDATE_CAP,
191
+ ]);
192
+ expect(d.pruneSections).toHaveBeenCalledWith("acc-1", ["s1"]);
193
+ expect(lines(d)).toEqual([
194
+ "op=email-retention account=acc-1 interested=1 pruned=1 kept-outstanding=1 repaired=2 ageDays=30",
195
+ ]);
196
+ });
197
+ it("reports repaired=0 beside a non-zero prune, the broken-chain signature", async () => {
198
+ const d = deps({
199
+ listGateArgs: vi.fn(async () => [sectionGate("ev-a")]),
200
+ listAgedSections: vi.fn(async () => [
201
+ { id: "s1", processedBy: ["ev-a"], parentProcessedBy: [] },
202
+ ]),
203
+ pruneSections: vi.fn(async () => ({ repaired: 0, pruned: 1 })),
204
+ });
205
+ await sweepAccount(d, "acc-1", 30);
206
+ expect(lines(d)[0]).toContain("pruned=1 kept-outstanding=0 repaired=0");
207
+ });
208
+ it("skips the repair and the delete when nothing is prunable", async () => {
209
+ const d = deps({
210
+ listGateArgs: vi.fn(async () => [sectionGate("ev-a")]),
211
+ listAgedSections: vi.fn(async () => [{ id: "s1", processedBy: [], parentProcessedBy: [] }]),
212
+ });
213
+ await sweepAccount(d, "acc-1", 30);
214
+ expect(d.pruneSections).not.toHaveBeenCalled();
215
+ expect(lines(d)).toEqual([
216
+ "op=email-retention account=acc-1 interested=1 pruned=0 kept-outstanding=1 repaired=0 ageDays=30",
217
+ ]);
218
+ });
219
+ it("names a capped candidate batch so a truncated sweep is never silent", async () => {
220
+ const rows = Array.from({ length: RETENTION_CANDIDATE_CAP }, (_, i) => ({
221
+ id: `s${i}`,
222
+ processedBy: [],
223
+ parentProcessedBy: [],
224
+ }));
225
+ const d = deps({
226
+ listGateArgs: vi.fn(async () => [sectionGate("ev-a")]),
227
+ listAgedSections: vi.fn(async () => rows),
228
+ });
229
+ await sweepAccount(d, "acc-1", 30);
230
+ expect(lines(d)[0]).toContain("capped=true");
231
+ });
232
+ });
233
+ //# sourceMappingURL=retention.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.test.js","sourceRoot":"","sources":["../../../src/sampler/__tests__/retention.test.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ import { fetchSinceUid, resolveDefaultRecipients } from "../lib/imap.js";
5
5
  import { dispatchEmailMessagesToConversationArchive } from "../lib/conversation-archive-dispatch.js";
6
6
  import { listAllImapAccounts, readSamplerCursor, advanceSamplerCursor } from "../lib/credentials.js";
7
7
  import { sampleMailbox } from "./sample-mailbox.js";
8
+ import { runRetention } from "./retention.js";
9
+ import { retentionSources } from "./retention-sources.js";
8
10
  // The standing email-sampler (Task 2682): a per-brand systemd user unit that
9
11
  // each cycle enumerates every IMAP mailbox on the install and writes new
10
12
  // inbound messages to the graph via the plugin's own dispatch, independent of
@@ -46,6 +48,23 @@ const deps = {
46
48
  async function cycle() {
47
49
  await imapArm();
48
50
  await outlookArm();
51
+ await retentionPass();
52
+ }
53
+ /**
54
+ * The retention sweep (Task 2685). Runs AFTER both arms so a cycle never deletes
55
+ * before it has written, and in its own try for the reason each arm has one: a
56
+ * retention failure must cost no ingest.
57
+ *
58
+ * `runRetention` reads `EMAIL_RETENTION_DAYS` itself and prunes nothing when it
59
+ * is unset, which is every install until an operator sets it.
60
+ */
61
+ async function retentionPass() {
62
+ try {
63
+ await runRetention(retentionSources(() => new Date(), logLine), process.env);
64
+ }
65
+ catch (e) {
66
+ logLine(`op=email-retention-error account=<sweep> detail=${e instanceof Error ? e.message : String(e)}`);
67
+ }
49
68
  }
50
69
  /** The IMAP arm (Task 2682). An enumeration failure ends this arm only; the
51
70
  * Outlook arm reads a different store and must still run. */
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/sampler/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/sampler/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { RetentionDeps } from "./retention.js";
2
+ export declare function retentionSources(now: () => Date, log: (line: string) => void): RetentionDeps;
3
+ //# sourceMappingURL=retention-sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention-sources.d.ts","sourceRoot":"","sources":["../../src/sampler/retention-sources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAwB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAuB1E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,aAAa,CAkJ5F"}
@@ -0,0 +1,146 @@
1
+ import neo4j from "neo4j-driver";
2
+ import { getSession } from "../lib/neo4j.js";
3
+ // The real graph behind `RetentionDeps` (Task 2685). Split from `retention.ts`
4
+ // so the predicate, the parsing and the sweep stay testable without Neo4j —
5
+ // the same division `scheduling-gate.ts` draws between its probe and
6
+ // `REAL_SOURCES`.
7
+ /**
8
+ * The longest deleted span the chain repair walks. A bound rather than `*0..`
9
+ * because an unbounded variable-length match over a long conversation is the one
10
+ * statement here that can run away; a span longer than this leaves the chain
11
+ * broken, which costs a later ingest a wrong tail and never costs data.
12
+ */
13
+ const RETENTION_CHAIN_HOPS = 500;
14
+ /** Neo4j returns list properties as-is; this narrows a `gateProcessedBy` value
15
+ * to the string list the predicate expects, treating an absent or wrong-typed
16
+ * property as "flagged by nobody". */
17
+ function flagList(value) {
18
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
19
+ }
20
+ export function retentionSources(now, log) {
21
+ return {
22
+ now,
23
+ log,
24
+ listEmailAccountIds: async () => {
25
+ const session = getSession();
26
+ try {
27
+ const res = await session.run(`MATCH (s:Section)
28
+ WHERE s.source = 'email' AND s.accountId IS NOT NULL
29
+ RETURN DISTINCT s.accountId AS accountId`);
30
+ return res.records.map((r) => r.get("accountId"));
31
+ }
32
+ finally {
33
+ await session.close();
34
+ }
35
+ },
36
+ // Every gate-bearing :Event on the account, enabled or suspended. A
37
+ // suspended routine counts as interested (operator decision 2026-08-13), so
38
+ // enabling a parked routine never finds its backlog already deleted — hence
39
+ // no `eventStatus` predicate here.
40
+ //
41
+ // `e.eventId`, never `elementId(e)`: the dispatcher passes the eventId
42
+ // PROPERTY as the flag key (agent-turn-dispatch.ts:506), so joining on the
43
+ // element id would compare against a value `gateProcessedBy` never holds and
44
+ // every section would look outstanding for ever.
45
+ listGateArgs: async (accountId) => {
46
+ const session = getSession();
47
+ try {
48
+ const res = await session.run(`MATCH (e:Event)
49
+ WHERE e.accountId = $accountId AND e.gateArgs IS NOT NULL
50
+ RETURN e.eventId AS eventId, e.gateArgs AS gateArgs`, { accountId });
51
+ return res.records
52
+ .filter((r) => typeof r.get("eventId") === "string")
53
+ .map((r) => ({
54
+ eventId: r.get("eventId"),
55
+ gateArgs: r.get("gateArgs") ?? null,
56
+ }));
57
+ }
58
+ finally {
59
+ await session.close();
60
+ }
61
+ },
62
+ // `s.createdAt < $cutoff` is lexical, which equals chronological because
63
+ // every value on both sides is a `toISOString()` string of fixed width. A
64
+ // createdAt of any other type compares as null in Cypher, so the row drops
65
+ // out of the candidate set and the section is KEPT.
66
+ listAgedSections: async (accountId, cutoff, limit) => {
67
+ const session = getSession();
68
+ try {
69
+ const res = await session.run(`MATCH (s:Section)
70
+ WHERE s.accountId = $accountId AND s.source = 'email'
71
+ AND s.createdAt < $cutoff
72
+ OPTIONAL MATCH (a:ConversationArchive)-[:HAS_SECTION]->(s)
73
+ RETURN elementId(s) AS id,
74
+ s.gateProcessedBy AS processedBy,
75
+ CASE WHEN a IS NULL THEN NULL ELSE coalesce(a.gateProcessedBy, []) END
76
+ AS parentProcessedBy
77
+ ORDER BY s.createdAt ASC
78
+ LIMIT $limit`, { accountId, cutoff, limit: neo4j.int(limit) });
79
+ return res.records.map((r) => {
80
+ const parent = r.get("parentProcessedBy");
81
+ return {
82
+ id: r.get("id"),
83
+ processedBy: flagList(r.get("processedBy")),
84
+ parentProcessedBy: parent === null || parent === undefined ? null : flagList(parent),
85
+ };
86
+ });
87
+ }
88
+ finally {
89
+ await session.close();
90
+ }
91
+ },
92
+ // The repair and the delete, in ONE write transaction.
93
+ //
94
+ // Sections are chained by :NEXT, and the next ingest finds the tail by
95
+ // looking for one with no outgoing :NEXT, LIMIT 1
96
+ // (memory-ingest.ts:1734-1742). Deleting from the middle of a chain leaves
97
+ // TWO such nodes, so a later ingest could chain new content onto a middle
98
+ // section. The repair connects each surviving predecessor to the first
99
+ // surviving section after the deleted span, shortest span first.
100
+ //
101
+ // It must run BEFORE the delete, because it walks the very edges the delete
102
+ // removes — and therefore inside the same transaction as the delete, because
103
+ // between the two the predecessor holds an edge to the doomed section AND
104
+ // its new edge to the target. A failure between them would leave that fork
105
+ // committed, and a routine added before the next cycle would make the
106
+ // sections outstanding again, so nothing would ever come back to repair it.
107
+ //
108
+ // Both statements carry `accountId` beside the element ids, the way
109
+ // `flagProcessedNodes` does (scheduling/mcp/src/lib/gate-flag.ts:100-104).
110
+ // The ids are already account-scoped by `listAgedSections`; the predicate is
111
+ // here so the scope is a property of the DELETE itself and not of the
112
+ // caller's discipline.
113
+ pruneSections: async (accountId, ids) => {
114
+ const session = getSession();
115
+ try {
116
+ return await session.executeWrite(async (tx) => {
117
+ const repair = await tx.run(`MATCH (p:Section)-[:NEXT]->(first:Section)
118
+ WHERE first.accountId = $accountId
119
+ AND elementId(first) IN $ids AND NOT elementId(p) IN $ids
120
+ MATCH path = (first)-[:NEXT*0..${RETENTION_CHAIN_HOPS}]->(q:Section)
121
+ WHERE NOT elementId(q) IN $ids
122
+ AND all(n IN nodes(path)[0..-1] WHERE elementId(n) IN $ids)
123
+ WITH p, q, length(path) AS len
124
+ ORDER BY len ASC
125
+ WITH p, head(collect(q)) AS target
126
+ MERGE (p)-[r:NEXT]->(target)
127
+ ON CREATE SET r.createdByAgent = 'email-retention', r.createdAt = $now
128
+ RETURN count(r) AS repaired`, { accountId, ids, now: now().toISOString() });
129
+ const del = await tx.run(`MATCH (s:Section)
130
+ WHERE s.accountId = $accountId AND s.source = 'email'
131
+ AND elementId(s) IN $ids
132
+ DETACH DELETE s
133
+ RETURN count(s) AS pruned`, { accountId, ids });
134
+ return {
135
+ repaired: repair.records[0]?.get("repaired")?.toNumber?.() ?? 0,
136
+ pruned: del.records[0]?.get("pruned")?.toNumber?.() ?? 0,
137
+ };
138
+ });
139
+ }
140
+ finally {
141
+ await session.close();
142
+ }
143
+ },
144
+ };
145
+ }
146
+ //# sourceMappingURL=retention-sources.js.map