@rubytech/create-sitedesk-code 0.1.446 → 0.1.448

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 (224) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
  3. package/payload/platform/lib/graph-style/dist/index.js +6 -0
  4. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  5. package/payload/platform/lib/graph-style/src/index.ts +7 -0
  6. package/payload/platform/neo4j/schema.cypher +35 -1
  7. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  8. package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
  9. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +42 -5
  10. package/payload/platform/plugins/docs/references/calendar-booking.md +18 -1
  11. package/payload/platform/plugins/docs/references/claudeai-connectors.md +15 -0
  12. package/payload/platform/plugins/docs/references/outlook-guide.md +8 -3
  13. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +4 -4
  14. package/payload/platform/plugins/memory/references/schema-construction.md +7 -2
  15. package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
  16. package/payload/platform/plugins/outlook/PLUGIN.md +23 -5
  17. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.d.ts +2 -0
  18. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.d.ts.map +1 -0
  19. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +70 -0
  20. package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -0
  21. package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.d.ts +2 -0
  22. package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.d.ts.map +1 -0
  23. package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.js +22 -0
  24. package/payload/platform/plugins/outlook/mcp/dist/__tests__/html-to-text.test.js.map +1 -0
  25. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.d.ts +2 -0
  26. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.d.ts.map +1 -0
  27. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.js +60 -0
  28. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-delete.test.js.map +1 -0
  29. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.d.ts +2 -0
  30. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.d.ts.map +1 -0
  31. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.js +80 -0
  32. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-fetch-body.test.js.map +1 -0
  33. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.d.ts +2 -0
  34. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.d.ts.map +1 -0
  35. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.js +90 -0
  36. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-otp-extract.test.js.map +1 -0
  37. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.d.ts +2 -0
  38. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.d.ts.map +1 -0
  39. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.js +194 -0
  40. package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-reply.test.js.map +1 -0
  41. package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.d.ts +2 -0
  42. package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.d.ts.map +1 -0
  43. package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.js +16 -0
  44. package/payload/platform/plugins/outlook/mcp/dist/__tests__/otp.test.js.map +1 -0
  45. package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.d.ts +2 -0
  46. package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.d.ts.map +1 -0
  47. package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.js +103 -0
  48. package/payload/platform/plugins/outlook/mcp/dist/__tests__/outbound-attachments.test.js.map +1 -0
  49. package/payload/platform/plugins/outlook/mcp/dist/index.js +88 -0
  50. package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
  51. package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.d.ts +9 -0
  52. package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.d.ts.map +1 -0
  53. package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.js +24 -0
  54. package/payload/platform/plugins/outlook/mcp/dist/lib/html-to-text.js.map +1 -0
  55. package/payload/platform/plugins/outlook/mcp/dist/lib/otp.d.ts +2 -0
  56. package/payload/platform/plugins/outlook/mcp/dist/lib/otp.d.ts.map +1 -0
  57. package/payload/platform/plugins/outlook/mcp/dist/lib/otp.js +49 -0
  58. package/payload/platform/plugins/outlook/mcp/dist/lib/otp.js.map +1 -0
  59. package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +58 -0
  60. package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -0
  61. package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js +127 -0
  62. package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -0
  63. package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts +28 -0
  64. package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts.map +1 -0
  65. package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js +54 -0
  66. package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js.map +1 -0
  67. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +21 -0
  68. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -0
  69. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +38 -0
  70. package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -0
  71. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.d.ts +14 -0
  72. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.d.ts.map +1 -0
  73. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.js +39 -0
  74. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-delete.js.map +1 -0
  75. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +23 -0
  76. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -0
  77. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +44 -0
  78. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -0
  79. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.d.ts +22 -0
  80. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.d.ts.map +1 -0
  81. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.js +90 -0
  82. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-otp-extract.js.map +1 -0
  83. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts +27 -0
  84. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.d.ts.map +1 -0
  85. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +75 -0
  86. package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -0
  87. package/payload/platform/plugins/outlook/references/graph-surfaces.md +57 -1
  88. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +15 -4
  89. package/payload/platform/plugins/scheduling/PLUGIN.md +15 -0
  90. package/payload/platform/plugins/scheduling/mcp/dist/index.js +29 -1
  91. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  92. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js +53 -1
  93. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/booking-reconcile.test.js.map +1 -1
  94. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +21 -0
  95. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  96. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +80 -0
  97. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  98. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts +4 -0
  99. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.d.ts.map +1 -1
  100. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +68 -1
  101. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  102. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.d.ts +2 -0
  103. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.d.ts.map +1 -0
  104. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js +75 -0
  105. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-connector-busy-sync.test.js.map +1 -0
  106. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts +31 -0
  107. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.d.ts.map +1 -0
  108. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js +41 -0
  109. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-connector-busy-sync.js.map +1 -0
  110. package/payload/platform/plugins/voice-mirror/.claude-plugin/plugin.json +21 -0
  111. package/payload/platform/plugins/voice-mirror/PLUGIN.md +72 -0
  112. package/payload/platform/plugins/voice-mirror/lib/mcp-spawn-tee/index.js +193 -0
  113. package/payload/platform/plugins/voice-mirror/lib/mcp-spawn-tee/package.json +3 -0
  114. package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts +3 -0
  115. package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -0
  116. package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +403 -0
  117. package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -0
  118. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/neo4j.d.ts +14 -0
  119. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/neo4j.d.ts.map +1 -0
  120. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/neo4j.js +47 -0
  121. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/neo4j.js.map +1 -0
  122. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts +117 -0
  123. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.d.ts.map +1 -0
  124. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js +138 -0
  125. package/payload/platform/plugins/voice-mirror/mcp/dist/lib/voice-corpus.js.map +1 -0
  126. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts +90 -0
  127. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -0
  128. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +609 -0
  129. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -0
  130. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.d.ts +19 -0
  131. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -0
  132. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js +124 -0
  133. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js.map +1 -0
  134. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts +38 -0
  135. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -0
  136. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js +93 -0
  137. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js.map +1 -0
  138. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts +61 -0
  139. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -0
  140. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js +171 -0
  141. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -0
  142. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts +35 -0
  143. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts.map +1 -0
  144. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js +127 -0
  145. package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js.map +1 -0
  146. package/payload/platform/plugins/voice-mirror/mcp/package-lock.json +1327 -0
  147. package/payload/platform/plugins/voice-mirror/mcp/package.json +19 -0
  148. package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +420 -0
  149. package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +241 -0
  150. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  151. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +12 -0
  152. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  153. package/payload/platform/services/claude-session-manager/dist/http-server.js +1 -1
  154. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  155. package/payload/platform/templates/agents/admin/AGENTS.md +1 -1
  156. package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
  157. package/payload/platform/templates/specialists/agents/personal-assistant.md +6 -2
  158. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/PLUGIN.md +15 -9
  159. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/index.js +2 -0
  160. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/index.js.map +1 -1
  161. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/neo4j.d.ts +5 -0
  162. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/neo4j.d.ts.map +1 -0
  163. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/neo4j.js +41 -0
  164. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/lib/neo4j.js.map +1 -0
  165. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/tools/note-capture.d.ts +25 -0
  166. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/tools/note-capture.d.ts.map +1 -0
  167. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/tools/note-capture.js +114 -0
  168. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/dist/tools/note-capture.js.map +1 -0
  169. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/package-lock.json +137 -0
  170. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/package.json +1 -0
  171. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/index.ts +2 -0
  172. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/lib/__tests__/note-capture.test.ts +55 -0
  173. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/lib/neo4j.ts +51 -0
  174. package/payload/premium-plugins/sitedesk/plugins/sitedesk-job/mcp/src/tools/note-capture.ts +153 -0
  175. package/payload/server/public/assets/{AdminLoginScreens-D9L-CuWi.js → AdminLoginScreens-Bvf4HKmq.js} +1 -1
  176. package/payload/server/public/assets/AdminShell-CaGc8ODG.js +2 -0
  177. package/payload/server/public/assets/{Checkbox-CFdt8ljo.js → Checkbox-2SswiICc.js} +1 -1
  178. package/payload/server/public/assets/admin-B7GPCzpr.js +1 -0
  179. package/payload/server/public/assets/browser-CodHC_MO.js +1 -0
  180. package/payload/server/public/assets/calendar-DH7cyXKF.js +1 -0
  181. package/payload/server/public/assets/chat-BQyPAEMv.js +1 -0
  182. package/payload/server/public/assets/chevron-left-Cmev0R9v.js +1 -0
  183. package/payload/server/public/assets/data-DWxZzMfL.js +1 -0
  184. package/payload/server/public/assets/{graph-DrE7KupQ.js → graph-BToB78LG.js} +2 -2
  185. package/payload/server/public/assets/graph-labels-JRGrstzM.js +1 -0
  186. package/payload/server/public/assets/maximize-2-CEQRv64B.js +1 -0
  187. package/payload/server/public/assets/operator-Cx8cq2wf.js +1 -0
  188. package/payload/server/public/assets/page-D6kzsk18.js +32 -0
  189. package/payload/server/public/assets/page-TCsQy4eP.js +1 -0
  190. package/payload/server/public/assets/public-B61xcmAr.js +1 -0
  191. package/payload/server/public/assets/{rotate-ccw-Cqp4oqHV.js → rotate-ccw-7_3ol0Z4.js} +1 -1
  192. package/payload/server/public/assets/{tasks-bQKOhMga.js → tasks-BY9ajKXf.js} +1 -1
  193. package/payload/server/public/assets/{time-entry-format-BIL4ZaDK.js → time-entry-format-DmNhIJnM.js} +1 -1
  194. package/payload/server/public/assets/useCopyFeedback-C5-GOl5l.js +1 -0
  195. package/payload/server/public/assets/useSelectionMode-UOUVnP9B.js +1 -0
  196. package/payload/server/public/assets/{useSubAccountSwitcher-CVHKTSst.css → useSubAccountSwitcher-DgM1ergs.css} +1 -1
  197. package/payload/server/public/assets/useVoiceRecorder-BAP3yGxs.js +2 -0
  198. package/payload/server/public/browser.html +5 -5
  199. package/payload/server/public/calendar.html +6 -6
  200. package/payload/server/public/chat.html +12 -12
  201. package/payload/server/public/data.html +10 -10
  202. package/payload/server/public/graph.html +11 -11
  203. package/payload/server/public/index.html +13 -13
  204. package/payload/server/public/operator.html +14 -14
  205. package/payload/server/public/public.html +12 -12
  206. package/payload/server/public/tasks.html +5 -5
  207. package/payload/server/server.js +349 -171
  208. package/payload/server/public/assets/AdminShell-DSQrvZXq.js +0 -2
  209. package/payload/server/public/assets/admin-Ch4n-G0j.js +0 -1
  210. package/payload/server/public/assets/browser-B2AIxiuk.js +0 -1
  211. package/payload/server/public/assets/calendar-BvItH3B0.js +0 -1
  212. package/payload/server/public/assets/chat-eQFMaX9y.js +0 -1
  213. package/payload/server/public/assets/chevron-left-D2AnCEyE.js +0 -1
  214. package/payload/server/public/assets/data-BzOmbHoe.js +0 -1
  215. package/payload/server/public/assets/file-download-CK9j_Jx2.js +0 -1
  216. package/payload/server/public/assets/graph-labels-CwEtvIRV.js +0 -1
  217. package/payload/server/public/assets/maximize-2-CuzEom3K.js +0 -1
  218. package/payload/server/public/assets/operator-DlboYPPu.js +0 -1
  219. package/payload/server/public/assets/page-BuWH5gNK.js +0 -1
  220. package/payload/server/public/assets/page-Co9E8U5x.js +0 -32
  221. package/payload/server/public/assets/public-DtFpPMTG.js +0 -1
  222. package/payload/server/public/assets/useCopyFeedback-rB8QuqyH.js +0 -1
  223. package/payload/server/public/assets/useVoiceRecorder-Ctny8qWx.js +0 -2
  224. /package/payload/server/public/assets/{useSubAccountSwitcher-CHEt_m6q.js → useSubAccountSwitcher-DF6bohQA.js} +0 -0
