@rubytech/create-maxy-code 0.1.158 → 0.1.161

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 (359) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts +1 -1
  3. package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/admin-conversation-purge/dist/index.js +4 -4
  5. package/payload/platform/lib/admin-conversation-purge/dist/index.js.map +1 -1
  6. package/payload/platform/lib/admin-conversation-purge/src/index.ts +5 -5
  7. package/payload/platform/lib/graph-search/src/__tests__/fulltext-coverage.test.ts +48 -17
  8. package/payload/platform/lib/graph-style/dist/index.js +2 -2
  9. package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
  10. package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +1 -1
  11. package/payload/platform/lib/graph-style/src/index.ts +2 -2
  12. package/payload/platform/lib/graph-trash/dist/index.js +1 -1
  13. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  14. package/payload/platform/lib/graph-trash/src/index.ts +1 -1
  15. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +1 -1
  16. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
  17. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +3 -3
  18. package/payload/platform/lib/graph-write/dist/conversation-provenance.js +5 -5
  19. package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -1
  20. package/payload/platform/lib/graph-write/dist/index.js +1 -1
  21. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  22. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +1 -1
  23. package/payload/platform/lib/graph-write/src/conversation-provenance.ts +8 -8
  24. package/payload/platform/lib/graph-write/src/index.ts +1 -1
  25. package/payload/platform/neo4j/schema.cypher +86 -46
  26. package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
  27. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  28. package/payload/platform/plugins/admin/PLUGIN.md +8 -4
  29. package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh +1 -1
  30. package/payload/platform/plugins/admin/hooks/__tests__/per-turn-graph-pass-gate.test.sh +361 -0
  31. package/payload/platform/plugins/admin/hooks/__tests__/pre-turn-graph-pass.test.sh +13 -13
  32. package/payload/platform/plugins/admin/hooks/__tests__/turn-completed-graph-write.test.sh +1 -1
  33. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +2 -2
  34. package/payload/platform/plugins/admin/hooks/lib/admin-graph-pass-common.sh +102 -0
  35. package/payload/platform/plugins/admin/hooks/per-turn-graph-pass-gate.sh +106 -0
  36. package/payload/platform/plugins/admin/hooks/pre-turn-graph-pass.sh +15 -8
  37. package/payload/platform/plugins/admin/hooks/turn-completed-graph-write.sh +4 -4
  38. package/payload/platform/plugins/admin/mcp/dist/index.js +65 -24
  39. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  40. package/payload/platform/plugins/admin/skills/business-profile/SKILL.md +1 -1
  41. package/payload/platform/plugins/admin/skills/session-management/SKILL.md +2 -2
  42. package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +5 -5
  43. package/payload/platform/plugins/admin/skills/stream-log-review/references/analysis-patterns.md +6 -6
  44. package/payload/platform/plugins/business-assistant/references/profiling.md +1 -1
  45. package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
  46. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +2 -2
  47. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  48. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +3 -3
  49. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts +1 -1
  50. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +2 -2
  51. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts +1 -1
  52. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.d.ts.map +1 -1
  53. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js +4 -4
  54. package/payload/platform/plugins/contacts/mcp/dist/tools/group-create.js.map +1 -1
  55. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js +10 -10
  56. package/payload/platform/plugins/contacts/mcp/dist/tools/group-manage.js.map +1 -1
  57. package/payload/platform/plugins/docs/references/admin-session.md +6 -6
  58. package/payload/platform/plugins/docs/references/admin-ui.md +1 -1
  59. package/payload/platform/plugins/docs/references/internals.md +5 -5
  60. package/payload/platform/plugins/docs/references/plugins-guide.md +6 -5
  61. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  62. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  63. package/payload/platform/plugins/email/PLUGIN.md +19 -2
  64. package/payload/platform/plugins/email/mcp/dist/index.js +78 -0
  65. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  66. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.d.ts +8 -0
  67. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.d.ts.map +1 -0
  68. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.js +177 -0
  69. package/payload/platform/plugins/email/mcp/dist/lib/conversation-archive-dispatch.js.map +1 -0
  70. package/payload/platform/plugins/email/mcp/dist/lib/embedding.js +2 -2
  71. package/payload/platform/plugins/email/mcp/dist/lib/embedding.js.map +1 -1
  72. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +6 -54
  73. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -1
  74. package/payload/platform/plugins/email/mcp/dist/lib/graph.js +12 -300
  75. package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -1
  76. package/payload/platform/plugins/email/mcp/dist/lib/imap.d.ts +1 -1
  77. package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.d.ts +23 -0
  78. package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.d.ts.map +1 -0
  79. package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.js +15 -0
  80. package/payload/platform/plugins/email/mcp/dist/lib/ingest-batch.js.map +1 -0
  81. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts +6 -0
  82. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.d.ts.map +1 -0
  83. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js +89 -0
  84. package/payload/platform/plugins/email/mcp/dist/tools/email-classify.js.map +1 -0
  85. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.d.ts +15 -0
  86. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.d.ts.map +1 -0
  87. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js +49 -0
  88. package/payload/platform/plugins/email/mcp/dist/tools/email-fetch.js.map +1 -0
  89. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts +20 -0
  90. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.d.ts.map +1 -0
  91. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js +91 -0
  92. package/payload/platform/plugins/email/mcp/dist/tools/email-ingest.js.map +1 -0
  93. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -1
  94. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +32 -52
  95. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
  96. package/payload/platform/plugins/email/references/email-reference.md +19 -83
  97. package/payload/platform/plugins/email/skills/email-ingest/SKILL.md +87 -0
  98. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +10 -37
  99. package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +1 -0
  100. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  101. package/payload/platform/plugins/memory/PLUGIN.md +11 -2
  102. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +139 -439
  103. package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +15 -47
  104. package/payload/platform/plugins/memory/mcp/dist/index.js +197 -58
  105. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  106. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +43 -27
  107. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
  108. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-delete-gate.test.d.ts +2 -0
  109. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-delete-gate.test.d.ts.map +1 -0
  110. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-delete-gate.test.js +62 -0
  111. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/label-delete-gate.test.js.map +1 -0
  112. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +5 -0
  113. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
  114. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js +1 -1
  115. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js.map +1 -1
  116. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts +42 -25
  117. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts.map +1 -1
  118. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js +23 -17
  119. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js.map +1 -1
  120. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/email.d.ts +3 -0
  121. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/email.d.ts.map +1 -0
  122. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/email.js +61 -0
  123. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/email.js.map +1 -0
  124. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +1 -0
  125. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -1
  126. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +3 -0
  127. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -1
  128. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +1 -1
  129. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -1
  130. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +1 -0
  131. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -1
  132. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js +2 -2
  133. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js.map +1 -1
  134. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts +2 -0
  135. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.d.ts.map +1 -1
  136. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js +9 -3
  137. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-pipeline/derive-keys.js.map +1 -1
  138. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts +3 -3
  139. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.d.ts.map +1 -1
  140. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js +6 -6
  141. package/payload/platform/plugins/memory/mcp/dist/lib/graph-prune.js.map +1 -1
  142. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.d.ts +41 -0
  143. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.d.ts.map +1 -0
  144. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.js +71 -0
  145. package/payload/platform/plugins/memory/mcp/dist/lib/kd-classify-gate.js.map +1 -0
  146. package/payload/platform/plugins/memory/mcp/dist/lib/label-delete-gate.d.ts +18 -0
  147. package/payload/platform/plugins/memory/mcp/dist/lib/label-delete-gate.d.ts.map +1 -0
  148. package/payload/platform/plugins/memory/mcp/dist/lib/label-delete-gate.js +31 -0
  149. package/payload/platform/plugins/memory/mcp/dist/lib/label-delete-gate.js.map +1 -0
  150. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +1 -1
  151. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
  152. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +2 -2
  153. package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +2 -2
  154. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +1 -1
  155. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -1
  156. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts +5 -0
  157. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.d.ts.map +1 -1
  158. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +162 -78
  159. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
  160. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js +1 -1
  161. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-enrich-rejection.test.js.map +1 -1
  162. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-memory-expunge-emit.test.js +3 -3
  163. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-memory-expunge-emit.test.js.map +1 -1
  164. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js +2 -0
  165. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-normalisers-source-agnosticism.test.js.map +1 -1
  166. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js +2 -2
  167. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/log-ingest.test.js.map +1 -1
  168. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js +2 -0
  169. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js.map +1 -1
  170. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js +6 -0
  171. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-emit.test.js.map +1 -1
  172. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-reserved-label.test.d.ts +2 -0
  173. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-reserved-label.test.d.ts.map +1 -0
  174. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-reserved-label.test.js +141 -0
  175. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-delete-reserved-label.test.js.map +1 -0
  176. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js +8 -8
  177. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js.map +1 -1
  178. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-reserved-label.test.d.ts +2 -0
  179. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-reserved-label.test.d.ts.map +1 -0
  180. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-reserved-label.test.js +86 -0
  181. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-reserved-label.test.js.map +1 -0
  182. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js +2 -2
  183. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js.map +1 -1
  184. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +33 -34
  185. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
  186. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +94 -273
  187. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
  188. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-list-chunks.d.ts +43 -0
  189. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-list-chunks.d.ts.map +1 -0
  190. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-list-chunks.js +96 -0
  191. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-list-chunks.js.map +1 -0
  192. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts +1 -1
  193. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.d.ts.map +1 -1
  194. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js +2 -2
  195. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-memory-expunge.js.map +1 -1
  196. package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.d.ts +6 -0
  197. package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.d.ts.map +1 -0
  198. package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.js +73 -0
  199. package/payload/platform/plugins/memory/mcp/dist/tools/kd-classify.js.map +1 -0
  200. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  201. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +0 -1
  202. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  203. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.d.ts +2 -2
  204. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.d.ts.map +1 -1
  205. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js +4 -4
  206. package/payload/platform/plugins/memory/mcp/dist/tools/memory-brain-capture-recent.js.map +1 -1
  207. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts +6 -0
  208. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -1
  209. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +2 -0
  210. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -1
  211. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +8 -0
  212. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  213. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +40 -5
  214. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  215. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +8 -1
  216. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
  217. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +25 -9
  218. package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
  219. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +1 -1
  220. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -1
  221. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +2 -2
  222. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -1
  223. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +11 -0
  224. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  225. package/payload/platform/plugins/memory/mcp/dist/tools/memory-node-exists.d.ts +28 -0
  226. package/payload/platform/plugins/memory/mcp/dist/tools/memory-node-exists.d.ts.map +1 -0
  227. package/payload/platform/plugins/memory/mcp/dist/tools/memory-node-exists.js +46 -0
  228. package/payload/platform/plugins/memory/mcp/dist/tools/memory-node-exists.js.map +1 -0
  229. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.d.ts +1 -1
  230. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.js +1 -1
  231. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +1 -1
  232. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +1 -1
  233. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +2 -2
  234. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +1 -1
  235. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  236. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +26 -0
  237. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  238. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +2 -2
  239. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  240. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts +1 -1
  241. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.d.ts.map +1 -1
  242. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +11 -11
  243. package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
  244. package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +1 -1
  245. package/payload/platform/plugins/memory/mcp/scripts/graph/fixture.cypher +1 -1
  246. package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -4
  247. package/payload/platform/plugins/memory/references/graph-primitives.md +1 -1
  248. package/payload/platform/plugins/memory/references/schema-base.md +8 -10
  249. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +100 -116
  250. package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +74 -24
  251. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +1 -1
  252. package/payload/platform/plugins/notion-import/skills/notion-import/SKILL.md +1 -1
  253. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/SKILL.md +1 -1
  254. package/payload/platform/plugins/substack-import/.claude-plugin/plugin.json +8 -0
  255. package/payload/platform/plugins/substack-import/PLUGIN.md +34 -0
  256. package/payload/platform/plugins/substack-import/skills/substack-import/SKILL.md +182 -0
  257. package/payload/platform/plugins/substack-import/skills/substack-import/references/archive-shape.md +68 -0
  258. package/payload/platform/plugins/substack-import/skills/substack-import/references/attachments.md +72 -0
  259. package/payload/platform/plugins/substack-import/skills/substack-import/references/engagement.md +61 -0
  260. package/payload/platform/plugins/substack-import/skills/substack-import/references/posts.md +80 -0
  261. package/payload/platform/plugins/substack-import/skills/substack-import/references/subscribers.md +74 -0
  262. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  263. package/payload/platform/plugins/whatsapp/mcp/dist/index.js +2 -2
  264. package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
  265. package/payload/platform/plugins/work/mcp/dist/index.js +2 -2
  266. package/payload/platform/plugins/work/mcp/dist/index.js.map +1 -1
  267. package/payload/platform/plugins/work/mcp/dist/tools/project-create.js +6 -6
  268. package/payload/platform/plugins/work/mcp/dist/tools/project-create.js.map +1 -1
  269. package/payload/platform/plugins/work/mcp/dist/tools/session-list.d.ts +1 -1
  270. package/payload/platform/plugins/work/mcp/dist/tools/session-list.d.ts.map +1 -1
  271. package/payload/platform/plugins/work/mcp/dist/tools/session-list.js +1 -1
  272. package/payload/platform/plugins/work/mcp/dist/tools/session-list.js.map +1 -1
  273. package/payload/platform/plugins/x-import/references/archive-shape.md +2 -2
  274. package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +4 -4
  275. package/payload/platform/scripts/admin-persist-audit.ts +18 -18
  276. package/payload/platform/scripts/identity-forbidden-token-check.mjs +2 -0
  277. package/payload/platform/scripts/seed-neo4j.sh +1 -0
  278. package/payload/platform/services/claude-session-manager/dist/http-server.js +3 -3
  279. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  280. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +3 -3
  281. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +4 -4
  282. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  283. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  284. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +2 -0
  285. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  286. package/payload/platform/templates/specialists/agents/archive-ingest-operator.md +45 -0
  287. package/payload/platform/templates/specialists/agents/database-operator.md +5 -17
  288. package/payload/platform/templates/specialists/agents/librarian.md +1 -1
  289. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  290. package/payload/server/{chunk-AXOL2PPG.js → chunk-3TRIXQSJ.js} +101 -101
  291. package/payload/server/maxy-edge.js +1 -1
  292. package/payload/server/public/assets/{admin-3fLFD5h_.js → admin-Dal5FWaD.js} +2 -2
  293. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Breg9-99.js → architectureDiagram-Q4EWVU46-Do2zYONG.js} +1 -1
  294. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DXf1_673.js → blockDiagram-DXYQGD6D-DpIX_bRu.js} +1 -1
  295. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-CSZ36W4-.js → c4Diagram-AHTNJAMY-Cz335ZSY.js} +1 -1
  296. package/payload/server/public/assets/channel-PC4_IjI1.js +1 -0
  297. package/payload/server/public/assets/{chunk-336JU56O-DsrLk2eK.js → chunk-336JU56O-Dmknn3Wo.js} +2 -2
  298. package/payload/server/public/assets/{chunk-426QAEUC-JpNfNf81.js → chunk-426QAEUC-ClfGjizN.js} +1 -1
  299. package/payload/server/public/assets/{chunk-4TB4RGXK-DmClEAD0.js → chunk-4TB4RGXK-BPKctJ5c.js} +1 -1
  300. package/payload/server/public/assets/{chunk-5FUZZQ4R-BI9blW3N.js → chunk-5FUZZQ4R-BWDCO9zw.js} +1 -1
  301. package/payload/server/public/assets/{chunk-5PVQY5BW-BbgttEFO.js → chunk-5PVQY5BW-BE34_Db0.js} +1 -1
  302. package/payload/server/public/assets/{chunk-EDXVE4YY-BX_LCFOx.js → chunk-EDXVE4YY-BysA-_6v.js} +1 -1
  303. package/payload/server/public/assets/{chunk-ENJZ2VHE-Bb7EoD0w.js → chunk-ENJZ2VHE-BMVyMsqA.js} +1 -1
  304. package/payload/server/public/assets/{chunk-ICPOFSXX-DH8uQk-s.js → chunk-ICPOFSXX-C5KRdLV4.js} +1 -1
  305. package/payload/server/public/assets/{chunk-OYMX7WX6-FH5kgXsw.js → chunk-OYMX7WX6-DVZD-AVc.js} +1 -1
  306. package/payload/server/public/assets/{chunk-U2HBQHQK-D_ljlkHr.js → chunk-U2HBQHQK-DYmQdx8x.js} +1 -1
  307. package/payload/server/public/assets/{chunk-X2U36JSP-BX3EmZvm.js → chunk-X2U36JSP-DrZenIl3.js} +1 -1
  308. package/payload/server/public/assets/{chunk-YZCP3GAM-Vu7-E93h.js → chunk-YZCP3GAM-CDuGbvMq.js} +1 -1
  309. package/payload/server/public/assets/{chunk-ZZ45TVLE-CwjhNPX5.js → chunk-ZZ45TVLE-COztbiu5.js} +1 -1
  310. package/payload/server/public/assets/classDiagram-6PBFFD2Q-B_pMa-SX.js +1 -0
  311. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BdBAgF20.js +1 -0
  312. package/payload/server/public/assets/clone-CkNt9L9r.js +1 -0
  313. package/payload/server/public/assets/{dagre-Ce6gsj0C.js → dagre-Cv9Ib2OM.js} +1 -1
  314. package/payload/server/public/assets/{dagre-KV5264BT-BaMGpsoy.js → dagre-KV5264BT-CoEd61KZ.js} +1 -1
  315. package/payload/server/public/assets/{data-BHASh0_Y.js → data-DbSORCl4.js} +1 -1
  316. package/payload/server/public/assets/{diagram-5BDNPKRD-CHEE9rjq.js → diagram-5BDNPKRD-WXNzw3uW.js} +1 -1
  317. package/payload/server/public/assets/{diagram-G4DWMVQ6-C98sZLW1.js → diagram-G4DWMVQ6-C6IuQ2VV.js} +1 -1
  318. package/payload/server/public/assets/{diagram-MMDJMWI5-DNvMGsTZ.js → diagram-MMDJMWI5-CdJdir5A.js} +1 -1
  319. package/payload/server/public/assets/{diagram-TYMM5635-Cdte4IBa.js → diagram-TYMM5635-DxWKQH5T.js} +1 -1
  320. package/payload/server/public/assets/{erDiagram-SMLLAGMA-C9VZsWXK.js → erDiagram-SMLLAGMA-C9fvs33K.js} +1 -1
  321. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-tuP5fzzR.js → flowDiagram-DWJPFMVM-BO7Yrbqv.js} +1 -1
  322. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-SJgRPUwx.js → ganttDiagram-T4ZO3ILL-CQJyWBZq.js} +1 -1
  323. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DU_cI_50.js → gitGraphDiagram-UUTBAWPF-DZfQHrYw.js} +1 -1
  324. package/payload/server/public/assets/{graph-labels-DXlmm1og.js → graph-labels-BBB5jsZk.js} +1 -1
  325. package/payload/server/public/assets/{graph-BhEI09tF.js → graph-ys_joP2o.js} +1 -1
  326. package/payload/server/public/assets/{graphlib-D_nAsvmx.js → graphlib-C40H-Q5E.js} +1 -1
  327. package/payload/server/public/assets/{infoDiagram-42DDH7IO-Cj2Y19m6.js → infoDiagram-42DDH7IO-CakfV5f9.js} +1 -1
  328. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-DMc1xq1y.js → ishikawaDiagram-UXIWVN3A-CPuBPyfa.js} +1 -1
  329. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-C4CRAQma.js → journeyDiagram-VCZTEJTY-zpZm2Mmo.js} +1 -1
  330. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-CTr4AldU.js → kanban-definition-6JOO6SKY-DfJduQCo.js} +1 -1
  331. package/payload/server/public/assets/{line-Btlm_IAl.js → line-BpYQqO5R.js} +1 -1
  332. package/payload/server/public/assets/{mermaid-parser.core-DXFdj_y1.js → mermaid-parser.core-C5Mwi14D.js} +1 -1
  333. package/payload/server/public/assets/{mermaid.core-DFBhoCr5.js → mermaid.core-D8qBtvvT.js} +3 -3
  334. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-r9gVQybo.js → mindmap-definition-QFDTVHPH-Bli9JhIN.js} +1 -1
  335. package/payload/server/public/assets/{page-CDWu1Aft.js → page--bl5Y7ZI.js} +2 -2
  336. package/payload/server/public/assets/page-CwmZ76U7.js +1 -0
  337. package/payload/server/public/assets/{pieDiagram-DEJITSTG-CHKZjSaH.js → pieDiagram-DEJITSTG-MsLXTSLg.js} +1 -1
  338. package/payload/server/public/assets/{public-DyGWgfaZ.js → public-DltgEfA9.js} +2 -2
  339. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CSGR1HJr.js → quadrantDiagram-34T5L4WZ-De1sh2K0.js} +1 -1
  340. package/payload/server/public/assets/{requirementDiagram-MS252O5E-DZ6hX9D6.js → requirementDiagram-MS252O5E-D5Sfg1t_.js} +1 -1
  341. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DbZBKh0m.js → sankeyDiagram-XADWPNL6-DHA3LIbv.js} +1 -1
  342. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BHHpb8dt.js → sequenceDiagram-FGHM5R23-BE_QlG1B.js} +1 -1
  343. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DQ1jiLB4.js → stateDiagram-FHFEXIEX-NIwp3F4u.js} +1 -1
  344. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-pfk2F6st.js +1 -0
  345. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BlGiEU7p.js → timeline-definition-GMOUNBTQ-CPE9jyDF.js} +1 -1
  346. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DmEH-hsB.js → vennDiagram-DHZGUBPP-CKfoyYmH.js} +1 -1
  347. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-C7KJJ40d.js → wardleyDiagram-NUSXRM2D-B9yJek-o.js} +1 -1
  348. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-JDnusCaa.js → xychartDiagram-5P7HB3ND-DuCcSLqX.js} +1 -1
  349. package/payload/server/public/data.html +3 -3
  350. package/payload/server/public/graph.html +3 -3
  351. package/payload/server/public/index.html +4 -4
  352. package/payload/server/public/public.html +1 -1
  353. package/payload/server/server.js +133 -133
  354. package/payload/server/public/assets/channel-BmZnt3PP.js +0 -1
  355. package/payload/server/public/assets/classDiagram-6PBFFD2Q-kmMLFUpV.js +0 -1
  356. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BV7Ae8ct.js +0 -1
  357. package/payload/server/public/assets/clone-B5eqoN9I.js +0 -1
  358. package/payload/server/public/assets/page-cyvshX4Z.js +0 -1
  359. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CByT2RzF.js +0 -1
