@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.
- package/dist/cli.js +93 -61
- package/dist/rcs-schema.sql +42 -0
- package/dist/rcs-web/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
- package/dist/rcs-web/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/ArtifactsGallery-CNd9-Awp.js → dist/rcs-web/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/Claim-Dq08pl4W.js → dist/rcs-web/assets/Claim-CkuDG_ay.js} +1 -1
- package/dist/rcs-web/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
- package/dist/rcs-web/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/Login-CZbz6Wms.js → dist/rcs-web/assets/Login-Csvt3wvz.js} +1 -1
- package/dist/rcs-web/assets/MemoriesGallery-C5cT73hA.js +1 -0
- package/dist/rcs-web/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
- package/dist/rcs-web/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
- package/dist/rcs-web/assets/SessionDetail-CocGah-8.js +83 -0
- package/dist/rcs-web/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
- package/dist/rcs-web/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
- package/dist/rcs-web/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/TeamDetail-8T_kKUcX.js → dist/rcs-web/assets/TeamDetail-MPfqesJf.js} +2 -2
- package/dist/rcs-web/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
- package/dist/rcs-web/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
- package/dist/rcs-web/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
- package/dist/rcs-web/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
- package/dist/rcs-web/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
- package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → dist/rcs-web/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
- package/dist/rcs-web/assets/channel-DcTKV8fL.js +1 -0
- package/dist/rcs-web/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
- package/dist/rcs-web/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
- package/dist/rcs-web/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-Bgz9JRYO.js → dist/rcs-web/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-DIFGJlEF.js → dist/rcs-web/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
- package/dist/rcs-web/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
- package/dist/rcs-web/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
- package/dist/rcs-web/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
- package/dist/rcs-web/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
- package/dist/rcs-web/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
- package/dist/rcs-web/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-Bn4B33S-.js → dist/rcs-web/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
- package/dist/rcs-web/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
- package/dist/rcs-web/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-CMLav2KT.js → dist/rcs-web/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
- package/dist/rcs-web/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BexFwBQa.js → dist/rcs-web/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
- package/dist/rcs-web/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
- package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → dist/rcs-web/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
- package/dist/rcs-web/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
- package/dist/rcs-web/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
- package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
- package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
- package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
- package/dist/rcs-web/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
- package/dist/rcs-web/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
- package/dist/rcs-web/assets/main-CP31AzxJ.css +1 -0
- package/{packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-5FH_DIq9.js → dist/rcs-web/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
- package/dist/rcs-web/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
- package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
- package/dist/rcs-web/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
- package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
- package/dist/rcs-web/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
- package/dist/rcs-web/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → dist/rcs-web/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
- package/{packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → dist/rcs-web/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
- package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
- package/dist/rcs-web/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
- package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
- package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
- package/dist/rcs-web/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
- package/dist/rcs-web/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
- package/dist/rcs-web/index.html +2 -2
- package/package.json +1 -1
- package/packages/remote-control-server/src/__tests__/memories.test.ts +654 -0
- package/packages/remote-control-server/src/db/rcs-schema.sql +42 -0
- package/packages/remote-control-server/src/registerRoutes.ts +6 -0
- package/packages/remote-control-server/src/routes/v1/memories.ts +132 -0
- package/packages/remote-control-server/src/routes/web/memories.ts +321 -0
- package/packages/remote-control-server/src/services/memories.ts +747 -0
- package/packages/remote-control-server/web/dist/assets/{AdminPanel-B9YWaqlO.js → AdminPanel-D0i_28O2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ArtifactView-B8lAeS5B.js → ArtifactView-Dd6d3O3z.js} +1 -1
- package/{dist/rcs-web/assets/ArtifactsGallery-CNd9-Awp.js → packages/remote-control-server/web/dist/assets/ArtifactsGallery-BTVS7sar.js} +1 -1
- package/{dist/rcs-web/assets/Claim-Dq08pl4W.js → packages/remote-control-server/web/dist/assets/Claim-CkuDG_ay.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{Dashboard-JZcrtByh.js → Dashboard-BBZHnmut.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{Join-DgvRnpyb.js → Join-DbBj-5mj.js} +1 -1
- package/{dist/rcs-web/assets/Login-CZbz6Wms.js → packages/remote-control-server/web/dist/assets/Login-Csvt3wvz.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/MemoriesGallery-C5cT73hA.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{Register-DD7uiwEj.js → Register-Cmp7ogoN.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{RegisterEmpty-BrBfeQXp.js → RegisterEmpty-BoIy42g_.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/SessionDetail-CocGah-8.js +83 -0
- package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-kSo9esvT.js → SessionVisibilityBadge-C873QvnC.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{Setup-Ca30VUSF.js → Setup-DOU0qTNX.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ShareLanding-C_vlxFAM.js → ShareLanding-DMR9eh0F.js} +1 -1
- package/{dist/rcs-web/assets/TeamDetail-8T_kKUcX.js → packages/remote-control-server/web/dist/assets/TeamDetail-MPfqesJf.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{TeamList-DRXGQCJi.js → TeamList-CgWVmdPU.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{TeamSettings-Byhon5C1.js → TeamSettings-V3L0H8Nb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{UserSearchInput-wixyd_8w.js → UserSearchInput-xqtDX6Bz.js} +2 -2
- package/packages/remote-control-server/web/dist/assets/{UserSettings-Ds2retFk.js → UserSettings-Cfqs_5af.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{arc-CThS8KNL.js → arc-Bl97za3P.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-Bko99SuX.js → architectureDiagram-3BPJPVTR-B8IQfxi6.js} +1 -1
- package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-CqCAgp0i.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-DNfBHM8J.js} +1 -1
- package/{dist/rcs-web/assets/c4Diagram-AAUBKEIU-C1pGDG-L.js → packages/remote-control-server/web/dist/assets/c4Diagram-AAUBKEIU-CO4HDUF2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/channel-DcTKV8fL.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-Bz0kJwEY.js → chunk-2J33WTMH-4CBnqzb3.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-DYnlidI7.js → chunk-4BX2VUAB-ClbIGQlY.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-cMV4HRvm.js → chunk-55IACEB6-BnrTfmFM.js} +1 -1
- package/{dist/rcs-web/assets/chunk-727SXJPM-Bgz9JRYO.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-C17NZOQL.js} +1 -1
- package/{dist/rcs-web/assets/chunk-AQP2D5EJ-DIFGJlEF.js → packages/remote-control-server/web/dist/assets/chunk-AQP2D5EJ-CljirtnS.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-BUC7O7T0.js → chunk-FMBD7UC4-Bb20ipZI.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-kGqgVnBZ.js → chunk-ND2GUHAM-L3q9_xAn.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-B01O0qXh.js → chunk-QZHKN3VN-FyYNxzak.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-5DsQRSDS.js +1 -0
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-5DsQRSDS.js +1 -0
- package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-CscK0pCh.js → code-block-IT6T5CEO-CQVTEZwg.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{copy-CvmA5P_Q.js → copy-CFZBXuu9.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{cose-bilkent-S5V4N54A-BsBsVccW.js → cose-bilkent-S5V4N54A-Cs2MVxlS.js} +1 -1
- package/{dist/rcs-web/assets/dagre-BM42HDAG-Bn4B33S-.js → packages/remote-control-server/web/dist/assets/dagre-BM42HDAG-CWZcIIf5.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-DAuAIPR6.js → diagram-2AECGRRQ-DpumSk2d.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-w3k2b4gC.js → diagram-5GNKFQAL-DcF2qwdh.js} +1 -1
- package/{dist/rcs-web/assets/diagram-KO2AKTUF-CMLav2KT.js → packages/remote-control-server/web/dist/assets/diagram-KO2AKTUF-BGnqGSPz.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-D14sXWdh.js → diagram-LMA3HP47-k-UxYOZ9.js} +1 -1
- package/{dist/rcs-web/assets/diagram-OG6HWLK6-BexFwBQa.js → packages/remote-control-server/web/dist/assets/diagram-OG6HWLK6-BMPzmD6S.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{dialog-B4hKrd7w.js → dialog-DKk230y5.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-BA8ANmoV.js → erDiagram-TEJ5UH35-BvmA6W9Z.js} +1 -1
- package/{dist/rcs-web/assets/flowDiagram-I6XJVG4X-dTQIvKsj.js → packages/remote-control-server/web/dist/assets/flowDiagram-I6XJVG4X-ptAnksiF.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ganttDiagram-6RSMTGT7-8SDHYxXc.js → ganttDiagram-6RSMTGT7-Bhgrzcs3.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{gitGraphDiagram-PVQCEYII-Bu7LyvTG.js → gitGraphDiagram-PVQCEYII-B8-vcbF-.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-BuQuDvuX.js → infoDiagram-5YYISTIA-DaW0unc2.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-QjafGk8V.js → ishikawaDiagram-YF4QCWOH-4oYGuWP6.js} +1 -1
- package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-_FOXjXDF.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-B9YXpw0W.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-CbqJqyY6.js → kanban-definition-UN3LZRKU-Cglus7wI.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{linear-H6F41eb2.js → linear-CRTJd3_G.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{main-BopVSHhX.js → main-BKbA2ddo.js} +65 -65
- package/packages/remote-control-server/web/dist/assets/main-CP31AzxJ.css +1 -0
- package/{dist/rcs-web/assets/mermaid-VLURNSYL-5FH_DIq9.js → packages/remote-control-server/web/dist/assets/mermaid-VLURNSYL-D0JeVL0B.js} +4 -4
- package/packages/remote-control-server/web/dist/assets/{mermaid.core-C004NitB.js → mermaid.core-CobUk_K5.js} +5 -5
- package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-utqEJ2a8.js → mindmap-definition-RKZ34NQL-B1s-et_p.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{notifications-DOBs9HVf.js → notifications-BrgRJuzQ.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-OlbTwDZQ.js → pieDiagram-4H26LBE5-PcBs9RT_.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{quadrantDiagram-W4KKPZXB-BOe2DhrU.js → quadrantDiagram-W4KKPZXB-B2GxITjv.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{refresh-cw-BSwpDl-7.js → refresh-cw-BwmFF3kw.js} +1 -1
- package/{dist/rcs-web/assets/requirementDiagram-4Y6WPE33-s6V1yWUG.js → packages/remote-control-server/web/dist/assets/requirementDiagram-4Y6WPE33-BfyZiasB.js} +1 -1
- package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CdJD78Mx.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Br7tRtmq.js} +1 -1
- package/{dist/rcs-web/assets/sequenceDiagram-3UESZ5HK-DvGV6Dhl.js → packages/remote-control-server/web/dist/assets/sequenceDiagram-3UESZ5HK-CN1NSfHS.js} +1 -1
- package/{dist/rcs-web/assets/stateDiagram-AJRCARHV-Bj-_Ulcn.js → packages/remote-control-server/web/dist/assets/stateDiagram-AJRCARHV-C4S8hKUx.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-BLGWynk-.js → stateDiagram-v2-BHNVJYJU-CbEMz-jf.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{timeline-definition-PNZ67QCA-B6VAH6Ka.js → timeline-definition-PNZ67QCA-mTiK0HwD.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-DUwaLI6R.js → vennDiagram-CIIHVFJN-DCe95URb.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-CBetJore.js → wardleyDiagram-YWT4CUSO-Cct1r0Ax.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{workerType-BF4aY_R_.js → workerType-DW9l12WG.js} +1 -1
- package/packages/remote-control-server/web/dist/assets/{xychartDiagram-2RQKCTM6-BBnx6V66.js → xychartDiagram-2RQKCTM6-CVc0eQYa.js} +1 -1
- package/packages/remote-control-server/web/dist/index.html +2 -2
- package/dist/rcs-web/assets/SessionDetail-BbnQBjP1.js +0 -78
- package/dist/rcs-web/assets/channel-D6RbJTfV.js +0 -1
- package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
- package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
- package/dist/rcs-web/assets/main-C0n90aVY.css +0 -1
- package/packages/remote-control-server/web/dist/assets/SessionDetail-BbnQBjP1.js +0 -78
- package/packages/remote-control-server/web/dist/assets/channel-D6RbJTfV.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-BlCPk6aR.js +0 -1
- package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-BlCPk6aR.js +0 -1
- 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
|
+
}
|