@@ -0,0 +1,609 @@
1
+ /**
2
+ * voice-distil-profile — walk the operator's `human-only` corpus and
3
+ * persist a `:VoiceProfile` style card.
4
+ *
5
+ * Task 462: one `:VoiceProfile` per `(accountId, userId, format)`.
6
+ * `format` ∈ `{text, email, social-post, article, marketing-copy, note}`.
7
+ *
8
+ * - When `format` is supplied, distils that format only.
9
+ * - When `format` is omitted, enumerates distinct format values present
10
+ * in the corpus (nodes where `n.format IS NOT NULL`) and distils each
11
+ * that fires the cadence guard. Returns `skipReason: 'no-format-tagged-content'`
12
+ * when enumeration yields zero formats.
13
+ *
14
+ * Cadence guard: re-runs on ≥20% corpus growth since last profile, OR
15
+ * ≥30 days since `generatedAt`. `force: true` overrides both.
16
+ *
17
+ * Body-text sampling caps at ~120k chars (rough proxy for tokens at
18
+ * 4 chars/token = 30k tokens) — well inside Haiku's 200k context window
19
+ * with headroom for the system prompt + tool schema.
20
+ *
21
+ * Task 424 — the Haiku style-card composition moved into the calling agent's
22
+ * turn. The agent reads the corpus exemplars and feedback intents (this tool
23
+ * returns them when called with `mode='sample'`), composes the style-card
24
+ * YAML, then calls this tool again with `mode='write'` + `styleCardYaml` set.
25
+ */
26
+ import neo4j from "neo4j-driver";
27
+ import { getSession } from "../lib/neo4j.js";
28
+ import { notTrashed } from "../lib/voice-corpus.js";
29
+ import { VOICE_CORPUS_WHERE, voiceCorpusWhereWithFormat, voiceCorpusWhereWithFormatAndAuthor, profileUserIdForScope, FORMAT_VALUES, } from "../lib/voice-corpus.js";
30
+ /**
31
+ * Coerce a neo4j cypher RETURN value to a plain JS number.
32
+ *
33
+ * Neo4j-driver represents INTs as `Integer` objects (high/low 32-bit
34
+ * pair to safely carry int64). `Number()` *usually* works via valueOf()
35
+ * but the safe path is `Integer#toNumber()`. Returns null when the
36
+ * value is null/undefined so cadence-guard arithmetic stays explicit
37
+ * about the "no prior profile" case.
38
+ */
39
+ function toJsNumber(value) {
40
+ if (value === null || value === undefined)
41
+ return null;
42
+ if (typeof value === "number")
43
+ return value;
44
+ if (neo4j.isInt(value))
45
+ return value.toNumber();
46
+ if (typeof value === "string") {
47
+ const parsed = Number(value);
48
+ return Number.isFinite(parsed) ? parsed : null;
49
+ }
50
+ return null;
51
+ }
52
+ const SAMPLE_CHAR_CAP = 120_000;
53
+ const RE_RUN_GROWTH_FRACTION = 0.2;
54
+ const RE_RUN_AGE_DAYS = 30;
55
+ // The agent produces a YAML style card with at minimum these keys:
56
+ // sentenceLengthDistribution, register, favoured, avoided, taboos,
57
+ // openingPattern, closingPattern, exemplarSentences
58
+ // Exemplar sentences should be quoted verbatim from the returned corpus.
59
+ /**
60
+ * Single-format distil. Opens a new session for each call.
61
+ */
62
+ async function distilForFormat(params) {
63
+ const { accountId, userId, format, force = false } = params;
64
+ const scope = params.scope ?? "personal";
65
+ const profileUserId = profileUserIdForScope(scope, userId);
66
+ const anchorLabel = scope === "org" ? "LocalBusiness" : "AdminUser";
67
+ // Org walks the whole account; personal narrows to one author's content.
68
+ const corpusWhere = scope === "org"
69
+ ? voiceCorpusWhereWithFormat(format)
70
+ : voiceCorpusWhereWithFormatAndAuthor(format);
71
+ const corpusParams = scope === "org"
72
+ ? { accountId, format }
73
+ : { accountId, format, voiceAuthor: userId };
74
+ const session = getSession();
75
+ const now = new Date();
76
+ const nowIso = now.toISOString();
77
+ try {
78
+ // 1. Read existing scoped profile (if any) for cadence-guard comparison.
79
+ // Key-only match — the (accountId, profileUserId, format) triple is the
80
+ // unique constraint key, so no anchor traversal is needed on the read.
81
+ const existing = await session.run(`OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
82
+ RETURN elementId(p) AS profileId, p.corpusSize AS corpusSize,
83
+ p.generatedAt AS generatedAt`, { accountId, profileUserId, format });
84
+ const existingRow = existing.records[0];
85
+ const prevProfileId = existingRow?.get("profileId") ?? null;
86
+ const prevCorpusSize = toJsNumber(existingRow?.get("corpusSize"));
87
+ const prevGeneratedAt = existingRow?.get("generatedAt") ?? null;
88
+ // 2. Count corpus for this scope + format.
89
+ const countResult = await session.run(`MATCH (n)
90
+ WHERE ${corpusWhere}
91
+ RETURN count(n) AS c`, corpusParams);
92
+ const corpusSize = toJsNumber(countResult.records[0]?.get("c")) ?? 0;
93
+ if (corpusSize === 0) {
94
+ process.stderr.write(`[voice-distil] scope=${scope} userId=${profileUserId} format=${format} skip reason=empty-corpus\n`);
95
+ return {
96
+ profileId: prevProfileId,
97
+ corpusSize: 0,
98
+ generatedAt: prevGeneratedAt,
99
+ feedbackEntries: 0,
100
+ skipped: true,
101
+ skipReason: "empty-corpus",
102
+ };
103
+ }
104
+ // 3. Cadence guard.
105
+ if (!force && prevProfileId && prevCorpusSize !== null && prevGeneratedAt) {
106
+ const ageMs = now.getTime() - new Date(prevGeneratedAt).getTime();
107
+ const ageDays = ageMs / (24 * 60 * 60 * 1000);
108
+ const growth = prevCorpusSize > 0
109
+ ? (corpusSize - prevCorpusSize) / prevCorpusSize
110
+ : 1;
111
+ const ageTrigger = ageDays >= RE_RUN_AGE_DAYS;
112
+ const growthTrigger = growth >= RE_RUN_GROWTH_FRACTION;
113
+ if (!ageTrigger && !growthTrigger) {
114
+ const reason = ageDays < 1 ? "recent" : "below-threshold";
115
+ process.stderr.write(`[voice-distil] scope=${scope} userId=${profileUserId} format=${format} skip reason=${reason} corpusSize=${corpusSize} prevCorpus=${prevCorpusSize} ageDays=${ageDays.toFixed(1)}\n`);
116
+ return {
117
+ profileId: prevProfileId,
118
+ corpusSize,
119
+ generatedAt: prevGeneratedAt,
120
+ feedbackEntries: 0,
121
+ skipped: true,
122
+ skipReason: reason,
123
+ };
124
+ }
125
+ }
126
+ // 4. Sample corpus bodies up to the char cap. Pick recent first.
127
+ //
128
+ // Body resolution (Task 471):
129
+ // - For a :KnowledgeDocument with HAS_SECTION children, concatenate
130
+ // child :Section bodies in `position` order — KD.body itself is
131
+ // null after the Task 465 server-slicing change.
132
+ // - Other labels (:Message, :SocialPost, :Section chat chunks) read
133
+ // n.body directly via the existing COALESCE fallback chain.
134
+ //
135
+ // `collect(s.body)` is null-skipping in Cypher, so KDs with no children
136
+ // produce sectionBodies = [] and fall through to the COALESCE branch
137
+ // correctly. The `usedSectionWalk` flag is returned for per-exemplar
138
+ // observability.
139
+ const sampled = await session.run(`MATCH (n)
140
+ WHERE ${corpusWhere}
141
+ WITH n, coalesce(n.dateSent, n.datePublished, n.firstMessageAt, n.dateCreated, n.createdAt) AS ts
142
+ OPTIONAL MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
143
+ WHERE 'Section' IN labels(n) AND parent.conversationIdentity IS NOT NULL
144
+ AND ${notTrashed("parent")}
145
+ WITH n, ts, parent
146
+ OPTIONAL MATCH (n)-[:HAS_SECTION]->(s:Section)
147
+ WHERE ${notTrashed("s")}
148
+ WITH n, ts, parent, s ORDER BY s.position
149
+ WITH n, ts, parent, collect(s.body) AS sectionBodies
150
+ WITH n, ts, parent,
151
+ 'KnowledgeDocument' IN labels(n) AND size(sectionBodies) > 0 AS usedSectionWalk,
152
+ CASE
153
+ WHEN 'KnowledgeDocument' IN labels(n) AND size(sectionBodies) > 0
154
+ THEN reduce(acc = '', b IN sectionBodies | acc + b + '\n\n')
155
+ ELSE coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '')
156
+ END AS body
157
+ RETURN elementId(n) AS id,
158
+ labels(n) AS labels,
159
+ body,
160
+ usedSectionWalk,
161
+ parent.conversationIdentity IS NOT NULL AS isConversationChunk,
162
+ ts
163
+ ORDER BY ts IS NULL, ts DESC
164
+ LIMIT 500`, corpusParams);
165
+ const samples = [];
166
+ let sectionWalkCount = 0;
167
+ let nBodyCount = 0;
168
+ let charBudget = SAMPLE_CHAR_CAP;
169
+ for (const r of sampled.records) {
170
+ const body = r.get("body") ?? "";
171
+ const labels = r.get("labels") ?? [];
172
+ const isConversationChunk = Boolean(r.get("isConversationChunk"));
173
+ const usedSectionWalk = Boolean(r.get("usedSectionWalk"));
174
+ const label = isConversationChunk
175
+ ? "Conversation"
176
+ : (labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost"].includes(l)) ?? labels[0] ?? "Unknown");
177
+ if (body.length === 0)
178
+ continue;
179
+ const trimmed = body.length > charBudget ? body.slice(0, charBudget) : body;
180
+ samples.push({ id: r.get("id"), label, body: trimmed });
181
+ if (usedSectionWalk)
182
+ sectionWalkCount += 1;
183
+ else
184
+ nBodyCount += 1;
185
+ charBudget -= trimmed.length;
186
+ if (charBudget <= 0)
187
+ break;
188
+ }
189
+ // 5. Pull feedback intents from existing :VoiceEdit nodes for this scope +
190
+ // format. Keyed by profileUserId: org edits live under the sentinel, so
191
+ // org distil reads org feedback and personal distil reads only this
192
+ // operator's edits — no extra scope predicate needed.
193
+ const feedback = await session.run(`MATCH (e:VoiceEdit {userId: $profileUserId, accountId: $accountId, format: $format})
194
+ RETURN e.intent AS intent ORDER BY e.occurredAt DESC LIMIT 50`, { accountId, profileUserId, format });
195
+ const feedbackIntents = feedback.records
196
+ .map((r) => r.get("intent"))
197
+ .filter((x) => typeof x === "string" && x.length > 0);
198
+ // 6. Mode branch (Task 424).
199
+ const mode = params.mode ?? "sample";
200
+ if (mode === "sample") {
201
+ process.stderr.write(`[voice-distil] scope=${scope} userId=${profileUserId} format=${format} mode=sample corpusSize=${corpusSize} ` +
202
+ `exemplars=${samples.length} feedbackEntries=${feedbackIntents.length} ` +
203
+ `sample-body-source=section-walk:${sectionWalkCount} n.body:${nBodyCount}\n`);
204
+ return {
205
+ profileId: prevProfileId,
206
+ corpusSize,
207
+ generatedAt: prevGeneratedAt,
208
+ feedbackEntries: feedbackIntents.length,
209
+ skipped: true,
210
+ skipReason: "missing-style-card",
211
+ exemplars: samples,
212
+ feedbackIntents,
213
+ };
214
+ }
215
+ const styleCardYaml = (params.styleCardYaml ?? "").trim();
216
+ if (!styleCardYaml) {
217
+ throw new Error("voice-distil-profile: mode='write' requires styleCardYaml (the YAML you composed from the sample-mode response).");
218
+ }
219
+ // 7. Write the :VoiceProfile node — keyed on (accountId, profileUserId,
220
+ // format) — anchored on :AdminUser (personal) or :LocalBusiness (org),
221
+ // plus LEARNED_FROM edges to every sampled node. `scope` is SET after the
222
+ // MERGE (never inside the pattern) so a legacy scope-less personal
223
+ // profile is matched and backfilled rather than duplicated.
224
+ const anchorMatch = scope === "org"
225
+ ? `MATCH (anchor:LocalBusiness {accountId: $accountId})`
226
+ : `MATCH (anchor:AdminUser {accountId: $accountId, userId: $profileUserId})`;
227
+ // Two-step MERGE: the profile node is MERGEd by its unique key alone, then
228
+ // the anchor edge is MERGEd separately. MERGEing the node through the anchor
229
+ // pattern would create a duplicate (constraint error) if the node existed
230
+ // but its anchor edge did not. Keying the node first mirrors the key-only
231
+ // reads and keeps the legacy scope-less backfill matching by key.
232
+ const writeResult = await session.run(`${anchorMatch}
233
+ MERGE (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
234
+ ON CREATE SET p.createdAt = $now
235
+ MERGE (anchor)-[:HAS_VOICE_PROFILE]->(p)
236
+ SET p.scope = $scope,
237
+ p.styleCard = $styleCard,
238
+ p.generatedAt = $now,
239
+ p.corpusSize = $corpusSize,
240
+ p.feedbackEntries = $feedbackEntries,
241
+ p.updatedAt = $now
242
+ WITH p
243
+ UNWIND $sampleIds AS sid
244
+ MATCH (s) WHERE elementId(s) = sid
245
+ MERGE (p)-[:LEARNED_FROM]->(s)
246
+ RETURN elementId(p) AS profileId`, {
247
+ accountId,
248
+ profileUserId,
249
+ format,
250
+ scope,
251
+ styleCard: styleCardYaml,
252
+ now: nowIso,
253
+ corpusSize,
254
+ feedbackEntries: feedbackIntents.length,
255
+ sampleIds: samples.map((s) => s.id),
256
+ });
257
+ if (writeResult.records.length === 0) {
258
+ throw new Error(scope === "org"
259
+ ? `voice-mirror: :LocalBusiness {accountId='${accountId}'} not found. The account org node must exist before an org distil (created at onboarding alongside :AdminUser).`
260
+ : `voice-mirror: :AdminUser {accountId='${accountId}', userId='${userId}'} not found. Onboarding must promote the operator to :AdminUser before distillation.`);
261
+ }
262
+ const profileId = writeResult.records[0].get("profileId");
263
+ process.stderr.write(`[voice-distil] scope=${scope} userId=${profileUserId} anchor=${anchorLabel} format=${format} corpusSize=${corpusSize} generatedAt=${nowIso} feedbackEntries=${feedbackIntents.length}${scope === "org" ? " sentinel=__org__" : ""}\n`);
264
+ return {
265
+ profileId,
266
+ corpusSize,
267
+ generatedAt: nowIso,
268
+ feedbackEntries: feedbackIntents.length,
269
+ skipped: false,
270
+ };
271
+ }
272
+ finally {
273
+ await session.close();
274
+ }
275
+ }
276
+ /**
277
+ * Single-document amend (Task 472).
278
+ *
279
+ * Operator-initiated incremental update: the operator names one or more
280
+ * documents (`nodeIds`), the tool reads only those plus the existing
281
+ * `:VoiceProfile.styleCard` for the format, and the calling agent decides
282
+ * whether the documents move the profile or don't. Same body-resolution
283
+ * and trashed/authorshipMode/label predicates as the full-corpus sample;
284
+ * the corpus-wide format predicate is dropped (the operator picked the
285
+ * docs); the cadence guard does not apply.
286
+ */
287
+ async function amendForFormat(params) {
288
+ const { accountId, userId, format, nodeIds } = params;
289
+ const scope = params.scope ?? "personal";
290
+ const profileUserId = profileUserIdForScope(scope, userId);
291
+ const session = getSession();
292
+ try {
293
+ // 1. Existing scoped profile (may be absent — first-time amend, no prior
294
+ // card). Key-only match on the unique (accountId, profileUserId, format).
295
+ const profileRow = await session.run(`OPTIONAL MATCH (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
296
+ RETURN elementId(p) AS profileId,
297
+ p.styleCard AS styleCard,
298
+ p.corpusSize AS corpusSize,
299
+ p.generatedAt AS generatedAt`, { accountId, profileUserId, format });
300
+ const prow = profileRow.records[0];
301
+ const prevProfileId = prow?.get("profileId") ?? null;
302
+ const existingStyleCard = prow?.get("styleCard") ?? null;
303
+ const prevCorpusSize = toJsNumber(prow?.get("corpusSize")) ?? 0;
304
+ const prevGeneratedAt = prow?.get("generatedAt") ?? null;
305
+ // 2. Per-document eligibility classification. Each input id gets a
306
+ // verdict; the first failing predicate wins. The predicate set mirrors
307
+ // VOICE_CORPUS_WHERE except `format` is dropped (operator picked the
308
+ // docs) and conversation-section sub-pattern is inlined for the
309
+ // per-document case.
310
+ const eligibilityResult = await session.run(`UNWIND $nodeIds AS nid
311
+ OPTIONAL MATCH (n) WHERE elementId(n) = nid
312
+ WITH nid, n,
313
+ CASE
314
+ WHEN n IS NULL THEN 'node-not-found'
315
+ WHEN n.accountId <> $accountId THEN 'wrong-account'
316
+ WHEN NOT coalesce(n.authorshipMode, '') IN ['human-only', 'human-led-agent-assisted']
317
+ THEN 'ineligible-authorship-mode'
318
+ WHEN n:Trashed OR n.deletedAt IS NOT NULL THEN 'trashed'
319
+ WHEN NOT (
320
+ any(lbl IN labels(n) WHERE lbl IN ['KnowledgeDocument', 'Message', 'SocialPost'])
321
+ OR ('Section' IN labels(n) AND EXISTS {
322
+ MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
323
+ WHERE parent.conversationIdentity IS NOT NULL
324
+ AND ${notTrashed("parent")}
325
+ })
326
+ ) THEN 'ineligible-label'
327
+ ELSE 'ok'
328
+ END AS verdict
329
+ RETURN nid, verdict`, { accountId, nodeIds });
330
+ const eligibleIds = [];
331
+ const ineligible = [];
332
+ for (const r of eligibilityResult.records) {
333
+ const nid = r.get("nid");
334
+ const verdict = r.get("verdict");
335
+ if (verdict === "ok")
336
+ eligibleIds.push(nid);
337
+ else
338
+ ineligible.push({ nodeId: nid, reason: verdict });
339
+ }
340
+ // 3. Feedback intents (same as sample) — agent benefits even on a no-op
341
+ // amend ("you've already corrected this voice tic ten times"). Keyed by
342
+ // profileUserId so org amends read org feedback.
343
+ const feedback = await session.run(`MATCH (e:VoiceEdit {userId: $profileUserId, accountId: $accountId, format: $format})
344
+ RETURN e.intent AS intent ORDER BY e.occurredAt DESC LIMIT 50`, { accountId, profileUserId, format });
345
+ const feedbackIntents = feedback.records
346
+ .map((r) => r.get("intent"))
347
+ .filter((x) => typeof x === "string" && x.length > 0);
348
+ if (eligibleIds.length === 0) {
349
+ process.stderr.write(`[voice-distil] scope=${scope} mode=amend userId=${profileUserId} format=${format} nodeIds=${nodeIds.length} ` +
350
+ `existingProfile=${prevProfileId ?? "none"} eligible=0 ineligible=${ineligible.length}\n`);
351
+ return {
352
+ profileId: prevProfileId,
353
+ corpusSize: prevCorpusSize,
354
+ generatedAt: prevGeneratedAt,
355
+ feedbackEntries: feedbackIntents.length,
356
+ skipped: true,
357
+ skipReason: "amend-no-eligible-documents",
358
+ existingStyleCard,
359
+ eligible: 0,
360
+ ineligible,
361
+ exemplars: [],
362
+ feedbackIntents,
363
+ };
364
+ }
365
+ // 4. Body walk for eligible ids — identical body resolution to the
366
+ // full-corpus sample path: KD-with-Section-children concatenates child
367
+ // bodies in `position` order (trashed sections filtered); other labels
368
+ // fall through to the COALESCE chain.
369
+ const bodyResult = await session.run(`UNWIND $eligibleIds AS nid
370
+ MATCH (n) WHERE elementId(n) = nid
371
+ OPTIONAL MATCH (parent:KnowledgeDocument)-[:HAS_SECTION]->(n)
372
+ WHERE 'Section' IN labels(n) AND parent.conversationIdentity IS NOT NULL
373
+ AND ${notTrashed("parent")}
374
+ WITH n, parent
375
+ OPTIONAL MATCH (n)-[:HAS_SECTION]->(s:Section)
376
+ WHERE ${notTrashed("s")}
377
+ WITH n, parent, s ORDER BY s.position
378
+ WITH n, parent, collect(s.body) AS sectionBodies
379
+ WITH n, parent,
380
+ 'KnowledgeDocument' IN labels(n) AND size(sectionBodies) > 0 AS usedSectionWalk,
381
+ CASE
382
+ WHEN 'KnowledgeDocument' IN labels(n) AND size(sectionBodies) > 0
383
+ THEN reduce(acc = '', b IN sectionBodies | acc + b + '\n\n')
384
+ ELSE coalesce(n.body, n.abstract, n.subject, n.title, n.summary, '')
385
+ END AS body
386
+ RETURN elementId(n) AS id,
387
+ labels(n) AS labels,
388
+ body,
389
+ usedSectionWalk,
390
+ parent.conversationIdentity IS NOT NULL AS isConversationChunk`, { eligibleIds });
391
+ const exemplars = [];
392
+ let charBudget = SAMPLE_CHAR_CAP;
393
+ let sectionWalkCount = 0;
394
+ let nBodyCount = 0;
395
+ for (const r of bodyResult.records) {
396
+ const body = r.get("body") ?? "";
397
+ if (body.length === 0)
398
+ continue;
399
+ const labels = r.get("labels") ?? [];
400
+ const isConversationChunk = Boolean(r.get("isConversationChunk"));
401
+ const usedSectionWalk = Boolean(r.get("usedSectionWalk"));
402
+ const label = isConversationChunk
403
+ ? "Conversation"
404
+ : (labels.find((l) => ["KnowledgeDocument", "Message", "SocialPost"].includes(l)) ?? labels[0] ?? "Unknown");
405
+ const trimmed = body.length > charBudget ? body.slice(0, charBudget) : body;
406
+ exemplars.push({ id: r.get("id"), label, body: trimmed });
407
+ if (usedSectionWalk)
408
+ sectionWalkCount += 1;
409
+ else
410
+ nBodyCount += 1;
411
+ charBudget -= trimmed.length;
412
+ if (charBudget <= 0)
413
+ break;
414
+ }
415
+ process.stderr.write(`[voice-distil] scope=${scope} mode=amend userId=${profileUserId} format=${format} nodeIds=${nodeIds.length} ` +
416
+ `existingProfile=${prevProfileId ?? "none"} eligible=${eligibleIds.length} ineligible=${ineligible.length} ` +
417
+ `exemplars=${exemplars.length} sample-body-source=section-walk:${sectionWalkCount} n.body:${nBodyCount}\n`);
418
+ // `skipped: false` because the function DID deliver value — exemplars
419
+ // + existingStyleCard for the agent to act on. The agent decides
420
+ // whether to compose new YAML (calls `mode='write'`) or replies "no
421
+ // material delta"; either is a valid amend outcome, neither is a
422
+ // "skip" in the cadence-guard sense.
423
+ return {
424
+ profileId: prevProfileId,
425
+ corpusSize: prevCorpusSize,
426
+ generatedAt: prevGeneratedAt,
427
+ feedbackEntries: feedbackIntents.length,
428
+ skipped: false,
429
+ existingStyleCard,
430
+ eligible: eligibleIds.length,
431
+ ineligible,
432
+ exemplars,
433
+ feedbackIntents,
434
+ };
435
+ }
436
+ finally {
437
+ await session.close();
438
+ }
439
+ }
440
+ /**
441
+ * Amend write (Task 472). Persists the agent-composed style card after an
442
+ * `amend` sample. Bypasses the cadence guard and the full corpus walk; the
443
+ * only `LEARNED_FROM` edges added are to `amendedFromNodeIds`. Pre-existing
444
+ * `LEARNED_FROM` edges remain via `MERGE`. `corpusSize` on the profile is
445
+ * untouched — the amend doesn't change the operator's qualifying corpus,
446
+ * only the styleCard's interpretation of it.
447
+ */
448
+ async function amendWriteForFormat(params) {
449
+ const { accountId, userId, format, styleCardYaml, amendedFromNodeIds } = params;
450
+ const scope = params.scope ?? "personal";
451
+ const profileUserId = profileUserIdForScope(scope, userId);
452
+ const anchorLabel = scope === "org" ? "LocalBusiness" : "AdminUser";
453
+ const session = getSession();
454
+ const nowIso = new Date().toISOString();
455
+ try {
456
+ // 1. Pre-resolve the amend ids and refuse if none survive. The write
457
+ // Cypher SETs `p` before the UNWIND/MATCH on amend ids, so if every
458
+ // id is stale the profile would be mutated then the function would
459
+ // throw — leaving a silently advanced styleCard the operator never
460
+ // confirmed. Resolving here means the SET never runs on an empty
461
+ // target list, and stale ids surface as a structured error before
462
+ // any write commits. Code-review pass-1 fix.
463
+ const resolve = await session.run(`UNWIND $amendIds AS sid
464
+ MATCH (s) WHERE elementId(s) = sid
465
+ RETURN elementId(s) AS id`, { amendIds: amendedFromNodeIds });
466
+ const resolvedAmendIds = resolve.records.map((r) => r.get("id"));
467
+ if (resolvedAmendIds.length === 0) {
468
+ throw new Error(`voice-mirror: none of amendedFromNodeIds=${JSON.stringify(amendedFromNodeIds)} ` +
469
+ `resolved to live nodes. Confirm the named docs exist and are accessible.`);
470
+ }
471
+ const feedback = await session.run(`MATCH (e:VoiceEdit {userId: $profileUserId, accountId: $accountId, format: $format})
472
+ RETURN count(e) AS c`, { accountId, profileUserId, format });
473
+ const feedbackEntries = toJsNumber(feedback.records[0]?.get("c")) ?? 0;
474
+ const anchorMatch = scope === "org"
475
+ ? `MATCH (anchor:LocalBusiness {accountId: $accountId})`
476
+ : `MATCH (anchor:AdminUser {accountId: $accountId, userId: $profileUserId})`;
477
+ // Two-step MERGE (see distilForFormat): node by unique key, then anchor edge.
478
+ const result = await session.run(`${anchorMatch}
479
+ MERGE (p:VoiceProfile {accountId: $accountId, userId: $profileUserId, format: $format})
480
+ ON CREATE SET p.createdAt = $now, p.corpusSize = 0
481
+ MERGE (anchor)-[:HAS_VOICE_PROFILE]->(p)
482
+ SET p.scope = $scope,
483
+ p.styleCard = $styleCard,
484
+ p.generatedAt = $now,
485
+ p.feedbackEntries = $feedbackEntries,
486
+ p.updatedAt = $now
487
+ WITH p
488
+ UNWIND $amendIds AS sid
489
+ MATCH (s) WHERE elementId(s) = sid
490
+ MERGE (p)-[:LEARNED_FROM]->(s)
491
+ WITH p
492
+ RETURN elementId(p) AS profileId, p.corpusSize AS corpusSize`, {
493
+ accountId,
494
+ profileUserId,
495
+ format,
496
+ scope,
497
+ styleCard: styleCardYaml,
498
+ now: nowIso,
499
+ feedbackEntries,
500
+ amendIds: resolvedAmendIds,
501
+ });
502
+ if (result.records.length === 0) {
503
+ throw new Error(scope === "org"
504
+ ? `voice-mirror: :LocalBusiness {accountId='${accountId}'} not found. The account org node must exist before an org amend-write.`
505
+ : `voice-mirror: :AdminUser {accountId='${accountId}', userId='${userId}'} not found. ` +
506
+ `Onboarding must promote the operator to :AdminUser before distillation.`);
507
+ }
508
+ const profileId = result.records[0].get("profileId");
509
+ const corpusSize = toJsNumber(result.records[0].get("corpusSize")) ?? 0;
510
+ process.stderr.write(`[voice-distil] scope=${scope} mode=write userId=${profileUserId} anchor=${anchorLabel} format=${format} amended=true ` +
511
+ `amendedFromNodeIds=${amendedFromNodeIds.length} corpusSize=${corpusSize} ` +
512
+ `generatedAt=${nowIso} feedbackEntries=${feedbackEntries}\n`);
513
+ return {
514
+ profileId,
515
+ corpusSize,
516
+ generatedAt: nowIso,
517
+ feedbackEntries,
518
+ skipped: false,
519
+ };
520
+ }
521
+ finally {
522
+ await session.close();
523
+ }
524
+ }
525
+ export async function voiceDistilProfile(params) {
526
+ const { accountId, userId, force = false } = params;
527
+ const scope = params.scope ?? "personal";
528
+ if (!accountId || !userId) {
529
+ throw new Error("voice-distil-profile: accountId and userId required");
530
+ }
531
+ // Amend sample (Task 472): operator-initiated single-document update.
532
+ if (params.mode === "amend") {
533
+ if (!params.format) {
534
+ throw new Error("voice-distil-profile: mode='amend' requires format");
535
+ }
536
+ const nodeIds = params.nodeIds ?? [];
537
+ if (nodeIds.length === 0) {
538
+ throw new Error("voice-distil-profile: mode='amend' requires nodeIds (at least one)");
539
+ }
540
+ return amendForFormat({
541
+ accountId,
542
+ userId,
543
+ format: params.format,
544
+ nodeIds,
545
+ scope,
546
+ });
547
+ }
548
+ // Amend write (Task 472): persist after the agent composed from an amend
549
+ // sample. Triggered by `mode='write'` + non-empty `amendedFromNodeIds`.
550
+ // Distinct from the full-corpus write — bypasses cadence + corpus walk.
551
+ if (params.mode === "write" &&
552
+ params.amendedFromNodeIds &&
553
+ params.amendedFromNodeIds.length > 0) {
554
+ if (!params.format) {
555
+ throw new Error("voice-distil-profile: mode='write' with amendedFromNodeIds requires format");
556
+ }
557
+ const styleCardYaml = (params.styleCardYaml ?? "").trim();
558
+ if (!styleCardYaml) {
559
+ throw new Error("voice-distil-profile: mode='write' requires styleCardYaml (the YAML you composed from the amend-sample response).");
560
+ }
561
+ return amendWriteForFormat({
562
+ accountId,
563
+ userId,
564
+ format: params.format,
565
+ styleCardYaml,
566
+ amendedFromNodeIds: params.amendedFromNodeIds,
567
+ scope,
568
+ });
569
+ }
570
+ // Single-format path.
571
+ if (params.format !== undefined) {
572
+ return distilForFormat({ ...params, format: params.format, force });
573
+ }
574
+ // Multi-format path: enumerate distinct format values present in the corpus.
575
+ // Scope-aware: personal enumerates only the operator's authored content, org
576
+ // enumerates the whole account.
577
+ const session = getSession();
578
+ let formats = [];
579
+ try {
580
+ const enumResult = await session.run(`MATCH (n)
581
+ WHERE ${VOICE_CORPUS_WHERE} AND n.format IS NOT NULL${scope === "personal" ? " AND n.voiceAuthor = $voiceAuthor" : ""}
582
+ RETURN DISTINCT n.format AS fmt`, scope === "personal" ? { accountId, voiceAuthor: userId } : { accountId });
583
+ formats = enumResult.records
584
+ .map((r) => r.get("fmt"))
585
+ .filter((f) => FORMAT_VALUES.includes(f));
586
+ }
587
+ finally {
588
+ await session.close();
589
+ }
590
+ if (formats.length === 0) {
591
+ process.stderr.write(`[voice-distil] scope=${scope} userId=${profileUserIdForScope(scope, userId)} skip reason=no-format-tagged-content\n`);
592
+ return {
593
+ profileId: null,
594
+ corpusSize: 0,
595
+ generatedAt: null,
596
+ feedbackEntries: 0,
597
+ skipped: true,
598
+ skipReason: "no-format-tagged-content",
599
+ };
600
+ }
601
+ // Distil each discovered format sequentially.
602
+ const results = [];
603
+ for (const format of formats) {
604
+ const result = await distilForFormat({ ...params, format, force });
605
+ results.push({ ...result, format });
606
+ }
607
+ return results;
608
+ }
609
+ //# sourceMappingURL=voice-distil-profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-distil-profile.js","sourceRoot":"","sources":["../../src/tools/voice-distil-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,MAAM,cAAc,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EACL,kBAAkB,EAClB,0BAA0B,EAC1B,mCAAmC,EACnC,qBAAqB,EACrB,aAAa,GAGd,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAQ,KAAoC,CAAC,QAAQ,EAAE,CAAC;IAChF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AACnC,MAAM,eAAe,GAAG,EAAE,CAAC;AA+F3B,mEAAmE;AACnE,qEAAqE;AACrE,sDAAsD;AACtD,yEAAyE;AAEzE;;GAEG;AACH,KAAK,UAAU,eAAe,CAC5B,MAGG;IAEH,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,MAAM,CAAC;IAC5D,MAAM,KAAK,GAAe,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC;IACrD,MAAM,aAAa,GAAG,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC;IACpE,yEAAyE;IACzE,MAAM,WAAW,GACf,KAAK,KAAK,KAAK;QACb,CAAC,CAAC,0BAA0B,CAAC,MAAM,CAAC;QACpC,CAAC,CAAC,mCAAmC,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,YAAY,GAChB,KAAK,KAAK,KAAK;QACb,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE;QACvB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEjC,IAAI,CAAC;QACH,yEAAyE;QACzE,2EAA2E;QAC3E,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC;;2CAEqC,EACrC,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CACrC,CAAC;QACF,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,aAAa,GAAI,WAAW,EAAE,GAAG,CAAC,WAAW,CAAmB,IAAI,IAAI,CAAC;QAC/E,MAAM,cAAc,GAAG,UAAU,CAAC,WAAW,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;QAClE,MAAM,eAAe,GAAI,WAAW,EAAE,GAAG,CAAC,aAAa,CAAmB,IAAI,IAAI,CAAC;QAEnF,2CAA2C;QAC3C,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC;eACS,WAAW;4BACE,EACtB,YAAY,CACb,CAAC;QACF,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAErE,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,WAAW,aAAa,WAAW,MAAM,6BAA6B,CACpG,CAAC;YACF,OAAO;gBACL,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,eAAe;gBAC5B,eAAe,EAAE,CAAC;gBAClB,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,cAAc;aAC3B,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,IAAI,CAAC,KAAK,IAAI,aAAa,IAAI,cAAc,KAAK,IAAI,IAAI,eAAe,EAAE,CAAC;YAC1E,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC;YAClE,MAAM,OAAO,GAAG,KAAK,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC9C,MAAM,MAAM,GACV,cAAc,GAAG,CAAC;gBAChB,CAAC,CAAC,CAAC,UAAU,GAAG,cAAc,CAAC,GAAG,cAAc;gBAChD,CAAC,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAG,OAAO,IAAI,eAAe,CAAC;YAC9C,MAAM,aAAa,GAAG,MAAM,IAAI,sBAAsB,CAAC;YACvD,IAAI,CAAC,UAAU,IAAI,CAAC,aAAa,EAAE,CAAC;gBAClC,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC;gBAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,WAAW,aAAa,WAAW,MAAM,gBAAgB,MAAM,eAAe,UAAU,eAAe,cAAc,YAAY,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACrL,CAAC;gBACF,OAAO;oBACL,SAAS,EAAE,aAAa;oBACxB,UAAU;oBACV,WAAW,EAAE,eAAe;oBAC5B,eAAe,EAAE,CAAC;oBAClB,OAAO,EAAE,IAAI;oBACb,UAAU,EAAE,MAAM;iBACnB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,iEAAiE;QACjE,EAAE;QACF,8BAA8B;QAC9B,sEAAsE;QACtE,oEAAoE;QACpE,qDAAqD;QACrD,sEAAsE;QACtE,gEAAgE;QAChE,EAAE;QACF,wEAAwE;QACxE,qEAAqE;QACrE,qEAAqE;QACrE,iBAAiB;QACjB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B;eACS,WAAW;;;;eAIX,UAAU,CAAC,QAAQ,CAAC;;;eAGpB,UAAU,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;iBAiBb,EACX,YAAY,CACb,CAAC;QACF,MAAM,OAAO,GAAkD,EAAE,CAAC;QAClE,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,UAAU,GAAG,eAAe,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAChC,MAAM,IAAI,GAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAmB,IAAI,EAAE,CAAC;YACpD,MAAM,MAAM,GAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAc,IAAI,EAAE,CAAC;YACnD,MAAM,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC;YAClE,MAAM,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,mBAAmB;gBAC/B,CAAC,CAAC,cAAc;gBAChB,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACjB,CAAC,mBAAmB,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC3D,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAChC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5E,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAClE,IAAI,eAAe;gBAAE,gBAAgB,IAAI,CAAC,CAAC;;gBACtC,UAAU,IAAI,CAAC,CAAC;YACrB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAC7B,IAAI,UAAU,IAAI,CAAC;gBAAE,MAAM;QAC7B,CAAC;QAED,2EAA2E;QAC3E,2EAA2E;QAC3E,uEAAuE;QACvE,yDAAyD;QACzD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC;qEAC+D,EAC/D,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CACrC,CAAC;QACF,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO;aACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAkB,CAAC;aAC5C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAErE,6BAA6B;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC;QACrC,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,WAAW,aAAa,WAAW,MAAM,2BAA2B,UAAU,GAAG;gBAC5G,aAAa,OAAO,CAAC,MAAM,oBAAoB,eAAe,CAAC,MAAM,GAAG;gBACxE,mCAAmC,gBAAgB,WAAW,UAAU,IAAI,CAC/E,CAAC;YACF,OAAO;gBACL,SAAS,EAAE,aAAa;gBACxB,UAAU;gBACV,WAAW,EAAE,eAAe;gBAC5B,eAAe,EAAE,eAAe,CAAC,MAAM;gBACvC,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,oBAAoB;gBAChC,SAAS,EAAE,OAAO;gBAClB,eAAe;aAChB,CAAC;QACJ,CAAC;QAED,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,kHAAkH,CACnH,CAAC;QACJ,CAAC;QAED,wEAAwE;QACxE,0EAA0E;QAC1E,6EAA6E;QAC7E,sEAAsE;QACtE,+DAA+D;QAC/D,MAAM,WAAW,GACf,KAAK,KAAK,KAAK;YACb,CAAC,CAAC,sDAAsD;YACxD,CAAC,CAAC,0EAA0E,CAAC;QACjF,2EAA2E;QAC3E,6EAA6E;QAC7E,0EAA0E;QAC1E,0EAA0E;QAC1E,kEAAkE;QAClE,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,GAAG,WAAW;;;;;;;;;;;;;;wCAcoB,EAClC;YACE,SAAS;YACT,aAAa;YACb,MAAM;YACN,KAAK;YACL,SAAS,EAAE,aAAa;YACxB,GAAG,EAAE,MAAM;YACX,UAAU;YACV,eAAe,EAAE,eAAe,CAAC,MAAM;YACvC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CACF,CAAC;QACF,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,KAAK,KAAK,KAAK;gBACb,CAAC,CAAC,4CAA4C,SAAS,kHAAkH;gBACzK,CAAC,CAAC,wCAAwC,SAAS,cAAc,MAAM,uFAAuF,CACjK,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAW,CAAC;QAEpE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,WAAW,aAAa,WAAW,WAAW,WAAW,MAAM,eAAe,UAAU,gBAAgB,MAAM,oBAAoB,eAAe,CAAC,MAAM,GAAG,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,IAAI,CACvO,CAAC;QAEF,OAAO;YACL,SAAS;YACT,UAAU;YACV,WAAW,EAAE,MAAM;YACnB,eAAe,EAAE,eAAe,CAAC,MAAM;YACvC,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,KAAK,UAAU,cAAc,CAAC,MAM7B;IACC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IACtD,MAAM,KAAK,GAAe,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC;IACrD,MAAM,aAAa,GAAG,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,yEAAyE;QACzE,6EAA6E;QAC7E,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC;;;;2CAIqC,EACrC,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CACrC,CAAC;QACF,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,aAAa,GAAI,IAAI,EAAE,GAAG,CAAC,WAAW,CAAmB,IAAI,IAAI,CAAC;QACxE,MAAM,iBAAiB,GAAI,IAAI,EAAE,GAAG,CAAC,WAAW,CAAmB,IAAI,IAAI,CAAC;QAC5E,MAAM,cAAc,GAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,eAAe,GAAI,IAAI,EAAE,GAAG,CAAC,aAAa,CAAmB,IAAI,IAAI,CAAC;QAE5E,mEAAmE;QACnE,0EAA0E;QAC1E,wEAAwE;QACxE,mEAAmE;QACnE,wBAAwB;QACxB,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,GAAG,CACzC;;;;;;;;;;;;;;0BAcoB,UAAU,CAAC,QAAQ,CAAC;;;;;2BAKnB,EACrB,EAAE,SAAS,EAAE,OAAO,EAAE,CACvB,CAAC;QAEF,MAAM,WAAW,GAAa,EAAE,CAAC;QACjC,MAAM,UAAU,GAAyC,EAAE,CAAC;QAC5D,KAAK,MAAM,CAAC,IAAI,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC1C,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAW,CAAC;YACnC,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAW,CAAC;YAC3C,IAAI,OAAO,KAAK,IAAI;gBAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;gBACvC,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,wEAAwE;QACxE,2EAA2E;QAC3E,oDAAoD;QACpD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC;qEAC+D,EAC/D,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CACrC,CAAC;QACF,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO;aACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAkB,CAAC;aAC5C,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAErE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,sBAAsB,aAAa,WAAW,MAAM,YAAY,OAAO,CAAC,MAAM,GAAG;gBAC5G,mBAAmB,aAAa,IAAI,MAAM,0BAA0B,UAAU,CAAC,MAAM,IAAI,CAC5F,CAAC;YACF,OAAO;gBACL,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,cAAc;gBAC1B,WAAW,EAAE,eAAe;gBAC5B,eAAe,EAAE,eAAe,CAAC,MAAM;gBACvC,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,6BAA6B;gBACzC,iBAAiB;gBACjB,QAAQ,EAAE,CAAC;gBACX,UAAU;gBACV,SAAS,EAAE,EAAE;gBACb,eAAe;aAChB,CAAC;QACJ,CAAC;QAED,mEAAmE;QACnE,0EAA0E;QAC1E,0EAA0E;QAC1E,yCAAyC;QACzC,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC;;;;eAIS,UAAU,CAAC,QAAQ,CAAC;;;eAGpB,UAAU,CAAC,GAAG,CAAC;;;;;;;;;;;;;;6EAc+C,EACvE,EAAE,WAAW,EAAE,CAChB,CAAC;QAEF,MAAM,SAAS,GAA0B,EAAE,CAAC;QAC5C,IAAI,UAAU,GAAG,eAAe,CAAC;QACjC,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,MAAM,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,IAAI,GAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAmB,IAAI,EAAE,CAAC;YACpD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAChC,MAAM,MAAM,GAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAc,IAAI,EAAE,CAAC;YACnD,MAAM,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC;YAClE,MAAM,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,mBAAmB;gBAC/B,CAAC,CAAC,cAAc;gBAChB,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACjB,CAAC,mBAAmB,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC3D,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;YACjC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5E,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YACpE,IAAI,eAAe;gBAAE,gBAAgB,IAAI,CAAC,CAAC;;gBACtC,UAAU,IAAI,CAAC,CAAC;YACrB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;YAC7B,IAAI,UAAU,IAAI,CAAC;gBAAE,MAAM;QAC7B,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,sBAAsB,aAAa,WAAW,MAAM,YAAY,OAAO,CAAC,MAAM,GAAG;YAC5G,mBAAmB,aAAa,IAAI,MAAM,aAAa,WAAW,CAAC,MAAM,eAAe,UAAU,CAAC,MAAM,GAAG;YAC5G,aAAa,SAAS,CAAC,MAAM,oCAAoC,gBAAgB,WAAW,UAAU,IAAI,CAC7G,CAAC;QAEF,sEAAsE;QACtE,iEAAiE;QACjE,oEAAoE;QACpE,iEAAiE;QACjE,qCAAqC;QACrC,OAAO;YACL,SAAS,EAAE,aAAa;YACxB,UAAU,EAAE,cAAc;YAC1B,WAAW,EAAE,eAAe;YAC5B,eAAe,EAAE,eAAe,CAAC,MAAM;YACvC,OAAO,EAAE,KAAK;YACd,iBAAiB;YACjB,QAAQ,EAAE,WAAW,CAAC,MAAM;YAC5B,UAAU;YACV,SAAS;YACT,eAAe;SAChB,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,mBAAmB,CAAC,MAOlC;IACC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC;IAChF,MAAM,KAAK,GAAe,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC;IACrD,MAAM,aAAa,GAAG,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC;IACpE,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,CAAC;QACH,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,oEAAoE;QACpE,qEAAqE;QACrE,gDAAgD;QAChD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B;;iCAE2B,EAC3B,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CACjC,CAAC;QACF,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAW,CAC7B,CAAC;QACF,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,4CAA4C,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,GAAG;gBAC/E,0EAA0E,CAC7E,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC;4BACsB,EACtB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CACrC,CAAC;QACF,MAAM,eAAe,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QAEvE,MAAM,WAAW,GACf,KAAK,KAAK,KAAK;YACb,CAAC,CAAC,sDAAsD;YACxD,CAAC,CAAC,0EAA0E,CAAC;QACjF,8EAA8E;QAC9E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,GAAG,WAAW;;;;;;;;;;;;;;oEAcgD,EAC9D;YACE,SAAS;YACT,aAAa;YACb,MAAM;YACN,KAAK;YACL,SAAS,EAAE,aAAa;YACxB,GAAG,EAAE,MAAM;YACX,eAAe;YACf,QAAQ,EAAE,gBAAgB;SAC3B,CACF,CAAC;QACF,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,KAAK,KAAK,KAAK;gBACb,CAAC,CAAC,4CAA4C,SAAS,0EAA0E;gBACjI,CAAC,CAAC,wCAAwC,SAAS,cAAc,MAAM,gBAAgB;oBACrF,yEAAyE,CAC9E,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAW,CAAC;QAC/D,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;QAExE,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,sBAAsB,aAAa,WAAW,WAAW,WAAW,MAAM,gBAAgB;YACrH,sBAAsB,kBAAkB,CAAC,MAAM,eAAe,UAAU,GAAG;YAC3E,eAAe,MAAM,oBAAoB,eAAe,IAAI,CAC/D,CAAC;QAEF,OAAO;YACL,SAAS;YACT,UAAU;YACV,WAAW,EAAE,MAAM;YACnB,eAAe;YACf,OAAO,EAAE,KAAK;SACf,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAgC;IAEhC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,MAAM,CAAC;IACpD,MAAM,KAAK,GAAe,MAAM,CAAC,KAAK,IAAI,UAAU,CAAC;IACrD,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,sEAAsE;IACtE,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,oEAAoE,CACrE,CAAC;QACJ,CAAC;QACD,OAAO,cAAc,CAAC;YACpB,SAAS;YACT,MAAM;YACN,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO;YACP,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,IACE,MAAM,CAAC,IAAI,KAAK,OAAO;QACvB,MAAM,CAAC,kBAAkB;QACzB,MAAM,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EACpC,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;QACJ,CAAC;QACD,OAAO,mBAAmB,CAAC;YACzB,SAAS;YACT,MAAM;YACN,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,aAAa;YACb,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;YAC7C,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED,sBAAsB;IACtB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,eAAe,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,gCAAgC;IAChC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAkB,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAClC;eACS,kBAAkB,4BAA4B,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE;uCACrF,EACjC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAC1E,CAAC;QACF,OAAO,GAAG,UAAU,CAAC,OAAO;aACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAW,CAAC;aAClC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAC7B,aAAmC,CAAC,QAAQ,CAAC,CAAC,CAAC,CACjD,CAAC;IACN,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,KAAK,WAAW,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,yCAAyC,CACtH,CAAC;QACF,OAAO;YACL,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,IAAI;YACjB,eAAe,EAAE,CAAC;YAClB,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,0BAA0B;SACvC,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface VoiceIngestSessionTextParams {
2
+ turns: string[];
3
+ accountId: string;
4
+ userId: string;
5
+ sessionId?: string;
6
+ }
7
+ export interface VoiceIngestSessionTextResult {
8
+ ingestedCount: number;
9
+ skippedCount: number;
10
+ sessionId: string | null;
11
+ }
12
+ /**
13
+ * Applies the "real operator text" exclusions to a single supplied turn.
14
+ * Returns the trimmed text to ingest, or null when the turn should be skipped.
15
+ * Exported for testability (no DB required).
16
+ */
17
+ export declare function filterOperatorText(text: unknown): string | null;
18
+ export declare function voiceIngestSessionText(params: VoiceIngestSessionTextParams): Promise<VoiceIngestSessionTextResult>;
19
+ //# sourceMappingURL=voice-ingest-session-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice-ingest-session-text.d.ts","sourceRoot":"","sources":["../../src/tools/voice-ingest-session-text.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAQ/D;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAkGvC"}