@@ -1,58 +1,58 @@
1
1
  #!/usr/bin/env node
2
2
  // =============================================================================
3
- // conversation-archive-ingest.mjs — in-process orchestrator for
4
- // conversation-archive-ingest.sh
5
- // env-only identity, fsync-per-line progress sink, per-session
6
- // checkpointing).
3
+ // conversation-archive-ingest.mjs — thin parse-and-prepare helper for the
4
+ // `conversation-archive` skill (Task 435).
7
5
  //
8
- // Source-agnostic. The same pipeline runs for every conversation source;
9
- // `--source <enum>` selects the normaliser at the top of the pipeline:
6
+ // Contract (post-Task 435): this script does ONLY the deterministic work —
7
+ // argv + env validation, source-file resolution, per-source normalisation,
8
+ // archiveSha256 + conversationIdentity computation, owner + participant
9
+ // verification, prior-archive cursor lookup, delta slicing, sessionization,
10
+ // stable archive-title computation. It emits ONE JSON line on stdout
11
+ // containing the prepared sessions array (turn-attributed text per session +
12
+ // per-session cursor metadata) plus every identity/scope value the
13
+ // `memory-ingest` MCP call needs. The script does NOT classify, does NOT
14
+ // call `memory-ingest`, does NOT loop.
10
15
  //
