@saluzi/saluzi-edu 0.7.4 → 0.7.6

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 (165) hide show
  1. package/dist/cli.js +93 -61
  2. package/dist/rcs-schema.sql +42 -0
  3. package/dist/rcs-web/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
  4. package/dist/rcs-web/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
  5. package/{packages/remote-control-server/web/dist/assets/ArtifactsGallery-CNd9-Awp.js → dist/rcs-web/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
  6. package/{packages/remote-control-server/web/dist/assets/Claim-Dq08pl4W.js → dist/rcs-web/assets/Claim-CkuDG_ay.js} +1 -1
  7. package/dist/rcs-web/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
  8. package/dist/rcs-web/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
  9. package/{packages/remote-control-server/web/dist/assets/Login-CZbz6Wms.js → dist/rcs-web/assets/Login-Csvt3wvz.js} +1 -1
  10. package/dist/rcs-web/assets/MemoriesGallery-C5cT73hA.js +1 -0
  11. package/dist/rcs-web/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
  12. package/dist/rcs-web/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
  13. package/dist/rcs-web/assets/SessionDetail-CocGah-8.js +83 -0
  14. package/dist/rcs-web/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
  15. package/dist/rcs-web/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
  16. package/dist/rcs-web/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
  17. package/{packages/remote-control-server/web/dist/assets/TeamDetail-8T_kKUcX.js → dist/rcs-web/assets/TeamDetail-MPfqesJf.js} +2 -2
  18. package/dist/rcs-web/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
  19. package/dist/rcs-web/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
  20. package/dist/rcs-web/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
  21. package/dist/rcs-web/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
  22. package/dist/rcs-web/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
  23. package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
  24. package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
  25. package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
  26. package/dist/rcs-web/assets/channel-DcTKV8fL.js +1 -0
  27. package/dist/rcs-web/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
  28. package/dist/rcs-web/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
  29. package/dist/rcs-web/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
  30. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Bgz9JRYO.js → dist/rcs-web/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
  31. package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-DIFGJlEF.js → dist/rcs-web/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
  32. package/dist/rcs-web/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
  33. package/dist/rcs-web/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
  34. package/dist/rcs-web/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
  35. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
  36. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
  37. package/dist/rcs-web/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
  38. package/dist/rcs-web/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
  39. package/dist/rcs-web/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
  40. package/{packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-Bn4B33S-.js → dist/rcs-web/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
  41. package/dist/rcs-web/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
  42. package/dist/rcs-web/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
  43. package/{packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-CMLav2KT.js → dist/rcs-web/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
  44. package/dist/rcs-web/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
  45. package/{packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BexFwBQa.js → dist/rcs-web/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
  46. package/dist/rcs-web/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
  47. package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
  48. package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
  49. package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
  50. package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
  51. package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
  52. package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
  53. package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
  54. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
  55. package/dist/rcs-web/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
  56. package/dist/rcs-web/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
  57. package/dist/rcs-web/assets/main-CP31AzxJ.css +1 -0
  58. package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-5FH_DIq9.js → dist/rcs-web/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
  59. package/dist/rcs-web/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
  60. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
  61. package/dist/rcs-web/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
  62. package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
  63. package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
  64. package/dist/rcs-web/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
  65. package/{packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → dist/rcs-web/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
  66. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
  67. package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
  68. package/{packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → dist/rcs-web/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
  69. package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
  70. package/dist/rcs-web/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
  71. package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
  72. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
  73. package/dist/rcs-web/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
  74. package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
  75. package/dist/rcs-web/index.html +2 -2
  76. package/package.json +1 -1
  77. package/packages/remote-control-server/src/__tests__/memories.test.ts +654 -0
  78. package/packages/remote-control-server/src/db/rcs-schema.sql +42 -0
  79. package/packages/remote-control-server/src/registerRoutes.ts +6 -0
  80. package/packages/remote-control-server/src/routes/v1/memories.ts +132 -0
  81. package/packages/remote-control-server/src/routes/web/memories.ts +321 -0
  82. package/packages/remote-control-server/src/services/memories.ts +747 -0
  83. package/packages/remote-control-server/web/dist/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
  84. package/packages/remote-control-server/web/dist/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
  85. package/{dist/rcs-web/assets/ArtifactsGallery-CNd9-Awp.js → packages/remote-control-server/web/dist/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
  86. package/{dist/rcs-web/assets/Claim-Dq08pl4W.js → packages/remote-control-server/web/dist/assets/Claim-CkuDG_ay.js} +1 -1
  87. package/packages/remote-control-server/web/dist/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
  88. package/packages/remote-control-server/web/dist/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
  89. package/{dist/rcs-web/assets/Login-CZbz6Wms.js → packages/remote-control-server/web/dist/assets/Login-Csvt3wvz.js} +1 -1
  90. package/packages/remote-control-server/web/dist/assets/MemoriesGallery-C5cT73hA.js +1 -0
  91. package/packages/remote-control-server/web/dist/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
  92. package/packages/remote-control-server/web/dist/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
  93. package/packages/remote-control-server/web/dist/assets/SessionDetail-CocGah-8.js +83 -0
  94. package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
  95. package/packages/remote-control-server/web/dist/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
  96. package/packages/remote-control-server/web/dist/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
  97. package/{dist/rcs-web/assets/TeamDetail-8T_kKUcX.js → packages/remote-control-server/web/dist/assets/TeamDetail-MPfqesJf.js} +2 -2
  98. package/packages/remote-control-server/web/dist/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
  99. package/packages/remote-control-server/web/dist/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
  100. package/packages/remote-control-server/web/dist/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
  101. package/packages/remote-control-server/web/dist/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
  102. package/packages/remote-control-server/web/dist/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
  103. package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
  104. package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
  105. package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
  106. package/packages/remote-control-server/web/dist/assets/channel-DcTKV8fL.js +1 -0
  107. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
  108. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
  109. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
  110. package/{dist/rcs-web/assets/chunk-727SXJPM-Bgz9JRYO.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
  111. package/{dist/rcs-web/assets/chunk-AQP2D5EJ-DIFGJlEF.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
  112. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
  113. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
  114. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
  115. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
  116. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
  117. package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
  118. package/packages/remote-control-server/web/dist/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
  119. package/packages/remote-control-server/web/dist/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
  120. package/{dist/rcs-web/assets/dagre-BM42HDAG-Bn4B33S-.js → packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
  121. package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
  122. package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
  123. package/{dist/rcs-web/assets/diagram-KO2AKTUF-CMLav2KT.js → packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
  124. package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
  125. package/{dist/rcs-web/assets/diagram-OG6HWLK6-BexFwBQa.js → packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
  126. package/packages/remote-control-server/web/dist/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
  127. package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
  128. package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
  129. package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
  130. package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
  132. package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
  133. package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
  134. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
  137. package/packages/remote-control-server/web/dist/assets/main-CP31AzxJ.css +1 -0
  138. package/{dist/rcs-web/assets/mermaid-VLURNSYL-5FH_DIq9.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
  139. package/packages/remote-control-server/web/dist/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
  140. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
  141. package/packages/remote-control-server/web/dist/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
  142. package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
  143. package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
  144. package/packages/remote-control-server/web/dist/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
  145. package/{dist/rcs-web/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
  146. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
  147. package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
  148. package/{dist/rcs-web/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
  149. package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
  150. package/packages/remote-control-server/web/dist/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
  151. package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
  152. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
  153. package/packages/remote-control-server/web/dist/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
  154. package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
  155. package/packages/remote-control-server/web/dist/index.html +2 -2
  156. package/dist/rcs-web/assets/SessionDetail-BbnQBjP1.js +0 -78
  157. package/dist/rcs-web/assets/channel-D6RbJTfV.js +0 -1
  158. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
  159. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
  160. package/dist/rcs-web/assets/main-C0n90aVY.css +0 -1
  161. package/packages/remote-control-server/web/dist/assets/SessionDetail-BbnQBjP1.js +0 -78
  162. package/packages/remote-control-server/web/dist/assets/channel-D6RbJTfV.js +0 -1
  163. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
  164. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
  165. package/packages/remote-control-server/web/dist/assets/main-C0n90aVY.css +0 -1
@@ -0,0 +1,747 @@
1
+ import { nanoid } from 'nanoid'
2
+ import type { Database } from 'bun:sqlite'
3
+ import { error as logError, log } from '../logger'
4
+ import { getDb } from '../db/sqlite'
5
+ import { publishSessionEvent } from './transport'
6
+
7
+ /**
8
+ * Memories — durable team/personal memory entries.
9
+ *
10
+ * Two first-class scopes, discriminated by `scope_kind`:
11
+ * - personal: `owner_user_id`, strictly private — visible only to the owner
12
+ * (not even system admins browse it in listings; admin bypass is limited
13
+ * to manage operations and pack assembly, mirroring the "private means
14
+ * private" TDAM semantics).
15
+ * - team: `team_id`, visible to every active team member.
16
+ *
17
+ * Sessions are NOT a first-class scope. `source_session_id` is a provenance
18
+ * link ("this memory was distilled from that session") and an optional
19
+ * recall filter — never a registration gate (TDAM task-optional lesson).
20
+ */
21
+
22
+ export const MEMORY_TYPES = [
23
+ 'user',
24
+ 'feedback',
25
+ 'project',
26
+ 'reference',
27
+ ] as const
28
+ export type MemoryType = (typeof MEMORY_TYPES)[number]
29
+
30
+ export const MEMORY_LAYERS = ['episodic', 'semantic', 'procedural'] as const
31
+ export type MemoryLayer = (typeof MEMORY_LAYERS)[number]
32
+
33
+ export type MemoryScopeKind = 'personal' | 'team'
34
+
35
+ export interface MemoryRow {
36
+ id: string
37
+ scope_kind: MemoryScopeKind
38
+ owner_user_id: string | null
39
+ team_id: string | null
40
+ name: string
41
+ description: string | null
42
+ type: MemoryType
43
+ layer: MemoryLayer
44
+ content: string
45
+ status: 'active' | 'superseded' | 'archived'
46
+ source_session_id: string | null
47
+ version: number
48
+ created_by: string | null
49
+ created_at: string
50
+ updated_at: string
51
+ }
52
+
53
+ export type MemoryMeta = Omit<MemoryRow, 'content'>
54
+
55
+ /** Pack payload handed to the bridge: rel path → file content. */
56
+ export interface MemoryPack {
57
+ files: Record<string, string>
58
+ }
59
+
60
+ export class MemoryError extends Error {
61
+ code: string
62
+ constructor(code: string, message: string) {
63
+ super(message)
64
+ this.name = 'MemoryError'
65
+ this.code = code
66
+ }
67
+ }
68
+
69
+ function nowIso(): string {
70
+ return new Date().toISOString()
71
+ }
72
+
73
+ function toMeta(row: MemoryRow): MemoryMeta {
74
+ const { content: _content, ...meta } = row
75
+ return meta
76
+ }
77
+
78
+ /** FTS5 write — insert on create, delete+insert on update (id-stable). */
79
+ function upsertFts(db: Database, row: MemoryRow): void {
80
+ try {
81
+ db.query('DELETE FROM memories_fts WHERE memory_id = $id').run({
82
+ $id: row.id,
83
+ })
84
+ db.query(
85
+ `INSERT INTO memories_fts (name, description, content, memory_id)
86
+ VALUES ($name, $description, $content, $id)`,
87
+ ).run({
88
+ $name: row.name,
89
+ $description: row.description ?? '',
90
+ $content: row.content,
91
+ $id: row.id,
92
+ })
93
+ } catch (err) {
94
+ // FTS is an accelerator, not a source of truth — a failed index write
95
+ // must not break the memory write itself.
96
+ logError(`[memories] fts upsert failed id=${row.id}:`, err)
97
+ }
98
+ }
99
+
100
+ function removeFts(db: Database, id: string): void {
101
+ try {
102
+ db.query('DELETE FROM memories_fts WHERE memory_id = $id').run({ $id: id })
103
+ } catch (err) {
104
+ logError(`[memories] fts delete failed id=${id}:`, err)
105
+ }
106
+ }
107
+
108
+ function getMemoryRowInner(db: Database, id: string): MemoryRow | null {
109
+ const row = db
110
+ .query('SELECT * FROM memories WHERE id = $id')
111
+ .get({ $id: id }) as MemoryRow | null
112
+ return row ?? null
113
+ }
114
+
115
+ /** Fetch a memory; archived rows read as missing (soft delete). */
116
+ export function getMemoryRow(id: string): MemoryRow | null {
117
+ try {
118
+ const row = getMemoryRowInner(getDb(), id)
119
+ if (!row || row.status === 'archived') return null
120
+ return row
121
+ } catch (err) {
122
+ logError(`[memories] getMemoryRow failed id=${id}:`, err)
123
+ return null
124
+ }
125
+ }
126
+
127
+ export interface CreateMemoryParams {
128
+ scopeKind: MemoryScopeKind
129
+ ownerUserId?: string
130
+ teamId?: string
131
+ name: string
132
+ description?: string
133
+ type: MemoryType
134
+ content: string
135
+ layer?: MemoryLayer
136
+ sourceSessionId?: string
137
+ createdBy?: string
138
+ }
139
+
140
+ export function createMemory(params: CreateMemoryParams): MemoryRow {
141
+ const db = getDb()
142
+
143
+ const name = params.name.trim()
144
+ if (!name) throw new MemoryError('invalid_name', 'name is required')
145
+ if (!params.content.trim()) {
146
+ throw new MemoryError('invalid_content', 'content is required')
147
+ }
148
+ if (!MEMORY_TYPES.includes(params.type)) {
149
+ throw new MemoryError(
150
+ 'invalid_type',
151
+ `type must be one of ${MEMORY_TYPES.join(', ')}`,
152
+ )
153
+ }
154
+
155
+ const now = nowIso()
156
+ const id = nanoid(21)
157
+ const ownerUserId =
158
+ params.scopeKind === 'personal' ? (params.ownerUserId ?? null) : null
159
+ const teamId = params.scopeKind === 'team' ? (params.teamId ?? null) : null
160
+
161
+ try {
162
+ db.query(
163
+ `INSERT INTO memories
164
+ (id, scope_kind, owner_user_id, team_id, name, description, type,
165
+ layer, content, status, source_session_id, version, created_by,
166
+ created_at, updated_at)
167
+ VALUES ($id, $scope_kind, $owner_user_id, $team_id, $name, $description,
168
+ $type, $layer, $content, 'active', $source_session_id, 1,
169
+ $created_by, $now, $now)`,
170
+ ).run({
171
+ $id: id,
172
+ $scope_kind: params.scopeKind,
173
+ $owner_user_id: ownerUserId,
174
+ $team_id: teamId,
175
+ $name: name,
176
+ $description: params.description?.trim() || null,
177
+ $type: params.type,
178
+ $layer: params.layer ?? 'semantic',
179
+ $content: params.content,
180
+ $source_session_id: params.sourceSessionId ?? null,
181
+ $created_by: params.createdBy ?? null,
182
+ $now: now,
183
+ })
184
+ } catch (err) {
185
+ // CHECK violation (missing owner column) or similar — surface as typed error.
186
+ throw new MemoryError(
187
+ 'invalid_scope',
188
+ `createMemory failed: ${err instanceof Error ? err.message : String(err)}`,
189
+ )
190
+ }
191
+
192
+ const row = getMemoryRowInner(db, id)
193
+ if (!row)
194
+ throw new MemoryError('insert_failed', 'memory row missing after insert')
195
+ upsertFts(db, row)
196
+ return row
197
+ }
198
+
199
+ function isTeamMember(db: Database, userId: string, teamId: string): boolean {
200
+ const row = db
201
+ .query(
202
+ 'SELECT 1 FROM team_members WHERE team_id = $teamId AND user_id = $userId LIMIT 1',
203
+ )
204
+ .get({ $teamId: teamId, $userId: userId })
205
+ return !!row
206
+ }
207
+
208
+ /**
209
+ * Visibility check. Order (TDAM permission-checker pattern):
210
+ * resource exists → personal: owner only (strictly private — admin listings
211
+ * don't include others' personal rows, and the single-fetch path keeps the
212
+ * same semantics for consistency) → team: active membership → deny.
213
+ */
214
+ export function isMemoryVisibleToUser(
215
+ id: string,
216
+ userId: string,
217
+ isAdmin = false,
218
+ ): boolean {
219
+ const db = getDb()
220
+ const row = getMemoryRow(id)
221
+ if (!row) return false
222
+
223
+ if (row.scope_kind === 'personal') {
224
+ return row.owner_user_id === userId
225
+ }
226
+ // Team scope — membership grants read; system admin too.
227
+ if (isAdmin) return true
228
+ return isTeamMember(db, userId, row.team_id!)
229
+ }
230
+
231
+ /**
232
+ * Manage = edit/delete/promote. System admin, personal owner,
233
+ * team row creator, or team owner/admin (managing other members' rows).
234
+ */
235
+ export function canManageMemory(
236
+ id: string,
237
+ userId: string,
238
+ isAdmin = false,
239
+ ): boolean {
240
+ const db = getDb()
241
+ const row = getMemoryRow(id)
242
+ if (!row) return false
243
+ if (isAdmin) return true
244
+
245
+ if (row.scope_kind === 'personal') {
246
+ return row.owner_user_id === userId
247
+ }
248
+ if (row.created_by === userId) return true
249
+ const member = db
250
+ .query(
251
+ `SELECT role FROM team_members
252
+ WHERE team_id = $teamId AND user_id = $userId LIMIT 1`,
253
+ )
254
+ .get({ $teamId: row.team_id!, $userId: userId }) as { role: string } | null
255
+ return !!member && (member.role === 'owner' || member.role === 'admin')
256
+ }
257
+
258
+ export interface ListMemoriesFilter {
259
+ teamId?: string
260
+ /** Personal space — rows owned by the caller across all teams. */
261
+ personalScope?: boolean
262
+ sessionId?: string
263
+ type?: MemoryType
264
+ }
265
+
266
+ /**
267
+ * List memories visible to a user. Permission filtering happens BEFORE the
268
+ * row cap so the result never leaks cross-team rows (caller-scoped lists,
269
+ * TDAM lesson: filter first, then paginate).
270
+ *
271
+ * Admins see team rows across teams but never others' personal rows —
272
+ * personal scope is owner-only by construction.
273
+ */
274
+ export function listMemoriesForUser(
275
+ userId: string,
276
+ filter: ListMemoriesFilter = {},
277
+ isAdmin = false,
278
+ ): MemoryMeta[] {
279
+ const db = getDb()
280
+ const conditions: string[] = [`status != 'archived'`]
281
+ const params: Record<string, string> = {}
282
+
283
+ if (filter.teamId) {
284
+ conditions.push("scope_kind = 'team' AND team_id = $teamId")
285
+ params.$teamId = filter.teamId
286
+ } else if (filter.personalScope) {
287
+ conditions.push("scope_kind = 'personal' AND owner_user_id = $userId")
288
+ params.$userId = userId
289
+ }
290
+ if (filter.sessionId) {
291
+ conditions.push('source_session_id = $sessionId')
292
+ params.$sessionId = filter.sessionId
293
+ }
294
+ if (filter.type) {
295
+ conditions.push('type = $type')
296
+ params.$type = filter.type
297
+ }
298
+
299
+ let rows: MemoryRow[] = []
300
+ try {
301
+ rows = db
302
+ .query(
303
+ `SELECT * FROM memories WHERE ${conditions.join(' AND ')}
304
+ ORDER BY updated_at DESC LIMIT 500`,
305
+ )
306
+ .all(params) as unknown as MemoryRow[]
307
+ } catch (err) {
308
+ logError('[memories] listMemoriesForUser failed:', err)
309
+ return []
310
+ }
311
+
312
+ if (isAdmin && !filter.personalScope) {
313
+ return rows.filter(r => r.scope_kind === 'team').map(toMeta)
314
+ }
315
+ return rows
316
+ .filter(row => isMemoryVisibleToUser(row.id, userId, isAdmin))
317
+ .map(toMeta)
318
+ }
319
+
320
+ export interface UpdateMemoryParams {
321
+ name?: string
322
+ description?: string
323
+ content?: string
324
+ type?: MemoryType
325
+ layer?: MemoryLayer
326
+ }
327
+
328
+ /** Edit an existing memory. Bumps version and refreshes FTS. */
329
+ export function updateMemory(
330
+ id: string,
331
+ params: UpdateMemoryParams,
332
+ ): MemoryRow | null {
333
+ const db = getDb()
334
+ const row = getMemoryRowInner(db, id)
335
+ if (!row || row.status === 'archived') return null
336
+
337
+ const name = params.name?.trim() || row.name
338
+ const description =
339
+ params.description !== undefined
340
+ ? params.description.trim() || null
341
+ : row.description
342
+ const content = params.content ?? row.content
343
+ if (!content.trim()) {
344
+ throw new MemoryError('invalid_content', 'content is required')
345
+ }
346
+ const type = params.type ?? row.type
347
+ if (!MEMORY_TYPES.includes(type)) {
348
+ throw new MemoryError(
349
+ 'invalid_type',
350
+ `type must be one of ${MEMORY_TYPES.join(', ')}`,
351
+ )
352
+ }
353
+ const layer = params.layer ?? row.layer
354
+
355
+ db.query(
356
+ `UPDATE memories
357
+ SET name = $name, description = $description, content = $content,
358
+ type = $type, layer = $layer, version = version + 1, updated_at = $now
359
+ WHERE id = $id`,
360
+ ).run({
361
+ $id: id,
362
+ $name: name,
363
+ $description: description,
364
+ $content: content,
365
+ $type: type,
366
+ $layer: layer,
367
+ $now: nowIso(),
368
+ })
369
+
370
+ const updated = getMemoryRowInner(db, id)
371
+ if (updated) upsertFts(db, updated)
372
+ return updated
373
+ }
374
+
375
+ /** Soft delete — archived reads as missing everywhere. */
376
+ export function archiveMemory(id: string): boolean {
377
+ const db = getDb()
378
+ try {
379
+ const result = db
380
+ .query(
381
+ `UPDATE memories SET status = 'archived', updated_at = $now
382
+ WHERE id = $id AND status != 'archived'`,
383
+ )
384
+ .run({ $id: id, $now: nowIso() })
385
+ const changed = (result as unknown as { changes?: number }).changes === 1
386
+ if (changed) removeFts(db, id)
387
+ return changed
388
+ } catch (err) {
389
+ logError(`[memories] archiveMemory failed id=${id}:`, err)
390
+ return false
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Promote a personal memory to team scope. Only the owner may promote their
396
+ * own memory, and they must be a member of the target team. The move keeps
397
+ * provenance (created_by, source_session_id) and bumps the version.
398
+ */
399
+ export function promoteMemoryToTeam(
400
+ id: string,
401
+ userId: string,
402
+ teamId: string,
403
+ ): MemoryRow {
404
+ const db = getDb()
405
+ const row = getMemoryRowInner(db, id)
406
+ if (!row || row.status === 'archived') {
407
+ throw new MemoryError('not_found', 'Memory not found')
408
+ }
409
+ if (row.scope_kind !== 'personal') {
410
+ throw new MemoryError(
411
+ 'not_personal',
412
+ 'Only personal memories can be promoted',
413
+ )
414
+ }
415
+ if (row.owner_user_id !== userId) {
416
+ throw new MemoryError('forbidden', 'Only the owner can promote this memory')
417
+ }
418
+ if (!isTeamMember(db, userId, teamId)) {
419
+ throw new MemoryError('forbidden', 'Not a member of the target team')
420
+ }
421
+
422
+ db.query(
423
+ `UPDATE memories
424
+ SET scope_kind = 'team', owner_user_id = NULL, team_id = $teamId,
425
+ version = version + 1, updated_at = $now
426
+ WHERE id = $id`,
427
+ ).run({ $id: id, $teamId: teamId, $now: nowIso() })
428
+
429
+ const updated = getMemoryRowInner(db, id)
430
+ if (!updated) throw new MemoryError('update_failed', 'promote failed')
431
+ upsertFts(db, updated)
432
+ return updated
433
+ }
434
+
435
+ // --- Memory pack (injection) ---------------------------------------------
436
+ //
437
+ // Compiles the stable-zone memory pack for a session: the primary user
438
+ // owner's personal memories plus, when the session is team-owned, the
439
+ // team's memories. Layout mirrors the CLI memdir conventions so the child
440
+ // process's existing loadMemoryPrompt machinery works unchanged:
441
+ //
442
+ // MEMORY.md — index, one line per entry (≤150 chars, ≤200 lines)
443
+ // <type>_<slug>.md — personal memory files, memdir frontmatter format
444
+ // team/ — team memories (memdir team subdir layout)
445
+
446
+ const PACK_MAX_LINES = 200
447
+ const PACK_LINE_MAX_CHARS = 150
448
+
449
+ function slugifyName(name: string): string {
450
+ const slug = name
451
+ .toLowerCase()
452
+ .replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
453
+ .replace(/^-+|-+$/g, '')
454
+ return slug || 'memory'
455
+ }
456
+
457
+ function memoryFileContent(row: MemoryRow): string {
458
+ return [
459
+ '---',
460
+ `name: ${row.name}`,
461
+ `description: ${row.description ?? row.name}`,
462
+ `type: ${row.type}`,
463
+ '---',
464
+ '',
465
+ row.content,
466
+ '',
467
+ ].join('\n')
468
+ }
469
+
470
+ function packLine(row: MemoryRow, relPath: string): string {
471
+ const hook = row.description ?? row.name
472
+ const line = `- [${row.name}](${relPath}) — ${hook}`
473
+ return line.length > PACK_LINE_MAX_CHARS
474
+ ? `${line.slice(0, PACK_LINE_MAX_CHARS - 1)}…`
475
+ : line
476
+ }
477
+
478
+ function buildPackFromRows(rows: MemoryRow[]): MemoryPack {
479
+ const files: Record<string, string> = {}
480
+ const lines: string[] = []
481
+ const used = new Set<string>()
482
+
483
+ for (const row of rows) {
484
+ const teamPrefix = row.scope_kind === 'team' ? 'team/' : ''
485
+ let rel = `${teamPrefix}${row.type}_${slugifyName(row.name)}.md`
486
+ // Dedupe colliding paths deterministically.
487
+ if (used.has(rel))
488
+ rel = `${teamPrefix}${row.type}_${slugifyName(row.name)}-${row.id}.md`
489
+ used.add(rel)
490
+ files[rel] = memoryFileContent(row)
491
+ lines.push(packLine(row, rel))
492
+ }
493
+
494
+ const capped = lines.slice(0, PACK_MAX_LINES)
495
+ if (lines.length > capped.length) {
496
+ capped.push(`- … ${lines.length - capped.length} more memories omitted`)
497
+ }
498
+ files['MEMORY.md'] = `${capped.join('\n')}\n`
499
+ return { files }
500
+ }
501
+
502
+ /** Resolve the identity context for a session: primary user owner + team owner. */
503
+ function resolveSessionMemoryScope(
504
+ db: Database,
505
+ sessionId: string,
506
+ ): { userId: string | null; teamId: string | null } {
507
+ try {
508
+ const owners = db
509
+ .query(
510
+ `SELECT owner_type, owner_id FROM session_owners WHERE session_id = $sid`,
511
+ )
512
+ .all({ $sid: sessionId }) as unknown as {
513
+ owner_type: string
514
+ owner_id: string
515
+ }[]
516
+ const userId = owners.find(o => o.owner_type === 'user')?.owner_id ?? null
517
+ const teamId = owners.find(o => o.owner_type === 'team')?.owner_id ?? null
518
+ return { userId, teamId }
519
+ } catch (err) {
520
+ logError(
521
+ `[memories] resolveSessionMemoryScope failed session=${sessionId}:`,
522
+ err,
523
+ )
524
+ return { userId: null, teamId: null }
525
+ }
526
+ }
527
+
528
+ function activePersonalMemories(db: Database, userId: string): MemoryRow[] {
529
+ return db
530
+ .query(
531
+ `SELECT * FROM memories
532
+ WHERE scope_kind = 'personal' AND owner_user_id = $uid AND status = 'active'
533
+ ORDER BY updated_at DESC LIMIT 100`,
534
+ )
535
+ .all({ $uid: userId }) as unknown as MemoryRow[]
536
+ }
537
+
538
+ function activeTeamMemories(db: Database, teamId: string): MemoryRow[] {
539
+ return db
540
+ .query(
541
+ `SELECT * FROM memories
542
+ WHERE scope_kind = 'team' AND team_id = $tid AND status = 'active'
543
+ ORDER BY updated_at DESC LIMIT 100`,
544
+ )
545
+ .all({ $tid: teamId }) as unknown as MemoryRow[]
546
+ }
547
+
548
+ /**
549
+ * Compile the injection pack for a session. Personal memories of the
550
+ * session's primary user owner; team memories only when the session is
551
+ * team-owned. Empty pack when the session has no user owner.
552
+ */
553
+ export function buildMemoryPack(sessionId: string): MemoryPack {
554
+ const db = getDb()
555
+ const { userId, teamId } = resolveSessionMemoryScope(db, sessionId)
556
+ if (!userId) return { files: {} }
557
+
558
+ const rows = activePersonalMemories(db, userId)
559
+ if (teamId) rows.push(...activeTeamMemories(db, teamId))
560
+ return buildPackFromRows(rows)
561
+ }
562
+
563
+ // --- Write-back (agent → store) -------------------------------------------
564
+
565
+ export interface MemoryWriteBackEntry {
566
+ rel_path: string
567
+ content: string
568
+ }
569
+
570
+ export interface SyncMemoriesResult {
571
+ created: number
572
+ updated: number
573
+ skipped: number
574
+ }
575
+
576
+ /**
577
+ * Ingest write-back entries from a bridge worker. Identity is derived from
578
+ * the session (owner_user_id) — caller-supplied owner/team fields are
579
+ * ignored by design (anti-forgery, TDAM memory-bridge lesson). Entries land
580
+ * in the owner's personal scope with source_session_id provenance.
581
+ *
582
+ * Dedup: FTS recall over the owner's active personal memories by name;
583
+ * a hit updates the existing row (content replace, version bump) instead
584
+ * of inserting a near-duplicate.
585
+ */
586
+ export function syncMemories(
587
+ sessionId: string,
588
+ entries: MemoryWriteBackEntry[],
589
+ ): SyncMemoriesResult {
590
+ const db = getDb()
591
+ const { userId } = resolveSessionMemoryScope(db, sessionId)
592
+ if (!userId) {
593
+ throw new MemoryError(
594
+ 'no_owner',
595
+ 'Session has no user owner to attribute memories to',
596
+ )
597
+ }
598
+
599
+ const result: SyncMemoriesResult = { created: 0, updated: 0, skipped: 0 }
600
+
601
+ for (const entry of entries) {
602
+ const rel = entry.rel_path
603
+ // Gate 1: skip working-layer scratch, dotfiles, non-markdown, and the
604
+ // injected team/ subtree. Team memories were materialized INTO the pack;
605
+ // a child edit to them must not fork a silent personal copy (team
606
+ // memories change through the web UI / promote flow, never write-back).
607
+ if (
608
+ rel === 'MEMORY.md' ||
609
+ rel === 'team/MEMORY.md' ||
610
+ rel.startsWith('team/') ||
611
+ rel.startsWith('.working/') ||
612
+ rel.includes('/.working/') ||
613
+ (rel.split('/').pop() ?? '').startsWith('.') ||
614
+ !rel.endsWith('.md')
615
+ ) {
616
+ result.skipped++
617
+ continue
618
+ }
619
+
620
+ const parsed = parseMemoryFrontmatter(entry.content)
621
+ if (!parsed) {
622
+ result.skipped++
623
+ continue
624
+ }
625
+
626
+ try {
627
+ const existing = findSimilarPersonalMemory(db, userId, parsed.name)
628
+ if (existing) {
629
+ const updated = updateMemory(existing.id, {
630
+ content: parsed.content,
631
+ description: parsed.description ?? undefined,
632
+ })
633
+ if (updated) result.updated++
634
+ } else {
635
+ createMemory({
636
+ scopeKind: 'personal',
637
+ ownerUserId: userId,
638
+ name: parsed.name,
639
+ description: parsed.description,
640
+ type: parsed.type,
641
+ content: parsed.content,
642
+ layer: 'semantic',
643
+ sourceSessionId: sessionId,
644
+ })
645
+ result.created++
646
+ }
647
+ } catch (err) {
648
+ if (err instanceof MemoryError) {
649
+ result.skipped++
650
+ } else {
651
+ throw err
652
+ }
653
+ }
654
+ }
655
+
656
+ if (result.created + result.updated > 0) {
657
+ try {
658
+ publishSessionEvent(
659
+ sessionId,
660
+ 'memory_synced',
661
+ { created: result.created, updated: result.updated },
662
+ 'outbound',
663
+ )
664
+ } catch (err) {
665
+ logError(`[memories] notify event failed session=${sessionId}:`, err)
666
+ }
667
+ log(
668
+ `[memories] sync session=${sessionId} created=${result.created} updated=${result.updated} skipped=${result.skipped}`,
669
+ )
670
+ }
671
+ return result
672
+ }
673
+
674
+ interface ParsedMemoryFile {
675
+ name: string
676
+ description?: string
677
+ type: MemoryType
678
+ content: string
679
+ }
680
+
681
+ const MEMORY_TYPE_SET = new Set<string>(MEMORY_TYPES)
682
+
683
+ /** Minimal frontmatter parse (name/description/type) — memdir file shape. */
684
+ function parseMemoryFrontmatter(raw: string): ParsedMemoryFile | null {
685
+ if (!raw.startsWith('---')) return null
686
+ const end = raw.indexOf('\n---', 3)
687
+ if (end < 0) return null
688
+ const header = raw.slice(3, end)
689
+ const body = raw.slice(end + 4).replace(/^\n+/, '')
690
+
691
+ const fields = new Map<string, string>()
692
+ for (const line of header.split('\n')) {
693
+ const idx = line.indexOf(':')
694
+ if (idx > 0) {
695
+ fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim())
696
+ }
697
+ }
698
+
699
+ const name = fields.get('name')
700
+ const type = fields.get('type')
701
+ if (!name || !type || !MEMORY_TYPE_SET.has(type)) return null
702
+ if (!body.trim()) return null
703
+
704
+ return {
705
+ name,
706
+ description: fields.get('description') || undefined,
707
+ type: type as MemoryType,
708
+ content: body,
709
+ }
710
+ }
711
+
712
+ /** Dedup recall: exact-name match first, then FTS name match, owner-scoped. */
713
+ function findSimilarPersonalMemory(
714
+ db: Database,
715
+ userId: string,
716
+ name: string,
717
+ ): MemoryRow | null {
718
+ const exact = db
719
+ .query(
720
+ `SELECT * FROM memories
721
+ WHERE scope_kind = 'personal' AND owner_user_id = $uid
722
+ AND status = 'active' AND name = $name
723
+ LIMIT 1`,
724
+ )
725
+ .get({ $uid: userId, $name: name }) as MemoryRow | null
726
+ if (exact) return exact
727
+
728
+ try {
729
+ const hit = db
730
+ .query(
731
+ `SELECT m.* FROM memories m
732
+ JOIN memories_fts f ON f.memory_id = m.id
733
+ WHERE memories_fts MATCH $query
734
+ AND m.scope_kind = 'personal' AND m.owner_user_id = $uid
735
+ AND m.status = 'active'
736
+ LIMIT 1`,
737
+ )
738
+ .get({
739
+ $query: `"${name.replace(/"/g, '""')}"`,
740
+ $uid: userId,
741
+ }) as MemoryRow | null
742
+ return hit ?? null
743
+ } catch (err) {
744
+ logError('[memories] dedup fts lookup failed:', err)
745
+ return null
746
+ }
747
+ }