11
- // normalise → bind canonical senders derive conversationIdentity
12
- // look up prior :ConversationArchive (delta cursor)
13
- // sessionize delta at gap-hours boundaries
14
- // for each session: classify (mode='chat') → memoryIngest immediately
15
- // advance lastIngestedMessageHash + lastIngestedMessageAt
16
+ // The dispatched specialist drives the rest in-turn: for each session in the
17
+ // emitted JSON, it reads the turn text, produces a typed-section JSON
18
+ // chunking, and invokes `memory-ingest` with that session's sections + the
19
+ // per-session cursor (`lastIngestedMessageHash`, `lastIngestedMessageAt`).
20
+ // The first session's call carries `cleanupPriorChunks: true` iff the run
21
+ // was invoked with `--rebuild`.
16
22
  //
17
- // Identity: `process.env.ACCOUNT_ID` and `process.env.USER_ID` are
18
- // the only inputs. Both are plumbed by `spawn-env.ts` into every Bash
19
- // subprocess; missing/malformed LOUD-FAIL at phase=argv. `--account-id` and
20
- // `--owner-element-id` flags are GONE; the writer derives the AdminUser
21
- // elementId from (accountId, userId) one Cypher hop away.
23
+ // Pre-Task-435 the script ran the Haiku classify + per-session memory-ingest
24
+ // itself; that path was deleted in Task 424 (server-side classifier removed)
25
+ // and finished off here in Task 435 (driver-side cleanup). The empty
26
+ // classifier stub the .mjs carried between 424 and 435 produced one raw
27
+ // unenriched `:Conversation` chunk per session that path is gone too.
22
28
  //
23
29
  // Argv (positional): <archive-path>
24
30
  // Argv (flags): --source <enum>
25
31
  // --participant-person-ids <csv>
26
32
  // --scope <admin|public>
27
- // [--rebuild] (destructive rebuild)
33
+ // [--rebuild]
28
34
  // [--timezone <iana>]
29
35
  // [--date-format <DD/MM/YY|MM/DD/YY|DD/MM/YYYY|MM/DD/YYYY>]
30
- // [--session-id <id>]
31
36
  //
32
- // Sessions split at SESSION_GAP_HOURS=8 (fixed constant). The flag
33
- // `--session-gap-hours` is REMOVED; passing it FAILs at phase=argv. The
34
- // chunked chat-mode classify path (sub-scope C) absorbs oversize
35
- // sessions internally no operator lever required.
37
+ // Identity: `process.env.ACCOUNT_ID` and `process.env.USER_ID` are the only
38
+ // inputs. Both are plumbed by `spawn-env.ts` into every Bash subprocess;
39
+ // missing/malformed → LOUD-FAIL at phase=argv. The writer derives the owner
40
+ // :AdminUser elementId from (accountId, userId) one Cypher hop away.
36
41
  //
37
- // Stdout (success): one JSON line counters the skill needs to formulate
38
- // the three operator-facing messages. See SKILL.md for the shape.
39
- // Stderr + progress file: `[conversation-archive] ...` lines, fsync per write.
40
- // Progress path: data/accounts/<accountId>/logs/conversation-archive-<sessionId>.log
41
- // Wrapper sh prints `[conversation-archive] progress-file=<absolute-path>` on
42
- // stdout before exec node, so the agent computes nothing.
42
+ // Sessions split at SESSION_GAP_HOURS=8 (fixed constant). The legacy
43
+ // `--session-gap-hours` flag is REMOVED; passing it FAILs at phase=argv.
44
+ //
45
+ // Stdout (success): one JSON line. See SKILL.md for the shape.
46
+ // Stderr: `[conversation-archive] FAIL phase=<…> reason="..."` on failure.
47
+ // Exit 0 on success; non-zero on failure.
43
48
  // =============================================================================
44
49
 
45
50
  import {
46
- closeSync,
47
51
  existsSync,
48
- fsyncSync,
49
- mkdirSync,
50
52
  mkdtempSync,
51
- openSync,
52
53
  readdirSync,
53
54
  rmSync,
54
55
  statSync,
55
- writeSync,
56
56
  } from "node:fs";
57
57
  import { join, resolve, dirname } from "node:path";
58
58
  import { tmpdir } from "node:os";
@@ -61,116 +61,34 @@ import { fileURLToPath } from "node:url";
61
61
 
62
62
  const __dirname = dirname(fileURLToPath(import.meta.url));
63
63
 
64
- // sub-scope D — fixed session gap. Pre-902 this was a CLI flag with a
65
- // 12h default; oversize sessions on dense archives drove operators to re-issue
66
- // with a smaller gap, which combined with cleanup-by-archiveSha256 to silently
67
- // destroy 138 chunks (Adam Mackay incident, 2026-05-04). The flag is gone;
68
- // chunked chat-mode classify (sub-scope C) absorbs oversize sessions
69
- // internally so the gap value is no longer an operational lever. 8h is a
70
- // tighter default than 12h and matches the operator-decided post-incident
71
- // constant.
64
+ // Fixed session gap — pre-902 this was a CLI flag with a 12h default; oversize
65
+ // sessions on dense archives drove operators to re-issue with a smaller gap,
66
+ // which combined with cleanup-by-archiveSha256 to silently destroy 138 chunks
67
+ // (Adam Mackay incident, 2026-05-04). The flag is gone; the in-turn classify
68
+ // path absorbs oversize sessions on the specialist side now.
72
69
  const SESSION_GAP_HOURS = 8;
73
70
 
74
- // ---------------------------------------------------------------------------
75
- // 1. Resolve dist paths.
76
- // ---------------------------------------------------------------------------
77
71
  const platformRoot =
78
72
  process.env.MAXY_PLATFORM_ROOT?.trim() ||
79
73
  resolve(__dirname, "..", "..", "..");
80
74
 
81
75
  const NORMALISERS_PATH = resolve(
82
- platformRoot,
83
- "plugins",
84
- "memory",
85
- "mcp",
86
- "dist",
87
- "lib",
88
- "conversation-normalisers",
89
- "index.js",
76
+ platformRoot, "plugins", "memory", "mcp", "dist", "lib",
77
+ "conversation-normalisers", "index.js",
90
78
  );
91
79
  const PIPELINE_PATH = resolve(
92
- platformRoot,
93
- "plugins",
94
- "memory",
95
- "mcp",
96
- "dist",
97
- "lib",
80
+ platformRoot, "plugins", "memory", "mcp", "dist", "lib",
98
81
  "conversation-pipeline",
99
82
  );
100
83
  const NEO4J_LIB_PATH = resolve(
101
- platformRoot,
102
- "plugins",
103
- "memory",
104
- "mcp",
105
- "dist",
106
- "lib",
107
- "neo4j.js",
108
- );
109
- // LLM_CLASSIFIER_PATH removed by Task 424. Task 435 reintroduces per-chunk
110
- // enrichment as in-turn specialist work.
111
- const MEMORY_INGEST_PATH = resolve(
112
- platformRoot,
113
- "plugins",
114
- "memory",
115
- "mcp",
116
- "dist",
117
- "tools",
118
- "memory-ingest.js",
84
+ platformRoot, "plugins", "memory", "mcp", "dist", "lib", "neo4j.js",
119
85
  );
120
86
  const UUID_LIB_PATH = resolve(
121
- platformRoot,
122
- "plugins",
123
- "memory",
124
- "mcp",
125
- "dist",
126
- "lib",
127
- "uuid.js",
87
+ platformRoot, "plugins", "memory", "mcp", "dist", "lib", "uuid.js",
128
88
  );
129
89
 
130
- // ---------------------------------------------------------------------------
131
- // 2. Progress sink + logger fan-out (sub-scope A).
132
- // `log()` and `fail()` write to stderr AND the progress file with fsync per
133
- // write. Parent Bash returns nothing until child exits; the progress file
134
- // is the only surface the agent's heartbeat poll can read mid-run.
135
- // ---------------------------------------------------------------------------
136
- let progressFd = -1;
137
90
  const startedMs = Date.now();
138
91
 
139
- function openProgressSink(absolutePath) {
140
- mkdirSync(dirname(absolutePath), { recursive: true });
141
- progressFd = openSync(absolutePath, "a");
142
- }
143
-
144
- function writeProgressLine(line) {
145
- // stderr first — preserves existing observability for parents that read
146
- // stderr after the child exits, AND makes the line visible via the
147
- // mcp-spawn-tee'd device server.log without a separate sink.
148
- process.stderr.write(line);
149
- if (progressFd >= 0) {
150
- try {
151
- writeSync(progressFd, line);
152
- fsyncSync(progressFd);
153
- } catch {
154
- // Disk pressure / EBADF: don't kill the run — stderr already carries
155
- // the line.
156
- }
157
- }
158
- }
159
-
160
- function log(line) {
161
- writeProgressLine(`[conversation-archive] ${line}\n`);
162
- }
163
-
164
- function endRun(exitCode) {
165
- const totalMs = Date.now() - startedMs;
166
- writeProgressLine(`[conversation-archive] end exit-code=${exitCode} ms=${totalMs}\n`);
167
- if (progressFd >= 0) {
168
- try { closeSync(progressFd); } catch { /* fd already closed */ }
169
- progressFd = -1;
170
- }
171
- process.exit(exitCode);
172
- }
173
-
174
92
  function fail(phase, fields) {
175
93
  const fieldStr = Object.entries(fields)
176
94
  .map(([k, v]) =>
@@ -179,12 +97,12 @@ function fail(phase, fields) {
179
97
  : `${k}=${v ?? "-"}`,
180
98
  )
181
99
  .join(" ");
182
- writeProgressLine(`[conversation-archive] FAIL phase=${phase} ${fieldStr}\n`);
183
- endRun(1);
100
+ process.stderr.write(`[conversation-archive] FAIL phase=${phase} ${fieldStr}\n`);
101
+ process.exit(1);
184
102
  }
185
103
 
186
104
  // ---------------------------------------------------------------------------
187
- // 3. Argv parsing
105
+ // Argv parsing
188
106
  // ---------------------------------------------------------------------------
189
107
  const VALID_SOURCES = new Set([
190
108
  "whatsapp",
@@ -195,12 +113,17 @@ const VALID_SOURCES = new Set([
195
113
  "meeting-minutes",
196
114
  "imessage",
197
115
  "slack",
116
+ "x-dm",
117
+ "email",
198
118
  "other",
199
119
  ]);
200
120
 
201
- // Boolean flags carry no value; every other flag consumes the following arg.
202
121
  const BOOLEAN_FLAGS = new Set(["rebuild"]);
203
122
 
123
+ function camelCase(s) {
124
+ return s.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
125
+ }
126
+
204
127
  function parseArgv(argv) {
205
128
  const args = argv.slice(2);
206
129
  let archive = null;
@@ -213,9 +136,6 @@ function parseArgv(argv) {
213
136
  continue;
214
137
  }
215
138
  const key = a.slice(2);
216
- // sub-scope D — the flag re-introduced the surface that enabled
217
- // the silent-partial-wipe failure (Defect B). Reject loudly with the
218
- // remediation the operator should use instead.
219
139
  if (key === "session-gap-hours") {
220
140
  fail("argv", {
221
141
  reason: `--session-gap-hours is no longer supported; gap is fixed at 8h. Use --rebuild on the prior export bytes if you need to re-classify.`,
@@ -232,7 +152,7 @@ function parseArgv(argv) {
232
152
  if (!archive) fail("argv", { reason: "missing positional <archive>" });
233
153
  if (!flags.source) fail("argv", { reason: "missing --source" });
234
154
  if (!VALID_SOURCES.has(flags.source)) {
235
- fail("argv", { reason: `invalid --source "${flags.source}" (whatsapp|telegram|signal|linkedin-messages|zoom-transcript|meeting-minutes|imessage|slack|other)` });
155
+ fail("argv", { reason: `invalid --source "${flags.source}" (whatsapp|telegram|signal|linkedin-messages|zoom-transcript|meeting-minutes|imessage|slack|x-dm|email|other)` });
236
156
  }
237
157
  if (!flags.participantPersonIds) {
238
158
  fail("argv", {
@@ -245,13 +165,10 @@ function parseArgv(argv) {
245
165
  }
246
166
  return { archive, flags };
247
167
  }
248
- function camelCase(s) {
249
- return s.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
250
- }
251
168
 
252
169
  // ---------------------------------------------------------------------------
253
- // 4. Archive resolution. WhatsApp ships zip-or-dir-or-_chat.txt; other
254
- // sources pass a single file path that the normaliser interprets directly.
170
+ // Archive resolution. WhatsApp ships zip-or-dir-or-_chat.txt; other sources
171
+ // pass a single file path the normaliser interprets directly.
255
172
  // ---------------------------------------------------------------------------
256
173
  function resolveSourceFile(archivePath, source) {
257
174
  const abs = resolve(archivePath);
@@ -290,6 +207,7 @@ function resolveSourceFile(archivePath, source) {
290
207
  fail("argv", { reason: `unsupported archive shape for source=${source}: ${abs}` });
291
208
  return { sourceFile: abs, cleanup: () => {} };
292
209
  }
210
+
293
211
  function findChatTxt(dir) {
294
212
  const entries = readdirSync(dir, { withFileTypes: true });
295
213
  for (const e of entries) {
@@ -305,28 +223,17 @@ function findChatTxt(dir) {
305
223
  }
306
224
 
307
225
  // ---------------------------------------------------------------------------
308
- // 4b. Stable archive title (sub-scope A).
309
- //
310
- // Format: `<source> · <owner> ↔ <other1>, <other2>, … · <YYYY-MM-DD>→<YYYY-MM-DD>`.
311
- //
312
- // The title is computed once per ingest from inputs that are immutable for
226
+ // Stable archive title computed once per ingest from inputs immutable for
313
227
  // the conversation (source, accountId, sortedParticipantElementIds, archive
314
228
  // dateRange) so re-ingests produce the same string. Memory-ingest writes it
315
- // to the `:ConversationArchive.title` ON CREATE and COALESCE-on-MATCH so the UI
316
- // (display-helpers.ts) reads a stable label without ever falling through to
317
- // `summary`. Per-session counter strings stay in the progress file only.
318
- //
229
+ // ON CREATE and COALESCE-on-MATCH; the UI reads `:ConversationArchive.title`
230
+ // directly. Format:
231
+ // `<source> · <owner> <other1>, <other2>, · <YYYY-MM-DD>→<YYYY-MM-DD>`
319
232
  // Names: AdminUser uses `displayName` then `slug`; Person composes
320
- // `givenName + familyName`. When neither resolves, fall back to a short
321
- // elementId prefix — same degraded format the migration uses on backfill,
322
- // so a re-ingest after the migration produces the same title shape that
323
- // already populated the node.
233
+ // `givenName + familyName`. Missing names fall back to short elementId
234
+ // prefixes — same degraded format the migration uses.
324
235
  // ---------------------------------------------------------------------------
325
-
326
236
  function isoToYmd(iso) {
327
- // Read the YYYY-MM-DD prefix verbatim — same tz-stable contract as
328
- // formatRunDate in display-helpers.ts. Parsing to Date + reformatting
329
- // would flip the day between operator timezones.
330
237
  const m = typeof iso === "string" ? iso.match(/^(\d{4}-\d{2}-\d{2})/) : null;
331
238
  return m ? m[1] : "?";
332
239
  }
@@ -348,16 +255,11 @@ function pickNameForRow(labels, props) {
348
255
  }
349
256
 
350
257
  async function computeArchiveTitle({
351
- source,
352
- accountId,
353
- ownerElementId,
354
- participantElementIds,
355
- firstMessageAt,
356
- lastMessageAt,
357
- getSession,
258
+ source, accountId, ownerElementId, participantElementIds,
259
+ firstMessageAt, lastMessageAt, getSession,
358
260
  }) {
359
261
  const session = getSession();
360
- let nameByElementId = new Map();
262
+ const nameByElementId = new Map();
361
263
  try {
362
264
  const ids = [ownerElementId, ...participantElementIds];
363
265
  const r = await session.run(
@@ -380,22 +282,16 @@ async function computeArchiveTitle({
380
282
  const otherNames = participantElementIds
381
283
  .map((id) => nameByElementId.get(id) || id.slice(0, 8))
382
284
  .join(", ");
383
- const firstYmd = isoToYmd(firstMessageAt);
384
- const lastYmd = isoToYmd(lastMessageAt);
385
- return `${source} · ${ownerName} ↔ ${otherNames} · ${firstYmd}→${lastYmd}`;
285
+ return `${source} · ${ownerName} ↔ ${otherNames} · ${isoToYmd(firstMessageAt)}→${isoToYmd(lastMessageAt)}`;
386
286
  }
387
287
 
388
288
  // ---------------------------------------------------------------------------
389
- // 5. Main
289
+ // Main
390
290
  // ---------------------------------------------------------------------------
391
291
  async function main() {
392
292
  const { archive, flags } = parseArgv(process.argv);
393
293
  const source = flags.source;
394
294
 
395
- // Identity is env-only (sub-scope C). spawn-env.ts plumbs both
396
- // ACCOUNT_ID and USER_ID into every Bash subprocess; the bin LOUD-FAILs if
397
- // either is absent or malformed. The disk-scan resolver and the
398
- // --account-id / --owner-element-id flags are gone.
399
295
  let UUID_REGEX;
400
296
  try {
401
297
  ({ UUID_REGEX } = await import(UUID_LIB_PATH));
@@ -431,36 +327,14 @@ async function main() {
431
327
  const scope = flags.scope;
432
328
  const timezone = flags.timezone || "Europe/London";
433
329
  const dateFormat = flags.dateFormat;
434
- // sub-scope B — `--rebuild` is the single opt-in for destructive
435
- // re-run-with-same-bytes. Without it, re-issuing the bin on the same export
436
- // is a no-op (chunk-count summary, exit 0). With it, the bin treats the run
437
- // as first-ingest (skips prior-cursor lookup) and the FIRST session's
438
- // memoryIngest call cleans prior chunks for the archiveSha256.
439
330
  const rebuild = flags.rebuild === true;
440
- const sessionId =
441
- flags.sessionId ||
442
- `conversation-archive:${source}:${Date.now()}:${Math.random().toString(36).slice(2, 10)}`;
443
-
444
- // ---------------------------------------------------------------------------
445
- // Open progress sink BEFORE any await. Path is deterministic from
446
- // (accountId, sessionId); the wrapper sh prints the same path on stdout so
447
- // the agent's heartbeat polls the right file.
448
- // ---------------------------------------------------------------------------
449
- const accountsDir = resolve(platformRoot, "..", "data", "accounts");
450
- const logsDir = resolve(accountsDir, accountId, "logs");
451
- const progressFilePath = resolve(logsDir, `conversation-archive-${sessionId}.log`);
452
- openProgressSink(progressFilePath);
453
-
454
- log(
455
- `start session=${sessionId} source=${source} archive=${archive} accountId=${accountId} userId=${userId} participants=${participantElementIds.length} scope=${scope} pid=${process.pid}`,
456
- );
457
331
 
458
332
  // Imports — fail loudly if any compiled dist missing
459
333
  let getNormaliser;
460
334
  let sessionize, toTurnText, findDeltaCursor;
461
335
  let deriveConversationIdentity, deriveMessageContentHash;
462
336
  let verifyParticipantElementIds;
463
- let getSession, memoryIngest;
337
+ let getSession;
464
338
  try {
465
339
  ({ getNormaliser } = await import(NORMALISERS_PATH));
466
340
  ({ sessionize } = await import(join(PIPELINE_PATH, "sessionize.js")));
@@ -470,7 +344,6 @@ async function main() {
470
344
  await import(join(PIPELINE_PATH, "derive-keys.js")));
471
345
  ({ verifyParticipantElementIds } = await import(join(PIPELINE_PATH, "sender-bind.js")));
472
346
  ({ getSession } = await import(NEO4J_LIB_PATH));
473
- ({ memoryIngest } = await import(MEMORY_INGEST_PATH));
474
347
  } catch (err) {
475
348
  fail("import", {
476
349
  reason: "failed to import compiled dist",
@@ -478,20 +351,7 @@ async function main() {
478
351
  });
479
352
  }
480
353
 
481
- // Task 424 per-session Haiku enrichment removed. Every session lands as an
482
- // unenriched raw `:Conversation` chunk; topic summaries / keywords / per-chunk
483
- // boundaries are reintroduced under Task 435 (specialist produces them
484
- // in-turn). Until then, the driver still ingests the archive — operator gets
485
- // the conversation in the graph, just without per-chunk enrichment.
486
- const classifyDocument = async ({ documentText }) => ({
487
- kind: "error",
488
- reason: "Task 424 — server-side classifier removed; Task 435 introduces in-turn enrichment.",
489
- documentText,
490
- });
491
-
492
- // Resolve owner AdminUser elementId from (accountId, userId). One Cypher
493
- // hop replaces the deleted --owner-element-id flag — owner identity is env
494
- // input, never an out-of-band flag.
354
+ // Resolve owner :AdminUser elementId from (accountId, userId).
495
355
  let ownerElementId;
496
356
  {
497
357
  const lookupSession = getSession();
@@ -517,12 +377,11 @@ async function main() {
517
377
  }
518
378
  await lookupSession.close().catch(() => {});
519
379
  }
520
- log(`owner-resolved ownerElementId=${ownerElementId.slice(0, 12)} userId=${userId.slice(0, 8)}`);
521
380
 
522
- // 6a. Resolve source file
381
+ // Resolve source file
523
382
  const { sourceFile, cleanup } = resolveSourceFile(archive, source);
524
383
 
525
- // 6b. Run the source-specific normaliser
384
+ // Run the source-specific normaliser
526
385
  let normaliserResult;
527
386
  try {
528
387
  const normaliser = getNormaliser(source);
@@ -539,16 +398,10 @@ async function main() {
539
398
  const allLines = normaliserResult.parsedLines;
540
399
  const archiveSha256 = normaliserResult.archiveSha256;
541
400
  const archiveSourceFile = normaliserResult.archiveSourceFile;
542
- log(
543
- `source=${source} file=${archiveSourceFile} owner=${ownerElementId} participants=${participantElementIds.length} scope=${scope} accountId=${accountId} archiveSha256=${archiveSha256.slice(0, 12)} gap=${SESSION_GAP_HOURS} rebuild=${rebuild}`,
544
- );
545
- log(
546
- `parsed lines=${normaliserResult.counters.parsed} media-skipped=${normaliserResult.counters.mediaSkipped} system-skipped=${normaliserResult.counters.systemSkipped}`,
547
- );
548
- // zero matched timestamps means this isn't a chat archive — the
549
- // file should route through document-ingest instead. The agent's
550
- // database-operator skill picks the right entry; this LOUD-FAIL surfaces
551
- // misroute as an actionable signal rather than a silent empty ingest.
401
+
402
+ // Zero matched timestamps means this isn't a chat archive — route through
403
+ // document-ingest instead. LOUD-FAIL surfaces misroute as an actionable
404
+ // signal rather than a silent empty ingest.
552
405
  if (allLines.length === 0) {
553
406
  cleanup();
554
407
  fail("parse", {
@@ -556,50 +409,46 @@ async function main() {
556
409
  });
557
410
  }
558
411
 
559
- // 6c. Verify participant elementIds resolve to graph nodes with the right
560
- // accountId and labels. Per-line senderName binding is gone;
561
- // operator-confirmed participants attach to the :ConversationArchive parent.
412
+ // Verify operator-confirmed participants resolve to graph nodes with the
413
+ // right accountId and labels. Per-line senderName binding is gone; only
414
+ // owner + operator-confirmed participants attach via :PARTICIPANT_IN.
562
415
  let session = getSession();
563
416
  try {
564
417
  await verifyParticipantElementIds({
565
- session,
566
- accountId,
567
- ownerElementId,
568
- participantElementIds,
418
+ session, accountId, ownerElementId, participantElementIds,
569
419
  });
570
420
  } catch (err) {
571
421
  await session.close().catch(() => {});
572
422
  cleanup();
573
423
  if (err && err.userFacing) {
574
- writeProgressLine(`[conversation-archive] FAIL ${err.message}\n`);
575
- endRun(1);
424
+ process.stderr.write(`[conversation-archive] FAIL ${err.message}\n`);
425
+ process.exit(1);
576
426
  }
577
427
  fail("argv", { reason: err instanceof Error ? err.message : String(err) });
578
428
  }
579
429
 
580
- // 6d. Derive conversationIdentity and look up the prior :ConversationArchive parent (Task 397).
581
- // sub-scope B — under `--rebuild` we skip the prior-cursor lookup
582
- // entirely; the run is treated as first-ingest, the first session cleans
583
- // prior chunks (cleanupPriorChunks=true), and the cursor is overwritten
584
- // by that session's MERGE. Without `--rebuild`, the prior cursor drives
585
- // delta-append OR (sub-scope B) the same-bytes no-op exit when
586
- // chunks already exist for this archiveSha256.
430
+ // Derive conversationIdentity and look up prior :ConversationArchive (Task
431
+ // 397). Under `--rebuild` we skip the prior-cursor lookup entirely; the
432
+ // run is treated as first-ingest, and the specialist's first memory-ingest
433
+ // call will carry `cleanupPriorChunks: true` to drop prior chunks for this
434
+ // archiveSha256.
587
435
  const conversationIdentity = deriveConversationIdentity({
588
436
  accountId,
589
437
  participantElementIds: [ownerElementId, ...participantElementIds],
438
+ source,
590
439
  });
591
440
  let priorArchive = null;
592
441
  let priorChunkCountForSha = 0;
593
442
  if (!rebuild) {
594
443
  try {
595
444
  const r = await session.run(
596
- `MATCH (a:ConversationArchive { conversationIdentity: $cid })
445
+ `MATCH (a:ConversationArchive { conversationIdentity: $cid, source: $source })
597
446
  OPTIONAL MATCH (a)-[:HAS_SECTION]->(c:Section { archiveSha256: $sha })
598
447
  RETURN elementId(a) AS elemId,
599
448
  a.lastIngestedMessageHash AS lastHash,
600
449
  a.lastIngestedMessageAt AS lastAt,
601
450
  count(c) AS chunksForSha LIMIT 1`,
602
- { cid: conversationIdentity, sha: archiveSha256 },
451
+ { cid: conversationIdentity, source, sha: archiveSha256 },
603
452
  );
604
453
  if (r.records[0] && r.records[0].get("elemId")) {
605
454
  priorArchive = {
@@ -619,11 +468,12 @@ async function main() {
619
468
  }
620
469
  await session.close().catch(() => {});
621
470
 
622
- // 6e. Compute deltaStart. Three branches:
623
- // (i) --rebuild: deltaStart=0, kind=rebuild, classify entire archive.
471
+ // Compute deltaStart. Three branches:
472
+ // (i) --rebuild: deltaStart=0, kind=rebuild, prepare entire archive.
624
473
  // (ii) no prior archive (or no cursor): first-ingest, deltaStart=0.
625
474
  // (iii) prior archive + cursor: standard delta-append. Same-bytes (no
626
- // cursor advance possible) AND chunks exist for this sha → no-op.
475
+ // cursor advance possible) AND chunks exist for this sha → emit
476
+ // empty-delta JSON; specialist surfaces a noop message.
627
477
  let deltaStart = 0;
628
478
  let deltaKind = rebuild ? "rebuild" : "first-ingest";
629
479
  if (!rebuild && priorArchive && priorArchive.lastHash) {
@@ -637,243 +487,93 @@ async function main() {
637
487
  });
638
488
  }
639
489
  if (cursor.kind === "empty") {
640
- log(`noop reason="no new messages since ${priorArchive.lastAt}" prior-chunks=${priorChunkCountForSha}`);
641
490
  cleanup();
642
- const totalMs = Date.now() - startedMs;
643
491
  process.stdout.write(JSON.stringify({
644
492
  archiveElementId: priorArchive.elemId,
645
493
  conversationIdentity,
646
494
  archiveSha256,
647
495
  archiveSourceFile,
648
496
  source,
497
+ scope,
498
+ accountId,
499
+ ownerElementId,
500
+ participantElementIds,
501
+ rebuild,
649
502
  parsed: normaliserResult.counters.parsed,
650
503
  mediaSkipped: normaliserResult.counters.mediaSkipped,
651
504
  systemSkipped: normaliserResult.counters.systemSkipped,
652
505
  delta: { kind: "empty-delta", deltaStart: allLines.length, deltaMessages: 0 },
653
- sessions: 0,
654
- chunks: priorChunkCountForSha,
655
- nextEdgesCreated: 0,
656
- participantsLinked: 0,
657
506
  dateRange: { first: allLines[0].dateSent, last: allLines[allLines.length - 1].dateSent },
658
- parsedLineCount: allLines.length,
659
- sessionsUnenriched: 0,
660
- topicKeywords: [],
661
- ms: totalMs,
507
+ senderHistogram: normaliserResult.counters.senderHistogram ?? [],
508
+ priorChunkCount: priorChunkCountForSha,
662
509
  priorLastIngestedMessageAt: priorArchive.lastAt,
510
+ sessions: [],
511
+ ms: Date.now() - startedMs,
663
512
  }) + "\n");
664
- endRun(0);
513
+ process.exit(0);
665
514
  }
666
515
  deltaStart = cursor.deltaStart;
667
516
  deltaKind = "delta";
668
517
  }
669
518
  const deltaLines = allLines.slice(deltaStart);
670
- log(
671
- `delta kind=${deltaKind} cursor=${priorArchive && priorArchive.lastHash && !rebuild ? priorArchive.lastHash.slice(0, 12) : "(first-ingest)"} cursor-line=${deltaStart} delta-messages=${deltaLines.length}`,
672
- );
673
519
 
674
- // 6f. Sessionize delta at the fixed 8h gap (sub-scope D).
520
+ // Sessionize delta at the fixed 8h gap.
675
521
  const sessions = sessionize(deltaLines, SESSION_GAP_HOURS);
676
- log(
677
- `sessionize source=${source} archiveSha256=${archiveSha256.slice(0, 12)} messages=${deltaLines.length} sessions=${sessions.length} gap-hours=${SESSION_GAP_HOURS}`,
678
- );
679
-
680
- // sub-scope A — compute the stable archive title once, before
681
- // the per-session loop. The title is `<source> · <owner> ↔ <others> ·
682
- // <YYYY-MM-DD>→<YYYY-MM-DD>`. Operator-confirmed participant names come
683
- // from one Cypher hop; absent canonical names fall back to elementId
684
- // prefixes so the migration's degraded-title path mirrors live behaviour.
522
+
523
+ // Stable archive title computed once from the full archive's date range
524
+ // (not the delta), so a delta-append re-ingest produces the same title.
685
525
  const archiveTitle = await computeArchiveTitle({
686
- source,
687
- accountId,
688
- ownerElementId,
689
- participantElementIds,
526
+ source, accountId, ownerElementId, participantElementIds,
690
527
  firstMessageAt: allLines[0].dateSent,
691
528
  lastMessageAt: allLines[allLines.length - 1].dateSent,
692
529
  getSession,
693
530
  });
694
- log(`archive-title=${archiveTitle}`);
695
-
696
- // 6g. Per-session classify + immediate memoryIngest (sub-scope E).
697
- // Each session commits atomically: chunks + cursor advance happen together
698
- // via memoryIngest. A kill mid-loop leaves a partial archive whose cursor
699
- // is at session N-1's last message; the next invocation slices from there
700
- // and re-classifies only session N onward. degrade-on-error
701
- // is preserved: a Haiku error on session N still emits an unenriched chunk
702
- // and advances the cursor, so resumption never re-attempts the same Haiku
703
- // error.
704
- const allKeywords = new Set();
705
- let sessionsUnenriched = 0;
706
- let totalChunksWritten = 0;
707
- let totalNextEdges = 0;
708
- let participantsLinked = 0;
709
- let archiveElementIdLatest = priorArchive ? priorArchive.elemId : "";
710
-
711
- for (let sIdx = 0; sIdx < sessions.length; sIdx++) {
712
- const s = sessions[sIdx];
713
- const isFirstSessionInRun = sIdx === 0;
714
- const sessionStart = Date.now();
715
- const text = toTurnText(s);
716
- const result = await classifyDocument({
717
- accountId,
718
- mode: "chat",
719
- anchorDescription: `Conversation transcript (${[ownerElementId, ...participantElementIds].length} participants, session ${s.index + 1} of ${sessions.length})`,
720
- ontologyLabels: new Set([]),
721
- naturalEdgeMap: "",
722
- documentText: text,
723
- });
724
531
 
725
- let sessionChunks;
726
- let sessionKeywords = [];
727
- if (result.kind === "error") {
728
- sessionsUnenriched += 1;
729
- log(`session-unenriched session=${s.index + 1}/${sessions.length} reason="${result.reason.replace(/"/g, '\\"')}"`);
730
- // Emit one raw conversation :Section chunk covering the whole session.
731
- // No summary, no topicKeywords — a follow-up pass can re-enrich.
732
- sessionChunks = [{
733
- kind: "Conversation",
734
- title: `Unenriched session ${s.index + 1}`,
735
- body: text,
736
- summary: "",
737
- sourceStart: 0,
738
- sourceEnd: text.length,
739
- properties: {
740
- unenriched: true,
741
- unenrichedReason: result.reason,
742
- firstMessageAt: s.firstMessageAt,
743
- lastMessageAt: s.lastMessageAt,
744
- messageCount: s.messages.length,
745
- },
746
- anchorEdge: null,
747
- }];
748
- } else {
749
- const chunkCount = result.output.sections.length;
750
- log(
751
- `classify-session sessionIndex=${s.index + 1}/${sessions.length} messages=${s.messages.length} chars=${text.length} chunks=${chunkCount} ms=${Date.now() - sessionStart}`,
752
- );
753
- if (chunkCount === 0 && s.messages.length > 0) {
754
- cleanup();
755
- fail("classify", {
756
- reason: `session ${s.index} of ${s.messages.length} messages produced zero chunks (classifier-prompt regression)`,
757
- });
758
- }
759
- sessionChunks = result.output.sections;
760
- sessionKeywords = result.output.documentKeywords ?? [];
761
- for (const kw of sessionKeywords) allKeywords.add(kw);
762
- }
763
-
764
- // Per-session cursor: advance to THIS session's last message. Atomic with
765
- // chunk writes inside memoryIngest's MERGE/CREATE Cypher transaction.
766
- const lastSessionLine = s.messages[s.messages.length - 1];
767
- const sessionLastHash = deriveMessageContentHash({
768
- dateSent: lastSessionLine.dateSent,
769
- body: lastSessionLine.body,
770
- });
771
- const sessionLastAt = lastSessionLine.dateSent;
772
-
773
- let ingestResult;
774
- const ingestStart = Date.now();
775
- try {
776
- ingestResult = await memoryIngest({
777
- accountId,
778
- conversationIdentity,
779
- source,
780
- // sub-scope A — `archiveTitle` is the stable label written
781
- // to the conversation-document parent's `title` property (UI
782
- // prefers it over `summary`). `documentSummary` is also the stable
783
- // title rather than the per-session counter that previously locked
784
- // the parent's first checkpoint into "Session 1/N: …".
785
- archiveTitle,
786
- documentSummary: archiveTitle,
787
- anchorNodeId: ownerElementId,
788
- anchorLabel: "AdminUser",
789
- sections: sessionChunks,
790
- scope,
791
- sessionId,
792
- documentKeywords: Array.from(allKeywords),
793
- archiveSha256,
794
- archiveSourceFile,
795
- lastIngestedMessageHash: sessionLastHash,
796
- lastIngestedMessageAt: sessionLastAt,
797
- participantElementIds: [ownerElementId, ...participantElementIds],
798
- // sub-scope B — cleanup-by-archiveSha256 runs ONLY under
799
- // `--rebuild`, AND only on the first session of the run (subsequent
800
- // sessions skip cleanup or they would delete chunks the earlier
801
- // sessions in the same run just wrote).
802
- cleanupPriorChunks: rebuild && isFirstSessionInRun,
803
- });
804
- } catch (err) {
805
- cleanup();
806
- fail("memory-ingest", {
807
- reason: err instanceof Error ? err.message : String(err),
808
- sessionIndex: s.index + 1,
809
- sessionsTotal: sessions.length,
810
- });
811
- }
812
-
813
- archiveElementIdLatest = ingestResult.documentNodeId;
814
- totalChunksWritten += ingestResult.sectionCount;
815
- totalNextEdges += ingestResult.edgeBreakdown.NEXT ?? 0;
816
- if (isFirstSessionInRun) {
817
- participantsLinked = ingestResult.edgeBreakdown.PARTICIPANT_IN ?? 0;
818
- // sub-scope E — surface destructive cleanup as a WARN line in
819
- // the progress sink (not just memory-ingest's stderr). The agent's
820
- // heartbeat grep-matches this exact prefix and elevates to operator
821
- // chat. Cleanup only ever fires under `--rebuild` per sub-scope B,
822
- // so seeing the line outside of an operator-issued `--rebuild` is a
823
- // doctrine violation worth surfacing loudly.
824
- const cleaned = ingestResult.cleanedPriorChunks ?? 0;
825
- if (cleaned > 0) {
826
- log(
827
- `WARN cleanup-dropped chunks=${cleaned} archiveSha256=${archiveSha256.slice(0, 12)} — prior data deleted, expected only under --rebuild`,
828
- );
829
- }
830
- }
831
- log(
832
- `session-committed sessionIndex=${s.index + 1}/${sessions.length} chunks-written=${ingestResult.sectionCount} cursor-at="${sessionLastAt}" ms=${Date.now() - ingestStart}`,
833
- );
834
- // sub-scope F — derived progress line. The agent's heartbeat
835
- // surfaces only `progress` lines as chat tokens; raw `classify-session`
836
- // and `session-committed` lines remain on disk for diagnosis. The render
837
- // is computed in the bin (deterministic) instead of in SKILL.md prose
838
- // (LLM drift) — sessionIndex/N → P% with running totals comes from one
839
- // place, not from per-tick agent arithmetic.
840
- const pct = Math.round((100 * (s.index + 1)) / sessions.length);
841
- log(
842
- `progress sessionIndex=${s.index + 1}/${sessions.length} pct=${pct} chunks-so-far=${totalChunksWritten} elapsed-ms=${Date.now() - startedMs}`,
843
- );
844
- }
845
-
846
- if (sessionsUnenriched > 0) {
847
- log(`classify-summary sessions=${sessions.length} unenriched=${sessionsUnenriched} enriched=${sessions.length - sessionsUnenriched}`);
848
- }
532
+ // Build the per-session payload the specialist iterates in-turn. Each entry
533
+ // carries the turn text the specialist classifies + the cursor values the
534
+ // specialist passes through to `memory-ingest` for atomic checkpointing.
535
+ const sessionPayloads = sessions.map((s, sIdx) => {
536
+ const lastMessage = s.messages[s.messages.length - 1];
537
+ return {
538
+ sessionIndex: sIdx + 1,
539
+ totalSessions: sessions.length,
540
+ messageCount: s.messages.length,
541
+ firstMessageAt: s.firstMessageAt,
542
+ lastMessageAt: s.lastMessageAt,
543
+ lastMessageHash: deriveMessageContentHash({
544
+ dateSent: lastMessage.dateSent,
545
+ body: lastMessage.body,
546
+ }),
547
+ turnText: toTurnText(s),
548
+ };
549
+ });
849
550
 
850
551
  cleanup();
851
- const totalMs = Date.now() - startedMs;
852
- log(
853
- `done source=${source} conversationIdentity=${conversationIdentity.slice(0, 12)} archiveElementId=${archiveElementIdLatest} chunks-written=${totalChunksWritten} sessions=${sessions.length} total-ms=${totalMs}`,
854
- );
855
552
 
856
553
  process.stdout.write(JSON.stringify({
857
- archiveElementId: archiveElementIdLatest,
554
+ archiveElementId: priorArchive ? priorArchive.elemId : null,
858
555
  conversationIdentity,
859
556
  archiveSha256,
860
557
  archiveSourceFile,
558
+ archiveTitle,
861
559
  source,
560
+ scope,
561
+ accountId,
562
+ ownerElementId,
563
+ participantElementIds,
564
+ rebuild,
862
565
  parsed: normaliserResult.counters.parsed,
863
566
  mediaSkipped: normaliserResult.counters.mediaSkipped,
864
567
  systemSkipped: normaliserResult.counters.systemSkipped,
865
568
  delta: { kind: deltaKind, deltaStart, deltaMessages: deltaLines.length },
866
- sessions: sessions.length,
867
- chunks: totalChunksWritten,
868
- nextEdgesCreated: totalNextEdges,
869
- participantsLinked,
870
569
  dateRange: { first: allLines[0].dateSent, last: allLines[allLines.length - 1].dateSent },
871
- parsedLineCount: allLines.length,
872
- sessionsUnenriched,
873
- topicKeywords: Array.from(allKeywords),
874
- ms: totalMs,
570
+ senderHistogram: normaliserResult.counters.senderHistogram ?? [],
571
+ priorChunkCount: priorChunkCountForSha,
572
+ priorLastIngestedMessageAt: priorArchive ? priorArchive.lastAt : null,
573
+ sessions: sessionPayloads,
574
+ ms: Date.now() - startedMs,
875
575
  }) + "\n");
876
- endRun(0);
576
+ process.exit(0);
877
577
  }
878
578
 
879
579
  main().catch((err) => {