@remnic/core 1.0.2 → 1.1.0
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/abort-error.d.ts +32 -0
- package/dist/abort-error.js +11 -0
- package/dist/access-cli.d.ts +13 -3
- package/dist/access-cli.js +96 -80
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +12 -4
- package/dist/access-http.js +25 -18
- package/dist/access-mcp.d.ts +32 -4
- package/dist/access-mcp.js +16 -1
- package/dist/access-schema.d.ts +28 -28
- package/dist/access-schema.js +1 -1
- package/dist/access-service-HmO1Trrx.d.ts +732 -0
- package/dist/access-service.d.ts +15 -601
- package/dist/access-service.js +21 -15
- package/dist/active-memory-bridge.d.ts +66 -0
- package/dist/active-memory-bridge.js +11 -0
- package/dist/active-memory-bridge.js.map +1 -0
- package/dist/active-recall.d.ts +96 -0
- package/dist/active-recall.js +308 -0
- package/dist/active-recall.js.map +1 -0
- package/dist/behavior-learner.js +1 -1
- package/dist/bootstrap.d.ts +6 -3
- package/dist/bootstrap.js +2 -2
- package/dist/boxes.js +2 -2
- package/dist/briefing.d.ts +169 -0
- package/dist/briefing.js +52 -0
- package/dist/briefing.js.map +1 -0
- package/dist/buffer.d.ts +19 -5
- package/dist/buffer.js +2 -2
- package/dist/calibration.js +6 -6
- package/dist/causal-behavior.js +5 -5
- package/dist/causal-chain.js +3 -3
- package/dist/causal-consolidation.d.ts +22 -2
- package/dist/causal-consolidation.js +36 -9
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +6 -6
- package/dist/causal-trajectory-graph.js +1 -1
- package/dist/causal-trajectory.d.ts +14 -1
- package/dist/causal-trajectory.js +5 -1
- package/dist/{chunk-KWBU5S5U.js → chunk-2ODBA7MQ.js} +9 -3
- package/dist/chunk-2ODBA7MQ.js.map +1 -0
- package/dist/{chunk-ZJLY4QSU.js → chunk-37UIFYWO.js} +130 -6
- package/dist/chunk-37UIFYWO.js.map +1 -0
- package/dist/chunk-3PG3H5TD.js +7 -0
- package/dist/chunk-3PG3H5TD.js.map +1 -0
- package/dist/{chunk-NTTLPF7F.js → chunk-3QFQGRHO.js} +5 -5
- package/dist/{chunk-QDOSNLB4.js → chunk-3QHL5ABG.js} +17 -15
- package/dist/chunk-3QHL5ABG.js.map +1 -0
- package/dist/{chunk-6UJQNRIO.js → chunk-3SV6CQHO.js} +92 -33
- package/dist/chunk-3SV6CQHO.js.map +1 -0
- package/dist/{chunk-U4PV25RD.js → chunk-3WHVNEN7.js} +1 -1
- package/dist/chunk-3WHVNEN7.js.map +1 -0
- package/dist/{chunk-XUHI52HK.js → chunk-44ICJRF3.js} +98 -10
- package/dist/chunk-44ICJRF3.js.map +1 -0
- package/dist/{chunk-HG2NKWR2.js → chunk-47UU5PU2.js} +49 -10
- package/dist/chunk-47UU5PU2.js.map +1 -0
- package/dist/chunk-4DJQYKMN.js +187 -0
- package/dist/chunk-4DJQYKMN.js.map +1 -0
- package/dist/chunk-4KAN3GZ3.js +225 -0
- package/dist/chunk-4KAN3GZ3.js.map +1 -0
- package/dist/chunk-4LACOVZX.js +813 -0
- package/dist/chunk-4LACOVZX.js.map +1 -0
- package/dist/{chunk-ORZMT74A.js → chunk-4NRAJUDS.js} +11 -1
- package/dist/chunk-4NRAJUDS.js.map +1 -0
- package/dist/{chunk-B7LOFDVE.js → chunk-4WMCPJWX.js} +8 -3
- package/dist/chunk-4WMCPJWX.js.map +1 -0
- package/dist/{chunk-G3AG3KZN.js → chunk-5IZL4DCV.js} +2 -2
- package/dist/{chunk-BRK4ODMI.js → chunk-5NPGSAVB.js} +2 -2
- package/dist/{chunk-QANCTXQF.js → chunk-6LX5ORAS.js} +3 -3
- package/dist/chunk-6MKAMLQL.js +16 -0
- package/dist/chunk-6MKAMLQL.js.map +1 -0
- package/dist/{chunk-ESSMF2FR.js → chunk-6PFRXT4K.js} +15 -6
- package/dist/chunk-6PFRXT4K.js.map +1 -0
- package/dist/{chunk-UIYZ5T3I.js → chunk-6UJ47TVX.js} +8 -8
- package/dist/chunk-6ZH4TU6I.js +245 -0
- package/dist/chunk-6ZH4TU6I.js.map +1 -0
- package/dist/{chunk-L5RPWGFK.js → chunk-7DHTMOND.js} +2 -2
- package/dist/{chunk-L7WO3MZ4.js → chunk-7ECD5ATE.js} +2 -2
- package/dist/{chunk-Q6FETXJA.js → chunk-7SEAZFFB.js} +2 -2
- package/dist/{chunk-V4YC4LUK.js → chunk-7WQ6SLIE.js} +175 -63
- package/dist/chunk-7WQ6SLIE.js.map +1 -0
- package/dist/chunk-ALXMCZEU.js +332 -0
- package/dist/chunk-ALXMCZEU.js.map +1 -0
- package/dist/{chunk-TVVVQQAK.js → chunk-BLKTA7MM.js} +58 -24
- package/dist/chunk-BLKTA7MM.js.map +1 -0
- package/dist/{chunk-SCHEKPYH.js → chunk-C2EFFULQ.js} +1 -1
- package/dist/{chunk-GJR6D6KC.js → chunk-D654IBA6.js} +2 -2
- package/dist/{chunk-OTFNI3OO.js → chunk-DEPL3635.js} +1828 -401
- package/dist/chunk-DEPL3635.js.map +1 -0
- package/dist/{chunk-UYSKNO6E.js → chunk-DHHP2Z4X.js} +15 -4
- package/dist/chunk-DHHP2Z4X.js.map +1 -0
- package/dist/{chunk-UV2FO7J4.js → chunk-E6K4NIEU.js} +2 -2
- package/dist/{chunk-T4WRIV2C.js → chunk-EABGC2TL.js} +2 -2
- package/dist/chunk-EJI5XIBB.js +232 -0
- package/dist/chunk-EJI5XIBB.js.map +1 -0
- package/dist/{chunk-ONRU4L2N.js → chunk-FEMOX5AD.js} +2 -2
- package/dist/{chunk-IFFFR3MR.js → chunk-FSFEQI74.js} +3 -3
- package/dist/chunk-G4SK7DSQ.js +121 -0
- package/dist/chunk-G4SK7DSQ.js.map +1 -0
- package/dist/{chunk-WWIQTB2Y.js → chunk-GGD5W7TB.js} +9 -2
- package/dist/chunk-GGD5W7TB.js.map +1 -0
- package/dist/{chunk-QWUUMMIK.js → chunk-GV6NLQ4X.js} +1355 -80
- package/dist/chunk-GV6NLQ4X.js.map +1 -0
- package/dist/{chunk-2PO5ZRKV.js → chunk-GZCUW5IC.js} +16 -3
- package/dist/chunk-GZCUW5IC.js.map +1 -0
- package/dist/{chunk-AAI7JARD.js → chunk-HMDCOMYU.js} +8 -11
- package/dist/chunk-HMDCOMYU.js.map +1 -0
- package/dist/chunk-IQT3XTKW.js +121 -0
- package/dist/chunk-IQT3XTKW.js.map +1 -0
- package/dist/{chunk-J3BT33K7.js → chunk-ITRLGI2T.js} +5 -5
- package/dist/{chunk-BDFZXRSO.js → chunk-J4IYOZZ5.js} +15 -2
- package/dist/chunk-J4IYOZZ5.js.map +1 -0
- package/dist/{chunk-J47FNDR7.js → chunk-JIU55F3X.js} +7 -7
- package/dist/{chunk-MDDAA2AO.js → chunk-JL2PU6AI.js} +17 -6
- package/dist/chunk-JL2PU6AI.js.map +1 -0
- package/dist/{chunk-ZKYI7UVO.js → chunk-JR4ZC3G4.js} +2 -2
- package/dist/{chunk-UCYSTFZR.js → chunk-JRNQ3RNA.js} +2 -2
- package/dist/{chunk-GPGBSNKM.js → chunk-K4FLSOR5.js} +2 -2
- package/dist/chunk-KVE7R4CG.js +320 -0
- package/dist/chunk-KVE7R4CG.js.map +1 -0
- package/dist/chunk-LAYN4LDC.js +267 -0
- package/dist/chunk-LAYN4LDC.js.map +1 -0
- package/dist/{chunk-ISY75RLM.js → chunk-MBJHSA7F.js} +344 -7
- package/dist/chunk-MBJHSA7F.js.map +1 -0
- package/dist/{chunk-PGK3VUHN.js → chunk-MTLYEMJB.js} +3 -2
- package/dist/chunk-MTLYEMJB.js.map +1 -0
- package/dist/{chunk-QY2BHY5O.js → chunk-MVTHXUBX.js} +297 -34
- package/dist/chunk-MVTHXUBX.js.map +1 -0
- package/dist/{chunk-LP47L3ZX.js → chunk-N42IWANG.js} +5 -5
- package/dist/{chunk-YNI4S5WT.js → chunk-N53K2EXC.js} +2 -2
- package/dist/{chunk-763GUIOU.js → chunk-NBNN5GOB.js} +2 -2
- package/dist/{chunk-CXWFUJR2.js → chunk-NQEVYWX6.js} +195 -5
- package/dist/chunk-NQEVYWX6.js.map +1 -0
- package/dist/{chunk-KL4CP4SB.js → chunk-O5ETUNBT.js} +17 -5
- package/dist/chunk-O5ETUNBT.js.map +1 -0
- package/dist/{chunk-OOSWAUYB.js → chunk-ODWDQNRE.js} +2 -2
- package/dist/chunk-OIT5QGG4.js +80 -0
- package/dist/chunk-OIT5QGG4.js.map +1 -0
- package/dist/{chunk-HLBYLYRD.js → chunk-PAORGQRI.js} +70 -13
- package/dist/chunk-PAORGQRI.js.map +1 -0
- package/dist/chunk-PVGDJXVK.js +21 -0
- package/dist/chunk-PVGDJXVK.js.map +1 -0
- package/dist/{chunk-OTAVQCSF.js → chunk-PYXS46O7.js} +2 -2
- package/dist/chunk-QDW3E4RD.js +108 -0
- package/dist/chunk-QDW3E4RD.js.map +1 -0
- package/dist/{chunk-YNCQ7E4M.js → chunk-QDYXG4CS.js} +4 -3
- package/dist/chunk-QDYXG4CS.js.map +1 -0
- package/dist/{chunk-HLXVTBF3.js → chunk-QNJMBKFK.js} +3 -2
- package/dist/chunk-QNJMBKFK.js.map +1 -0
- package/dist/{chunk-4A24LIM2.js → chunk-S75M5ZRK.js} +2 -2
- package/dist/chunk-SYUK3VLY.js +789 -0
- package/dist/chunk-SYUK3VLY.js.map +1 -0
- package/dist/{chunk-QCCCQT3O.js → chunk-TBBDFYXW.js} +2 -2
- package/dist/chunk-TBBDFYXW.js.map +1 -0
- package/dist/chunk-U66YHYC7.js +31 -0
- package/dist/chunk-U66YHYC7.js.map +1 -0
- package/dist/{chunk-MWGVGUIS.js → chunk-UEYA6UC7.js} +36 -4
- package/dist/chunk-UEYA6UC7.js.map +1 -0
- package/dist/{chunk-M5KEYE5E.js → chunk-URB2WSKZ.js} +2 -2
- package/dist/chunk-UVJFDP7P.js +202 -0
- package/dist/chunk-UVJFDP7P.js.map +1 -0
- package/dist/chunk-W6SL7OFG.js +180 -0
- package/dist/chunk-W6SL7OFG.js.map +1 -0
- package/dist/chunk-WBSAYXVI.js +7945 -0
- package/dist/chunk-WBSAYXVI.js.map +1 -0
- package/dist/{chunk-M5ZBBBJI.js → chunk-XZ2TIKGC.js} +39 -9
- package/dist/chunk-XZ2TIKGC.js.map +1 -0
- package/dist/chunk-Y4FHOFJ2.js +140 -0
- package/dist/chunk-Y4FHOFJ2.js.map +1 -0
- package/dist/chunk-YDBIWGNI.js +298 -0
- package/dist/chunk-YDBIWGNI.js.map +1 -0
- package/dist/chunk-YNB73F22.js +137 -0
- package/dist/chunk-YNB73F22.js.map +1 -0
- package/dist/{chunk-IZME7KW2.js → chunk-ZVBB3T7V.js} +31 -12
- package/dist/chunk-ZVBB3T7V.js.map +1 -0
- package/dist/chunking.js +1 -1
- package/dist/citations.d.ts +67 -0
- package/dist/citations.js +13 -0
- package/dist/citations.js.map +1 -0
- package/dist/cli-BneVIEvh.d.ts +1240 -0
- package/dist/cli.d.ts +32 -1147
- package/dist/cli.js +150 -7092
- package/dist/cli.js.map +1 -1
- package/dist/codex-materialize-CQlLTzke.d.ts +139 -0
- package/dist/codex-thread-key.d.ts +3 -0
- package/dist/codex-thread-key.js +7 -0
- package/dist/codex-thread-key.js.map +1 -0
- package/dist/config.js +3 -2
- package/dist/connectors/codex/instructions.md +160 -0
- package/dist/connectors/codex/resources/namespace-cheatsheet.md +48 -0
- package/dist/contradiction-review-WIUBAR52.js +21 -0
- package/dist/contradiction-review-WIUBAR52.js.map +1 -0
- package/dist/contradiction-scan-GR33PONM.js +376 -0
- package/dist/contradiction-scan-GR33PONM.js.map +1 -0
- package/dist/day-summary.d.ts +7 -2
- package/dist/day-summary.js +5 -2
- package/dist/direct-answer-wiring.d.ts +77 -0
- package/dist/direct-answer-wiring.js +75 -0
- package/dist/direct-answer-wiring.js.map +1 -0
- package/dist/direct-answer.d.ts +106 -0
- package/dist/direct-answer.js +10 -0
- package/dist/direct-answer.js.map +1 -0
- package/dist/embedding-fallback.d.ts +96 -2
- package/dist/embedding-fallback.js +6 -4
- package/dist/{engine-2A6J4XEX.js → engine-5TIQBYZR.js} +10 -7
- package/dist/engine-5TIQBYZR.js.map +1 -0
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +10 -7
- package/dist/entity-schema.d.ts +11 -0
- package/dist/entity-schema.js +19 -0
- package/dist/entity-schema.js.map +1 -0
- package/dist/explicit-capture.d.ts +6 -3
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge.d.ts +66 -0
- package/dist/extraction-judge.js +18 -0
- package/dist/extraction-judge.js.map +1 -0
- package/dist/extraction.d.ts +1 -0
- package/dist/extraction.js +12 -10
- package/dist/fallback-llm.d.ts +11 -2
- package/dist/fallback-llm.js +4 -4
- package/dist/graph.js +1 -1
- package/dist/harmonic-retrieval.js +2 -1
- package/dist/importance.d.ts +11 -1
- package/dist/importance.js +3 -1
- package/dist/index.d.ts +1027 -9
- package/dist/index.js +3303 -349
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +2 -1
- package/dist/intent.js +3 -1
- package/dist/lifecycle.js +1 -1
- package/dist/local-llm.d.ts +10 -3
- package/dist/local-llm.js +2 -2
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +1 -1
- package/dist/memory-cache.d.ts +2 -2
- package/dist/memory-cache.js +1 -1
- package/dist/{memory-projection-store-NxMkbocT.d.ts → memory-projection-store-DeSXPh1j.d.ts} +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/model-registry.js +2 -2
- package/dist/models-json.js +2 -2
- package/dist/native-knowledge.js +2 -2
- package/dist/negative.js +2 -2
- package/dist/operator-toolkit.js +20 -15
- package/dist/{orchestrator-zTa-Qo-1.d.ts → orchestrator-DRYA6_lW.d.ts} +273 -9
- package/dist/orchestrator.d.ts +6 -3
- package/dist/orchestrator.js +76 -63
- package/dist/page-versioning.d.ts +77 -0
- package/dist/page-versioning.js +15 -0
- package/dist/page-versioning.js.map +1 -0
- package/dist/plugin-id.d.ts +37 -0
- package/dist/plugin-id.js +11 -0
- package/dist/plugin-id.js.map +1 -0
- package/dist/policy-runtime.js +2 -2
- package/dist/profiling.js +2 -2
- package/dist/qmd.d.ts +5 -2
- package/dist/qmd.js +4 -3
- package/dist/recall-audit.d.ts +20 -0
- package/dist/recall-audit.js +50 -0
- package/dist/recall-audit.js.map +1 -0
- package/dist/recall-mmr.d.ts +152 -0
- package/dist/recall-mmr.js +17 -0
- package/dist/recall-mmr.js.map +1 -0
- package/dist/recall-qos.js +2 -2
- package/dist/recall-state.d.ts +28 -1
- package/dist/recall-state.js +2 -2
- package/dist/relevance.js +2 -2
- package/dist/resolution-QBTDHTG7.js +100 -0
- package/dist/resolution-QBTDHTG7.js.map +1 -0
- package/dist/resolve-provider-secret.d.ts +24 -1
- package/dist/resolve-provider-secret.js +4 -2
- package/dist/resume-bundles.js +6 -5
- package/dist/retrieval-agents.js +2 -2
- package/dist/retrieval.js +2 -2
- package/dist/schemas.d.ts +412 -54
- package/dist/schemas.js +3 -1
- package/dist/sdk-compat.d.ts +2 -0
- package/dist/sdk-compat.js +6 -3
- package/dist/sdk-compat.js.map +1 -1
- package/dist/semantic-chunking.d.ts +87 -0
- package/dist/semantic-chunking.js +20 -0
- package/dist/semantic-chunking.js.map +1 -0
- package/dist/semantic-consolidation-DrvSYRdB.d.ts +119 -0
- package/dist/semantic-consolidation.d.ts +4 -42
- package/dist/semantic-consolidation.js +23 -2
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/session-observer-state.js +2 -2
- package/dist/session-toggles.d.ts +22 -0
- package/dist/session-toggles.js +116 -0
- package/dist/session-toggles.js.map +1 -0
- package/dist/skills-registry.d.ts +47 -0
- package/dist/skills-registry.js +48 -0
- package/dist/skills-registry.js.map +1 -0
- package/dist/source-attribution.d.ts +169 -0
- package/dist/source-attribution.js +27 -0
- package/dist/source-attribution.js.map +1 -0
- package/dist/storage.d.ts +171 -10
- package/dist/storage.js +16 -5
- package/dist/summarizer.js +7 -7
- package/dist/temporal-supersession.d.ts +127 -0
- package/dist/temporal-supersession.js +20 -0
- package/dist/temporal-supersession.js.map +1 -0
- package/dist/threading.js +2 -2
- package/dist/tier-migration.d.ts +2 -1
- package/dist/tier-routing.js +2 -2
- package/dist/tokens.d.ts +21 -1
- package/dist/tokens.js +5 -1
- package/dist/transcript.js +2 -2
- package/dist/types-DJhqDJUV.d.ts +50 -0
- package/dist/types.d.ts +529 -3
- package/dist/types.js +1 -1
- package/dist/utility-learner.js +2 -2
- package/dist/utility-runtime.js +3 -3
- package/dist/verified-recall.js +11 -8
- package/dist/whitespace.d.ts +4 -0
- package/dist/whitespace.js +9 -0
- package/dist/whitespace.js.map +1 -0
- package/package.json +14 -8
- package/dist/chunk-2CJCWDMR.js +0 -87
- package/dist/chunk-2CJCWDMR.js.map +0 -1
- package/dist/chunk-2PO5ZRKV.js.map +0 -1
- package/dist/chunk-6UJQNRIO.js.map +0 -1
- package/dist/chunk-AAI7JARD.js.map +0 -1
- package/dist/chunk-B7LOFDVE.js.map +0 -1
- package/dist/chunk-BDFZXRSO.js.map +0 -1
- package/dist/chunk-CXWFUJR2.js.map +0 -1
- package/dist/chunk-DORBM6OB.js +0 -81
- package/dist/chunk-DORBM6OB.js.map +0 -1
- package/dist/chunk-ESSMF2FR.js.map +0 -1
- package/dist/chunk-HG2NKWR2.js.map +0 -1
- package/dist/chunk-HLBYLYRD.js.map +0 -1
- package/dist/chunk-HLXVTBF3.js.map +0 -1
- package/dist/chunk-ISY75RLM.js.map +0 -1
- package/dist/chunk-IZME7KW2.js.map +0 -1
- package/dist/chunk-KL4CP4SB.js.map +0 -1
- package/dist/chunk-KWBU5S5U.js.map +0 -1
- package/dist/chunk-M5ZBBBJI.js.map +0 -1
- package/dist/chunk-MDDAA2AO.js.map +0 -1
- package/dist/chunk-MWGVGUIS.js.map +0 -1
- package/dist/chunk-ORZMT74A.js.map +0 -1
- package/dist/chunk-OTFNI3OO.js.map +0 -1
- package/dist/chunk-PGK3VUHN.js.map +0 -1
- package/dist/chunk-QCCCQT3O.js.map +0 -1
- package/dist/chunk-QDOSNLB4.js.map +0 -1
- package/dist/chunk-QPKFPHOO.js +0 -178
- package/dist/chunk-QPKFPHOO.js.map +0 -1
- package/dist/chunk-QWUUMMIK.js.map +0 -1
- package/dist/chunk-QY2BHY5O.js.map +0 -1
- package/dist/chunk-TVVVQQAK.js.map +0 -1
- package/dist/chunk-U4PV25RD.js.map +0 -1
- package/dist/chunk-UYSKNO6E.js.map +0 -1
- package/dist/chunk-V4YC4LUK.js.map +0 -1
- package/dist/chunk-WWIQTB2Y.js.map +0 -1
- package/dist/chunk-XUHI52HK.js.map +0 -1
- package/dist/chunk-YNCQ7E4M.js.map +0 -1
- package/dist/chunk-ZJLY4QSU.js.map +0 -1
- /package/dist/{engine-2A6J4XEX.js.map → abort-error.js.map} +0 -0
- /package/dist/{chunk-NTTLPF7F.js.map → chunk-3QFQGRHO.js.map} +0 -0
- /package/dist/{chunk-G3AG3KZN.js.map → chunk-5IZL4DCV.js.map} +0 -0
- /package/dist/{chunk-BRK4ODMI.js.map → chunk-5NPGSAVB.js.map} +0 -0
- /package/dist/{chunk-QANCTXQF.js.map → chunk-6LX5ORAS.js.map} +0 -0
- /package/dist/{chunk-UIYZ5T3I.js.map → chunk-6UJ47TVX.js.map} +0 -0
- /package/dist/{chunk-L5RPWGFK.js.map → chunk-7DHTMOND.js.map} +0 -0
- /package/dist/{chunk-L7WO3MZ4.js.map → chunk-7ECD5ATE.js.map} +0 -0
- /package/dist/{chunk-Q6FETXJA.js.map → chunk-7SEAZFFB.js.map} +0 -0
- /package/dist/{chunk-SCHEKPYH.js.map → chunk-C2EFFULQ.js.map} +0 -0
- /package/dist/{chunk-GJR6D6KC.js.map → chunk-D654IBA6.js.map} +0 -0
- /package/dist/{chunk-UV2FO7J4.js.map → chunk-E6K4NIEU.js.map} +0 -0
- /package/dist/{chunk-T4WRIV2C.js.map → chunk-EABGC2TL.js.map} +0 -0
- /package/dist/{chunk-ONRU4L2N.js.map → chunk-FEMOX5AD.js.map} +0 -0
- /package/dist/{chunk-IFFFR3MR.js.map → chunk-FSFEQI74.js.map} +0 -0
- /package/dist/{chunk-J3BT33K7.js.map → chunk-ITRLGI2T.js.map} +0 -0
- /package/dist/{chunk-J47FNDR7.js.map → chunk-JIU55F3X.js.map} +0 -0
- /package/dist/{chunk-ZKYI7UVO.js.map → chunk-JR4ZC3G4.js.map} +0 -0
- /package/dist/{chunk-UCYSTFZR.js.map → chunk-JRNQ3RNA.js.map} +0 -0
- /package/dist/{chunk-GPGBSNKM.js.map → chunk-K4FLSOR5.js.map} +0 -0
- /package/dist/{chunk-LP47L3ZX.js.map → chunk-N42IWANG.js.map} +0 -0
- /package/dist/{chunk-YNI4S5WT.js.map → chunk-N53K2EXC.js.map} +0 -0
- /package/dist/{chunk-763GUIOU.js.map → chunk-NBNN5GOB.js.map} +0 -0
- /package/dist/{chunk-OOSWAUYB.js.map → chunk-ODWDQNRE.js.map} +0 -0
- /package/dist/{chunk-OTAVQCSF.js.map → chunk-PYXS46O7.js.map} +0 -0
- /package/dist/{chunk-4A24LIM2.js.map → chunk-S75M5ZRK.js.map} +0 -0
- /package/dist/{chunk-M5KEYE5E.js.map → chunk-URB2WSKZ.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildTokenEntry,
|
|
3
|
+
commitTokenEntry,
|
|
2
4
|
generateToken,
|
|
3
5
|
getAllValidTokens,
|
|
4
6
|
getAllValidTokensCached,
|
|
@@ -7,105 +9,195 @@ import {
|
|
|
7
9
|
resolveConnectorFromToken,
|
|
8
10
|
revokeToken,
|
|
9
11
|
saveTokenStore
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-O5ETUNBT.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveCategory
|
|
15
|
+
} from "./chunk-OIT5QGG4.js";
|
|
16
|
+
import {
|
|
17
|
+
FILTER_LABELS,
|
|
18
|
+
isDirectAnswerEligible
|
|
19
|
+
} from "./chunk-Y4FHOFJ2.js";
|
|
20
|
+
import {
|
|
21
|
+
clearBulkImportSources,
|
|
22
|
+
clearTrainingExportAdapters,
|
|
23
|
+
convertMemoriesToRecords,
|
|
24
|
+
formatBatchTranscript,
|
|
25
|
+
getBulkImportSource,
|
|
26
|
+
getTrainingExportAdapter,
|
|
27
|
+
isImportRole,
|
|
28
|
+
listBulkImportSources,
|
|
29
|
+
listTrainingExportAdapters,
|
|
30
|
+
parseIsoTimestamp,
|
|
31
|
+
parseStrictCliDate,
|
|
32
|
+
registerBulkImportSource,
|
|
33
|
+
registerTrainingExportAdapter,
|
|
34
|
+
runBulkImportCliCommand,
|
|
35
|
+
runBulkImportPipeline,
|
|
36
|
+
validateImportTurn
|
|
37
|
+
} from "./chunk-WBSAYXVI.js";
|
|
38
|
+
import "./chunk-7ECD5ATE.js";
|
|
39
|
+
import "./chunk-6LX5ORAS.js";
|
|
40
|
+
import "./chunk-3QHL5ABG.js";
|
|
41
|
+
import "./chunk-HL4DB7TO.js";
|
|
42
|
+
import "./chunk-ZPKBYX2F.js";
|
|
43
|
+
import "./chunk-3SLRNYNG.js";
|
|
44
|
+
import "./chunk-LIRZNNUP.js";
|
|
45
|
+
import "./chunk-Y4Z4I6WK.js";
|
|
46
|
+
import {
|
|
47
|
+
getMemoryForActiveMemory,
|
|
48
|
+
recallForActiveMemory
|
|
49
|
+
} from "./chunk-G4SK7DSQ.js";
|
|
50
|
+
import "./chunk-6MKAMLQL.js";
|
|
11
51
|
import {
|
|
12
52
|
Orchestrator,
|
|
53
|
+
buildProcedureRecallSection,
|
|
54
|
+
decideSemanticDedup,
|
|
13
55
|
defaultWorkspaceDir,
|
|
14
56
|
migrateFromEngram,
|
|
15
57
|
rollbackFromEngramMigration,
|
|
16
58
|
sanitizeSessionKeyForFilename
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-IFFFR3MR.js";
|
|
20
|
-
import "./chunk-Z5AAYHUC.js";
|
|
21
|
-
import "./chunk-4A24LIM2.js";
|
|
22
|
-
import "./chunk-TPB3I2AC.js";
|
|
59
|
+
} from "./chunk-DEPL3635.js";
|
|
60
|
+
import "./chunk-DHHP2Z4X.js";
|
|
23
61
|
import "./chunk-UHGBNIOS.js";
|
|
24
|
-
import "./chunk-
|
|
25
|
-
import "./chunk-
|
|
62
|
+
import "./chunk-FSFEQI74.js";
|
|
63
|
+
import "./chunk-N42IWANG.js";
|
|
26
64
|
import "./chunk-ETOW6ACV.js";
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-
|
|
30
|
-
import "./chunk-
|
|
31
|
-
import "./chunk-
|
|
65
|
+
import "./chunk-W6SL7OFG.js";
|
|
66
|
+
import "./chunk-Z5AAYHUC.js";
|
|
67
|
+
import "./chunk-S75M5ZRK.js";
|
|
68
|
+
import "./chunk-TPB3I2AC.js";
|
|
69
|
+
import "./chunk-JR4ZC3G4.js";
|
|
70
|
+
import "./chunk-KVE7R4CG.js";
|
|
32
71
|
import "./chunk-X7XN6YU4.js";
|
|
33
|
-
import "./chunk-
|
|
72
|
+
import "./chunk-5NPGSAVB.js";
|
|
34
73
|
import "./chunk-C7VW7C3F.js";
|
|
74
|
+
import "./chunk-K4FLSOR5.js";
|
|
75
|
+
import "./chunk-V3RXWQIE.js";
|
|
76
|
+
import "./chunk-5IZL4DCV.js";
|
|
35
77
|
import "./chunk-YCN4BVDK.js";
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
78
|
+
import "./chunk-YDBIWGNI.js";
|
|
79
|
+
import "./chunk-7DHTMOND.js";
|
|
80
|
+
import "./chunk-47UU5PU2.js";
|
|
81
|
+
import "./chunk-D654IBA6.js";
|
|
38
82
|
import "./chunk-H63EDPFJ.js";
|
|
39
|
-
import "./chunk-
|
|
83
|
+
import "./chunk-YAZNBMNF.js";
|
|
84
|
+
import {
|
|
85
|
+
hasBroadGraphIntent,
|
|
86
|
+
inferIntentFromText,
|
|
87
|
+
intentCompatibilityScore,
|
|
88
|
+
isTaskInitiationIntent,
|
|
89
|
+
planRecallMode
|
|
90
|
+
} from "./chunk-GGD5W7TB.js";
|
|
91
|
+
import {
|
|
92
|
+
clearVerdictCache,
|
|
93
|
+
createVerdictCache,
|
|
94
|
+
judgeFactDurability,
|
|
95
|
+
verdictCacheSize
|
|
96
|
+
} from "./chunk-LAYN4LDC.js";
|
|
40
97
|
import {
|
|
41
98
|
ExtractionEngine
|
|
42
|
-
} from "./chunk-
|
|
43
|
-
import "./chunk-
|
|
44
|
-
import "./chunk-
|
|
45
|
-
import "./chunk-
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-YAZNBMNF.js";
|
|
99
|
+
} from "./chunk-3SV6CQHO.js";
|
|
100
|
+
import "./chunk-UEYA6UC7.js";
|
|
101
|
+
import "./chunk-NBNN5GOB.js";
|
|
102
|
+
import "./chunk-FEMOX5AD.js";
|
|
103
|
+
import "./chunk-JL2PU6AI.js";
|
|
48
104
|
import {
|
|
105
|
+
buildExtensionsFooterForSummary,
|
|
49
106
|
loadDaySummaryPrompt
|
|
50
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-GZCUW5IC.js";
|
|
51
108
|
import "./chunk-VEWZZM3H.js";
|
|
52
|
-
import "./chunk-
|
|
109
|
+
import "./chunk-ALXMCZEU.js";
|
|
53
110
|
import {
|
|
54
111
|
buildEntityRecallSection
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import "./chunk-
|
|
57
|
-
import "./chunk-
|
|
112
|
+
} from "./chunk-7WQ6SLIE.js";
|
|
113
|
+
import "./chunk-PAORGQRI.js";
|
|
114
|
+
import "./chunk-6UJ47TVX.js";
|
|
115
|
+
import "./chunk-PYXS46O7.js";
|
|
58
116
|
import "./chunk-3QKK7QOS.js";
|
|
59
|
-
import "./chunk-
|
|
60
|
-
import "./chunk-
|
|
61
|
-
import "./chunk-J47FNDR7.js";
|
|
117
|
+
import "./chunk-JRNQ3RNA.js";
|
|
118
|
+
import "./chunk-JIU55F3X.js";
|
|
62
119
|
import "./chunk-CULXMQJH.js";
|
|
63
|
-
import "./chunk-
|
|
64
|
-
import "./chunk-T4WRIV2C.js";
|
|
65
|
-
import "./chunk-LOBRX7VD.js";
|
|
120
|
+
import "./chunk-E6K4NIEU.js";
|
|
66
121
|
import {
|
|
67
122
|
LanceDbBackend,
|
|
68
123
|
MeilisearchBackend,
|
|
69
124
|
OramaBackend
|
|
70
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-ZVBB3T7V.js";
|
|
71
126
|
import "./chunk-YRMVARQP.js";
|
|
72
127
|
import {
|
|
73
128
|
QmdClient
|
|
74
|
-
} from "./chunk-
|
|
75
|
-
import
|
|
129
|
+
} from "./chunk-BLKTA7MM.js";
|
|
130
|
+
import {
|
|
131
|
+
LEGACY_PLUGIN_ID,
|
|
132
|
+
PLUGIN_ID,
|
|
133
|
+
resolveRemnicPluginEntry
|
|
134
|
+
} from "./chunk-U66YHYC7.js";
|
|
135
|
+
import "./chunk-EABGC2TL.js";
|
|
136
|
+
import "./chunk-LOBRX7VD.js";
|
|
137
|
+
import "./chunk-HMDCOMYU.js";
|
|
138
|
+
import "./chunk-J4IYOZZ5.js";
|
|
76
139
|
import "./chunk-LK6SGL53.js";
|
|
77
|
-
import "./chunk-
|
|
78
|
-
import "./chunk-C6QPK5GG.js";
|
|
79
|
-
import "./chunk-Q6FETXJA.js";
|
|
140
|
+
import "./chunk-7SEAZFFB.js";
|
|
80
141
|
import "./chunk-K6WK37A6.js";
|
|
81
|
-
import
|
|
82
|
-
|
|
83
|
-
|
|
142
|
+
import {
|
|
143
|
+
CODEX_THREAD_KEY_PREFIX
|
|
144
|
+
} from "./chunk-3PG3H5TD.js";
|
|
84
145
|
import "./chunk-FYIYMQ5N.js";
|
|
85
146
|
import "./chunk-2NMMFZ5T.js";
|
|
86
147
|
import {
|
|
148
|
+
coerceInstallExtension,
|
|
87
149
|
parseConfig
|
|
88
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-MBJHSA7F.js";
|
|
89
151
|
import "./chunk-Z5LAYHGJ.js";
|
|
152
|
+
import "./chunk-C6QPK5GG.js";
|
|
153
|
+
import {
|
|
154
|
+
MATERIALIZE_VERSION,
|
|
155
|
+
SENTINEL_FILE,
|
|
156
|
+
buildExtensionsBlockForConsolidation,
|
|
157
|
+
describeMemoriesDir,
|
|
158
|
+
ensureSentinel,
|
|
159
|
+
materializeForNamespace,
|
|
160
|
+
runCodexMaterialize,
|
|
161
|
+
runPostConsolidationMaterialize
|
|
162
|
+
} from "./chunk-SYUK3VLY.js";
|
|
163
|
+
import {
|
|
164
|
+
REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT,
|
|
165
|
+
discoverMemoryExtensions,
|
|
166
|
+
renderExtensionsBlock,
|
|
167
|
+
renderExtensionsFooter,
|
|
168
|
+
resolveExtensionsRoot
|
|
169
|
+
} from "./chunk-EJI5XIBB.js";
|
|
170
|
+
import {
|
|
171
|
+
FallbackLlmClient
|
|
172
|
+
} from "./chunk-44ICJRF3.js";
|
|
173
|
+
import "./chunk-XZ2TIKGC.js";
|
|
174
|
+
import "./chunk-Y27UJK6V.js";
|
|
175
|
+
import "./chunk-ODWDQNRE.js";
|
|
176
|
+
import "./chunk-UZB5KHKX.js";
|
|
177
|
+
import "./chunk-C2EFFULQ.js";
|
|
178
|
+
import "./chunk-4WMCPJWX.js";
|
|
179
|
+
import "./chunk-6HZ6AO2P.js";
|
|
90
180
|
import "./chunk-JWPLJLDU.js";
|
|
91
181
|
import {
|
|
92
182
|
BootstrapEngine
|
|
93
|
-
} from "./chunk-
|
|
94
|
-
import "./chunk-
|
|
183
|
+
} from "./chunk-N53K2EXC.js";
|
|
184
|
+
import "./chunk-URB2WSKZ.js";
|
|
185
|
+
import "./chunk-UVJFDP7P.js";
|
|
95
186
|
import "./chunk-XYIK4LF6.js";
|
|
96
|
-
import "./chunk-
|
|
97
|
-
import "./chunk-M5ZBBBJI.js";
|
|
98
|
-
import "./chunk-OOSWAUYB.js";
|
|
99
|
-
import "./chunk-Y27UJK6V.js";
|
|
100
|
-
import "./chunk-UZB5KHKX.js";
|
|
101
|
-
import "./chunk-M5KEYE5E.js";
|
|
187
|
+
import "./chunk-PVGDJXVK.js";
|
|
102
188
|
import "./chunk-NGAVDO7E.js";
|
|
103
189
|
import {
|
|
104
190
|
EngramAccessHttpServer
|
|
105
|
-
} from "./chunk-
|
|
191
|
+
} from "./chunk-37UIFYWO.js";
|
|
106
192
|
import {
|
|
107
193
|
EngramMcpServer
|
|
108
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-NQEVYWX6.js";
|
|
195
|
+
import {
|
|
196
|
+
buildCitationGuidance,
|
|
197
|
+
formatOaiMemCitation,
|
|
198
|
+
parseOaiMemCitation,
|
|
199
|
+
sanitizeNoteForCitation
|
|
200
|
+
} from "./chunk-IQT3XTKW.js";
|
|
109
201
|
import "./chunk-MARWOCVP.js";
|
|
110
202
|
import {
|
|
111
203
|
formatZodError,
|
|
@@ -114,47 +206,479 @@ import {
|
|
|
114
206
|
recallRequestSchema,
|
|
115
207
|
suggestionSubmitRequestSchema,
|
|
116
208
|
validateRequest
|
|
117
|
-
} from "./chunk-
|
|
209
|
+
} from "./chunk-MTLYEMJB.js";
|
|
118
210
|
import {
|
|
119
211
|
EngramAccessInputError,
|
|
120
212
|
EngramAccessService
|
|
121
|
-
} from "./chunk-
|
|
122
|
-
import "./chunk-
|
|
213
|
+
} from "./chunk-MVTHXUBX.js";
|
|
214
|
+
import "./chunk-ITRLGI2T.js";
|
|
123
215
|
import {
|
|
124
216
|
isTrustZoneName
|
|
125
217
|
} from "./chunk-EQINRHYR.js";
|
|
126
|
-
import "./chunk-
|
|
218
|
+
import "./chunk-QDYXG4CS.js";
|
|
219
|
+
import "./chunk-QNJMBKFK.js";
|
|
127
220
|
import "./chunk-EEQLFRUM.js";
|
|
221
|
+
import {
|
|
222
|
+
buildProcedureMarkdownBody,
|
|
223
|
+
parseProcedureStepsFromBody
|
|
224
|
+
} from "./chunk-QDW3E4RD.js";
|
|
225
|
+
import "./chunk-4NRAJUDS.js";
|
|
226
|
+
import "./chunk-DT5TVLJE.js";
|
|
227
|
+
import {
|
|
228
|
+
resolvePrincipal
|
|
229
|
+
} from "./chunk-N5AKDXAI.js";
|
|
230
|
+
import "./chunk-TBBDFYXW.js";
|
|
231
|
+
import "./chunk-DGXUHMOV.js";
|
|
232
|
+
import "./chunk-LPSF4OQH.js";
|
|
233
|
+
import "./chunk-XKECPATV.js";
|
|
234
|
+
import {
|
|
235
|
+
BRIEFING_FORMAT_ALLOWED,
|
|
236
|
+
FileCalendarSource,
|
|
237
|
+
briefingFilename,
|
|
238
|
+
buildBriefing,
|
|
239
|
+
focusMatchesEntity,
|
|
240
|
+
focusMatchesMemory,
|
|
241
|
+
parseBriefingFocus,
|
|
242
|
+
parseBriefingWindow,
|
|
243
|
+
renderBriefingMarkdown,
|
|
244
|
+
resolveBriefingSaveDir,
|
|
245
|
+
validateBriefingFormat
|
|
246
|
+
} from "./chunk-4LACOVZX.js";
|
|
128
247
|
import {
|
|
129
248
|
StorageManager
|
|
130
|
-
} from "./chunk-
|
|
131
|
-
import "./chunk-
|
|
132
|
-
import
|
|
133
|
-
|
|
249
|
+
} from "./chunk-GV6NLQ4X.js";
|
|
250
|
+
import "./chunk-3WHVNEN7.js";
|
|
251
|
+
import {
|
|
252
|
+
CITATION_UNKNOWN,
|
|
253
|
+
DEFAULT_CITATION_FORMAT,
|
|
254
|
+
attachCitation,
|
|
255
|
+
deriveSessionId,
|
|
256
|
+
formatCitation,
|
|
257
|
+
hasCitation,
|
|
258
|
+
parseAllCitations,
|
|
259
|
+
parseCitation,
|
|
260
|
+
stripCitation
|
|
261
|
+
} from "./chunk-4KAN3GZ3.js";
|
|
262
|
+
import {
|
|
263
|
+
createVersion,
|
|
264
|
+
diffVersions,
|
|
265
|
+
getVersion,
|
|
266
|
+
listVersions,
|
|
267
|
+
revertToVersion
|
|
268
|
+
} from "./chunk-6ZH4TU6I.js";
|
|
134
269
|
import "./chunk-SCU65EZI.js";
|
|
135
270
|
import "./chunk-BOUYNNYD.js";
|
|
271
|
+
import "./chunk-6PFRXT4K.js";
|
|
272
|
+
import "./chunk-TP4FZJIZ.js";
|
|
136
273
|
import "./chunk-DM2T26WE.js";
|
|
137
274
|
import "./chunk-QSVPYQPG.js";
|
|
138
|
-
import "./chunk-YNCQ7E4M.js";
|
|
139
|
-
import "./chunk-HLXVTBF3.js";
|
|
140
275
|
import "./chunk-M62O4P4T.js";
|
|
141
|
-
import "./chunk-
|
|
276
|
+
import "./chunk-4DJQYKMN.js";
|
|
142
277
|
import {
|
|
143
278
|
initLogger,
|
|
144
279
|
log
|
|
145
|
-
} from "./chunk-
|
|
146
|
-
import "./chunk-DGXUHMOV.js";
|
|
147
|
-
import "./chunk-LPSF4OQH.js";
|
|
148
|
-
import "./chunk-XKECPATV.js";
|
|
149
|
-
import "./chunk-6HZ6AO2P.js";
|
|
150
|
-
import "./chunk-QCCCQT3O.js";
|
|
280
|
+
} from "./chunk-2ODBA7MQ.js";
|
|
151
281
|
|
|
152
|
-
// src/
|
|
282
|
+
// src/binary-lifecycle/types.ts
|
|
283
|
+
var DEFAULT_SCAN_PATTERNS = [
|
|
284
|
+
"*.png",
|
|
285
|
+
"*.jpg",
|
|
286
|
+
"*.jpeg",
|
|
287
|
+
"*.gif",
|
|
288
|
+
"*.pdf",
|
|
289
|
+
"*.mp3",
|
|
290
|
+
"*.mp4",
|
|
291
|
+
"*.wav"
|
|
292
|
+
];
|
|
293
|
+
var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
|
|
294
|
+
var DEFAULT_GRACE_PERIOD_DAYS = 7;
|
|
295
|
+
|
|
296
|
+
// src/binary-lifecycle/backend.ts
|
|
153
297
|
import fs from "fs";
|
|
298
|
+
import fsp from "fs/promises";
|
|
299
|
+
import path from "path";
|
|
300
|
+
var FilesystemBackend = class {
|
|
301
|
+
type = "filesystem";
|
|
302
|
+
basePath;
|
|
303
|
+
constructor(basePath) {
|
|
304
|
+
if (!basePath || basePath.trim().length === 0) {
|
|
305
|
+
throw new Error("FilesystemBackend requires a non-empty basePath");
|
|
306
|
+
}
|
|
307
|
+
this.basePath = basePath;
|
|
308
|
+
}
|
|
309
|
+
async upload(localPath, remotePath) {
|
|
310
|
+
const dest = path.join(this.basePath, remotePath);
|
|
311
|
+
const destDir = path.dirname(dest);
|
|
312
|
+
await fsp.mkdir(destDir, { recursive: true });
|
|
313
|
+
await fsp.copyFile(localPath, dest);
|
|
314
|
+
return dest;
|
|
315
|
+
}
|
|
316
|
+
async exists(remotePath) {
|
|
317
|
+
const dest = path.join(this.basePath, remotePath);
|
|
318
|
+
try {
|
|
319
|
+
await fsp.access(dest, fs.constants.F_OK);
|
|
320
|
+
return true;
|
|
321
|
+
} catch {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async delete(remotePath) {
|
|
326
|
+
const dest = path.join(this.basePath, remotePath);
|
|
327
|
+
try {
|
|
328
|
+
await fsp.unlink(dest);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
if (err.code !== "ENOENT") throw err;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var NoneBackend = class {
|
|
335
|
+
type = "none";
|
|
336
|
+
async upload(_localPath, remotePath) {
|
|
337
|
+
return remotePath;
|
|
338
|
+
}
|
|
339
|
+
async exists(_remotePath) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
async delete(_remotePath) {
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
function createBackend(cfg) {
|
|
346
|
+
switch (cfg.type) {
|
|
347
|
+
case "filesystem": {
|
|
348
|
+
if (!cfg.basePath) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
'BinaryStorageBackendConfig.basePath is required when type is "filesystem"'
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return new FilesystemBackend(cfg.basePath);
|
|
354
|
+
}
|
|
355
|
+
case "s3":
|
|
356
|
+
throw new Error("S3 binary storage backend is not yet implemented");
|
|
357
|
+
case "none":
|
|
358
|
+
return new NoneBackend();
|
|
359
|
+
default:
|
|
360
|
+
throw new Error(`Unknown binary storage backend type: ${String(cfg.type)}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/binary-lifecycle/scanner.ts
|
|
365
|
+
import fsp2 from "fs/promises";
|
|
154
366
|
import path2 from "path";
|
|
367
|
+
function matchesPatterns(filename, patterns) {
|
|
368
|
+
const lower = filename.toLowerCase();
|
|
369
|
+
for (const pattern of patterns) {
|
|
370
|
+
if (pattern.startsWith("*.")) {
|
|
371
|
+
const ext = pattern.slice(1).toLowerCase();
|
|
372
|
+
if (lower.endsWith(ext)) return true;
|
|
373
|
+
} else if (lower === pattern.toLowerCase()) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
async function scanForBinaries(memoryDir, config, manifest) {
|
|
380
|
+
const tracked = new Set(manifest.assets.map((a) => a.originalPath));
|
|
381
|
+
const results = [];
|
|
382
|
+
async function walk(dir) {
|
|
383
|
+
let entries;
|
|
384
|
+
try {
|
|
385
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
386
|
+
} catch {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const fullPath = path2.join(dir, entry.name);
|
|
391
|
+
const relativePath = path2.relative(memoryDir, fullPath).split(path2.sep).join("/");
|
|
392
|
+
if (entry.isDirectory()) {
|
|
393
|
+
if (entry.name === ".binary-lifecycle") continue;
|
|
394
|
+
await walk(fullPath);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (!entry.isFile()) continue;
|
|
398
|
+
if (!matchesPatterns(entry.name, config.scanPatterns)) continue;
|
|
399
|
+
if (tracked.has(relativePath)) continue;
|
|
400
|
+
try {
|
|
401
|
+
const stat = await fsp2.stat(fullPath);
|
|
402
|
+
if (stat.size > config.maxBinarySizeBytes) continue;
|
|
403
|
+
if (stat.size === 0) continue;
|
|
404
|
+
} catch {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
results.push(relativePath);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
await walk(memoryDir);
|
|
411
|
+
return results;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/binary-lifecycle/manifest.ts
|
|
415
|
+
import fsp3 from "fs/promises";
|
|
416
|
+
import path3 from "path";
|
|
417
|
+
import crypto from "crypto";
|
|
418
|
+
var MANIFEST_DIR = ".binary-lifecycle";
|
|
419
|
+
var MANIFEST_FILE = "manifest.json";
|
|
420
|
+
function manifestDir(memoryDir) {
|
|
421
|
+
return path3.join(memoryDir, MANIFEST_DIR);
|
|
422
|
+
}
|
|
423
|
+
function manifestPath(memoryDir) {
|
|
424
|
+
return path3.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);
|
|
425
|
+
}
|
|
426
|
+
async function readManifest(memoryDir) {
|
|
427
|
+
const filePath = manifestPath(memoryDir);
|
|
428
|
+
try {
|
|
429
|
+
const raw = await fsp3.readFile(filePath, "utf-8");
|
|
430
|
+
const parsed = JSON.parse(raw);
|
|
431
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
432
|
+
return emptyManifest();
|
|
433
|
+
}
|
|
434
|
+
const obj = parsed;
|
|
435
|
+
if (obj.version !== 1 || !Array.isArray(obj.assets)) {
|
|
436
|
+
return emptyManifest();
|
|
437
|
+
}
|
|
438
|
+
return parsed;
|
|
439
|
+
} catch {
|
|
440
|
+
return emptyManifest();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async function writeManifest(memoryDir, manifest) {
|
|
444
|
+
const dir = manifestDir(memoryDir);
|
|
445
|
+
await fsp3.mkdir(dir, { recursive: true });
|
|
446
|
+
const dest = manifestPath(memoryDir);
|
|
447
|
+
const tmpSuffix = crypto.randomBytes(8).toString("hex");
|
|
448
|
+
const tmpPath = `${dest}.${tmpSuffix}.tmp`;
|
|
449
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
450
|
+
await fsp3.writeFile(tmpPath, content, "utf-8");
|
|
451
|
+
try {
|
|
452
|
+
await fsp3.rename(tmpPath, dest);
|
|
453
|
+
} catch (renameErr) {
|
|
454
|
+
try {
|
|
455
|
+
await fsp3.unlink(tmpPath);
|
|
456
|
+
} catch {
|
|
457
|
+
}
|
|
458
|
+
throw renameErr;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function emptyManifest() {
|
|
462
|
+
return { version: 1, assets: [] };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/binary-lifecycle/pipeline.ts
|
|
466
|
+
import fsp4 from "fs/promises";
|
|
467
|
+
import path4 from "path";
|
|
468
|
+
import crypto2 from "crypto";
|
|
469
|
+
async function hashFile(filePath) {
|
|
470
|
+
const content = await fsp4.readFile(filePath);
|
|
471
|
+
return crypto2.createHash("sha256").update(content).digest("hex");
|
|
472
|
+
}
|
|
473
|
+
function guessMimeType(ext) {
|
|
474
|
+
const map = {
|
|
475
|
+
".png": "image/png",
|
|
476
|
+
".jpg": "image/jpeg",
|
|
477
|
+
".jpeg": "image/jpeg",
|
|
478
|
+
".gif": "image/gif",
|
|
479
|
+
".pdf": "application/pdf",
|
|
480
|
+
".mp3": "audio/mpeg",
|
|
481
|
+
".mp4": "video/mp4",
|
|
482
|
+
".wav": "audio/wav"
|
|
483
|
+
};
|
|
484
|
+
return map[ext.toLowerCase()] ?? "application/octet-stream";
|
|
485
|
+
}
|
|
486
|
+
function escapeRegex(s) {
|
|
487
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
488
|
+
}
|
|
489
|
+
async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
|
|
490
|
+
let mirrored = 0;
|
|
491
|
+
const errors = [];
|
|
492
|
+
for (const relPath of newPaths) {
|
|
493
|
+
const fullPath = path4.join(memoryDir, relPath);
|
|
494
|
+
try {
|
|
495
|
+
const stat = await fsp4.stat(fullPath);
|
|
496
|
+
const contentHash = await hashFile(fullPath);
|
|
497
|
+
const ext = path4.extname(relPath);
|
|
498
|
+
const mimeType = guessMimeType(ext);
|
|
499
|
+
const remotePath = relPath;
|
|
500
|
+
let backendLocation = remotePath;
|
|
501
|
+
if (!dryRun) {
|
|
502
|
+
backendLocation = await backend.upload(fullPath, remotePath);
|
|
503
|
+
}
|
|
504
|
+
const record = {
|
|
505
|
+
originalPath: relPath,
|
|
506
|
+
mirroredPath: backendLocation,
|
|
507
|
+
contentHash,
|
|
508
|
+
sizeBytes: stat.size,
|
|
509
|
+
mimeType,
|
|
510
|
+
mirroredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
511
|
+
status: "mirrored"
|
|
512
|
+
};
|
|
513
|
+
assets.push(record);
|
|
514
|
+
mirrored++;
|
|
515
|
+
log2.info(`[binary-lifecycle] mirrored: ${relPath} (${stat.size} bytes)${dryRun ? " [dry-run]" : ""}`);
|
|
516
|
+
} catch (err) {
|
|
517
|
+
const msg = `mirror failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
518
|
+
log2.error(`[binary-lifecycle] ${msg}`);
|
|
519
|
+
errors.push(msg);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return { mirrored, errors };
|
|
523
|
+
}
|
|
524
|
+
async function stageRedirect(memoryDir, assets, log2, dryRun) {
|
|
525
|
+
let redirected = 0;
|
|
526
|
+
const errors = [];
|
|
527
|
+
const candidates = assets.filter((a) => a.status === "mirrored");
|
|
528
|
+
if (candidates.length === 0) return { redirected, errors };
|
|
529
|
+
const mdFiles = await findMarkdownFiles(memoryDir);
|
|
530
|
+
for (const asset of candidates) {
|
|
531
|
+
let matchCount = 0;
|
|
532
|
+
let writeFailCount = 0;
|
|
533
|
+
for (const mdPath of mdFiles) {
|
|
534
|
+
try {
|
|
535
|
+
const content = await fsp4.readFile(mdPath, "utf-8");
|
|
536
|
+
const mdDir = path4.dirname(mdPath);
|
|
537
|
+
const assetAbsolute = path4.join(memoryDir, asset.originalPath);
|
|
538
|
+
const relativeToMd = path4.relative(mdDir, assetAbsolute);
|
|
539
|
+
const relativeForward = relativeToMd.split(path4.sep).join("/");
|
|
540
|
+
const escaped = escapeRegex(relativeForward);
|
|
541
|
+
const pattern = new RegExp(
|
|
542
|
+
`(!?\\[[^\\]]*\\]\\()(\\.\\/)?(${escaped})(\\))`,
|
|
543
|
+
"g"
|
|
544
|
+
);
|
|
545
|
+
if (!pattern.test(content)) continue;
|
|
546
|
+
matchCount++;
|
|
547
|
+
if (!dryRun) {
|
|
548
|
+
pattern.lastIndex = 0;
|
|
549
|
+
const updated = content.replace(pattern, (_match, open, _dotSlash, _file, close) => {
|
|
550
|
+
return `${open}${asset.mirroredPath}${close}`;
|
|
551
|
+
});
|
|
552
|
+
await fsp4.writeFile(mdPath, updated, "utf-8");
|
|
553
|
+
}
|
|
554
|
+
} catch (err) {
|
|
555
|
+
writeFailCount++;
|
|
556
|
+
const msg = `redirect scan failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
557
|
+
log2.error(`[binary-lifecycle] ${msg}`);
|
|
558
|
+
errors.push(msg);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (matchCount > 0 && writeFailCount === 0) {
|
|
562
|
+
asset.status = "redirected";
|
|
563
|
+
asset.redirectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
564
|
+
redirected++;
|
|
565
|
+
log2.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
|
|
566
|
+
} else if (matchCount > 0 && writeFailCount > 0) {
|
|
567
|
+
asset.status = "error";
|
|
568
|
+
log2.warn(
|
|
569
|
+
`[binary-lifecycle] redirect partial failure for ${asset.originalPath}: ${matchCount} match(es), ${writeFailCount} write failure(s) \u2014 status set to error`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return { redirected, errors };
|
|
574
|
+
}
|
|
575
|
+
async function stageClean(memoryDir, assets, gracePeriodDays, log2, dryRun, forceClean) {
|
|
576
|
+
let cleaned = 0;
|
|
577
|
+
const errors = [];
|
|
578
|
+
const now = Date.now();
|
|
579
|
+
const graceMs = gracePeriodDays * 24 * 60 * 60 * 1e3;
|
|
580
|
+
const candidates = assets.filter(
|
|
581
|
+
(a) => a.status === "redirected"
|
|
582
|
+
);
|
|
583
|
+
for (const asset of candidates) {
|
|
584
|
+
const mirroredMs = new Date(asset.mirroredAt).getTime();
|
|
585
|
+
const ageMs = now - mirroredMs;
|
|
586
|
+
if (!forceClean && ageMs < graceMs) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const fullPath = path4.join(memoryDir, asset.originalPath);
|
|
590
|
+
try {
|
|
591
|
+
if (!dryRun) {
|
|
592
|
+
await fsp4.unlink(fullPath);
|
|
593
|
+
}
|
|
594
|
+
asset.status = "cleaned";
|
|
595
|
+
asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
596
|
+
cleaned++;
|
|
597
|
+
log2.info(`[binary-lifecycle] cleaned: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
if (err.code === "ENOENT") {
|
|
600
|
+
asset.status = "cleaned";
|
|
601
|
+
asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
602
|
+
cleaned++;
|
|
603
|
+
} else {
|
|
604
|
+
const msg = `clean failed for ${asset.originalPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
605
|
+
log2.error(`[binary-lifecycle] ${msg}`);
|
|
606
|
+
errors.push(msg);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return { cleaned, errors };
|
|
611
|
+
}
|
|
612
|
+
async function findMarkdownFiles(dir) {
|
|
613
|
+
const results = [];
|
|
614
|
+
async function walk(current) {
|
|
615
|
+
let entries;
|
|
616
|
+
try {
|
|
617
|
+
entries = await fsp4.readdir(current, { withFileTypes: true });
|
|
618
|
+
} catch {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
for (const entry of entries) {
|
|
622
|
+
const full = path4.join(current, entry.name);
|
|
623
|
+
if (entry.isDirectory()) {
|
|
624
|
+
if (entry.name === ".binary-lifecycle") continue;
|
|
625
|
+
await walk(full);
|
|
626
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
627
|
+
results.push(full);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
await walk(dir);
|
|
632
|
+
return results;
|
|
633
|
+
}
|
|
634
|
+
async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts) {
|
|
635
|
+
const dryRun = opts?.dryRun ?? false;
|
|
636
|
+
const forceClean = opts?.forceClean ?? false;
|
|
637
|
+
const manifest = await readManifest(memoryDir);
|
|
638
|
+
const newPaths = await scanForBinaries(memoryDir, config, manifest);
|
|
639
|
+
const scanned = newPaths.length;
|
|
640
|
+
const mirrorResult = await stageMirror(
|
|
641
|
+
memoryDir,
|
|
642
|
+
newPaths,
|
|
643
|
+
backend,
|
|
644
|
+
manifest.assets,
|
|
645
|
+
log2,
|
|
646
|
+
dryRun
|
|
647
|
+
);
|
|
648
|
+
const redirectResult = await stageRedirect(memoryDir, manifest.assets, log2, dryRun);
|
|
649
|
+
const cleanResult = await stageClean(
|
|
650
|
+
memoryDir,
|
|
651
|
+
manifest.assets,
|
|
652
|
+
config.gracePeriodDays,
|
|
653
|
+
log2,
|
|
654
|
+
dryRun,
|
|
655
|
+
forceClean
|
|
656
|
+
);
|
|
657
|
+
manifest.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
658
|
+
if (!dryRun) {
|
|
659
|
+
await writeManifest(memoryDir, manifest);
|
|
660
|
+
}
|
|
661
|
+
const allErrors = [
|
|
662
|
+
...mirrorResult.errors,
|
|
663
|
+
...redirectResult.errors,
|
|
664
|
+
...cleanResult.errors
|
|
665
|
+
];
|
|
666
|
+
return {
|
|
667
|
+
scanned,
|
|
668
|
+
mirrored: mirrorResult.mirrored,
|
|
669
|
+
redirected: redirectResult.redirected,
|
|
670
|
+
cleaned: cleanResult.cleaned,
|
|
671
|
+
errors: allErrors,
|
|
672
|
+
dryRun
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/projection/index.ts
|
|
677
|
+
import fs2 from "fs";
|
|
678
|
+
import path6 from "path";
|
|
155
679
|
|
|
156
680
|
// src/utils/category-dir.ts
|
|
157
|
-
import
|
|
681
|
+
import path5 from "path";
|
|
158
682
|
var CATEGORY_DIR_MAP = {
|
|
159
683
|
correction: "corrections",
|
|
160
684
|
question: "questions",
|
|
@@ -165,7 +689,8 @@ var CATEGORY_DIR_MAP = {
|
|
|
165
689
|
principle: "principles",
|
|
166
690
|
rule: "rules",
|
|
167
691
|
skill: "skills",
|
|
168
|
-
relationship: "relationships"
|
|
692
|
+
relationship: "relationships",
|
|
693
|
+
procedure: "procedures"
|
|
169
694
|
};
|
|
170
695
|
var ALL_CATEGORY_DIRS = [
|
|
171
696
|
"facts",
|
|
@@ -177,7 +702,7 @@ var ALL_CATEGORY_KEYS = [
|
|
|
177
702
|
];
|
|
178
703
|
function getCategoryDir(memoryDir, category) {
|
|
179
704
|
const dir = CATEGORY_DIR_MAP[category];
|
|
180
|
-
return dir ?
|
|
705
|
+
return dir ? path5.join(memoryDir, dir) : path5.join(memoryDir, "facts");
|
|
181
706
|
}
|
|
182
707
|
|
|
183
708
|
// src/projection/index.ts
|
|
@@ -194,11 +719,11 @@ async function generateContextTree(options) {
|
|
|
194
719
|
let nodesGenerated = 0;
|
|
195
720
|
let nodesSkipped = 0;
|
|
196
721
|
const categoryCounts = {};
|
|
197
|
-
|
|
722
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
198
723
|
const allCategories = filterCategories ?? ALL_CATEGORY_KEYS.filter((c) => c !== "question");
|
|
199
724
|
for (const category of allCategories) {
|
|
200
725
|
const categoryDir = getCategoryDir(memoryDir, category);
|
|
201
|
-
if (!
|
|
726
|
+
if (!fs2.existsSync(categoryDir)) continue;
|
|
202
727
|
categoryCounts[category] = 0;
|
|
203
728
|
const files = walkR(categoryDir);
|
|
204
729
|
let count = 0;
|
|
@@ -207,7 +732,7 @@ async function generateContextTree(options) {
|
|
|
207
732
|
nodesSkipped++;
|
|
208
733
|
continue;
|
|
209
734
|
}
|
|
210
|
-
const content =
|
|
735
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
211
736
|
const fm = parseFrontmatter(content);
|
|
212
737
|
if (!fm) {
|
|
213
738
|
nodesSkipped++;
|
|
@@ -218,17 +743,17 @@ async function generateContextTree(options) {
|
|
|
218
743
|
nodesSkipped++;
|
|
219
744
|
continue;
|
|
220
745
|
}
|
|
221
|
-
const outputPath =
|
|
222
|
-
|
|
223
|
-
|
|
746
|
+
const outputPath = path6.join(outputDir, node.path);
|
|
747
|
+
fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
|
|
748
|
+
fs2.writeFileSync(outputPath, node.content);
|
|
224
749
|
nodesGenerated++;
|
|
225
750
|
categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;
|
|
226
751
|
count++;
|
|
227
752
|
}
|
|
228
753
|
}
|
|
229
754
|
if (includeEntities) {
|
|
230
|
-
const entitiesDir =
|
|
231
|
-
if (
|
|
755
|
+
const entitiesDir = path6.join(memoryDir, "entities");
|
|
756
|
+
if (fs2.existsSync(entitiesDir)) {
|
|
232
757
|
categoryCounts["entity"] = 0;
|
|
233
758
|
const entityFiles = walkR(entitiesDir);
|
|
234
759
|
let count = 0;
|
|
@@ -237,12 +762,12 @@ async function generateContextTree(options) {
|
|
|
237
762
|
nodesSkipped++;
|
|
238
763
|
continue;
|
|
239
764
|
}
|
|
240
|
-
const content =
|
|
241
|
-
const fileName =
|
|
765
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
766
|
+
const fileName = path6.basename(filePath, ".md");
|
|
242
767
|
const node = projectEntityNode(fileName, content);
|
|
243
|
-
const outputPath =
|
|
244
|
-
|
|
245
|
-
|
|
768
|
+
const outputPath = path6.join(outputDir, "entities", `${fileName}.md`);
|
|
769
|
+
fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
|
|
770
|
+
fs2.writeFileSync(outputPath, node.content);
|
|
246
771
|
nodesGenerated++;
|
|
247
772
|
categoryCounts["entity"] = (categoryCounts["entity"] ?? 0) + 1;
|
|
248
773
|
count++;
|
|
@@ -250,8 +775,8 @@ async function generateContextTree(options) {
|
|
|
250
775
|
}
|
|
251
776
|
}
|
|
252
777
|
if (includeQuestions) {
|
|
253
|
-
const questionsDir =
|
|
254
|
-
if (
|
|
778
|
+
const questionsDir = path6.join(memoryDir, "questions");
|
|
779
|
+
if (fs2.existsSync(questionsDir)) {
|
|
255
780
|
categoryCounts["question"] = 0;
|
|
256
781
|
const qFiles = walkR(questionsDir);
|
|
257
782
|
let count = 0;
|
|
@@ -260,7 +785,7 @@ async function generateContextTree(options) {
|
|
|
260
785
|
nodesSkipped++;
|
|
261
786
|
continue;
|
|
262
787
|
}
|
|
263
|
-
const content =
|
|
788
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
264
789
|
const fm = parseFrontmatter(content);
|
|
265
790
|
if (!fm) {
|
|
266
791
|
nodesSkipped++;
|
|
@@ -271,9 +796,9 @@ async function generateContextTree(options) {
|
|
|
271
796
|
nodesSkipped++;
|
|
272
797
|
continue;
|
|
273
798
|
}
|
|
274
|
-
const outputPath =
|
|
275
|
-
|
|
276
|
-
|
|
799
|
+
const outputPath = path6.join(outputDir, node.path);
|
|
800
|
+
fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
|
|
801
|
+
fs2.writeFileSync(outputPath, node.content);
|
|
277
802
|
nodesGenerated++;
|
|
278
803
|
categoryCounts["question"] = (categoryCounts["question"] ?? 0) + 1;
|
|
279
804
|
count++;
|
|
@@ -281,7 +806,7 @@ async function generateContextTree(options) {
|
|
|
281
806
|
}
|
|
282
807
|
}
|
|
283
808
|
const index = generateIndex(categoryCounts, outputDir);
|
|
284
|
-
|
|
809
|
+
fs2.writeFileSync(path6.join(outputDir, "INDEX.md"), index);
|
|
285
810
|
return {
|
|
286
811
|
nodesGenerated,
|
|
287
812
|
nodesSkipped,
|
|
@@ -293,8 +818,8 @@ async function generateContextTree(options) {
|
|
|
293
818
|
function walkR(dir) {
|
|
294
819
|
const results = [];
|
|
295
820
|
function walk(directory) {
|
|
296
|
-
for (const entry of
|
|
297
|
-
const fullPath =
|
|
821
|
+
for (const entry of fs2.readdirSync(directory, { withFileTypes: true })) {
|
|
822
|
+
const fullPath = path6.join(directory, entry.name);
|
|
298
823
|
if (entry.isDirectory()) {
|
|
299
824
|
walk(fullPath);
|
|
300
825
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -336,13 +861,13 @@ function extractBody(content) {
|
|
|
336
861
|
}
|
|
337
862
|
function projectNode(filePath, category, fm, rawContent) {
|
|
338
863
|
const body = extractBody(rawContent);
|
|
339
|
-
const fileName =
|
|
340
|
-
const dateDir =
|
|
864
|
+
const fileName = path6.basename(filePath, ".md");
|
|
865
|
+
const dateDir = path6.basename(path6.dirname(filePath));
|
|
341
866
|
let relPath;
|
|
342
867
|
if (/^\d{4}-\d{2}-\d{2}$/.test(dateDir)) {
|
|
343
|
-
relPath =
|
|
868
|
+
relPath = path6.join(category, dateDir, `${fileName}.md`);
|
|
344
869
|
} else {
|
|
345
|
-
relPath =
|
|
870
|
+
relPath = path6.join(category, `${fileName}.md`);
|
|
346
871
|
}
|
|
347
872
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
348
873
|
const md = `# ${fm.id}
|
|
@@ -388,7 +913,7 @@ function projectEntityNode(fileName, content) {
|
|
|
388
913
|
${content}
|
|
389
914
|
`;
|
|
390
915
|
return {
|
|
391
|
-
path:
|
|
916
|
+
path: path6.join("entities", `${fileName}.md`),
|
|
392
917
|
category: "entity",
|
|
393
918
|
title: fileName,
|
|
394
919
|
content: md,
|
|
@@ -440,8 +965,8 @@ function generateIndex(categoryCounts, outputDir) {
|
|
|
440
965
|
}
|
|
441
966
|
|
|
442
967
|
// src/onboarding/index.ts
|
|
443
|
-
import
|
|
444
|
-
import
|
|
968
|
+
import fs3 from "fs";
|
|
969
|
+
import path7 from "path";
|
|
445
970
|
var LANGUAGE_RULES = [
|
|
446
971
|
{
|
|
447
972
|
language: "TypeScript",
|
|
@@ -567,13 +1092,13 @@ function walkDir(root, exclude, maxDepth) {
|
|
|
567
1092
|
if (depth > maxDepth) return;
|
|
568
1093
|
let entries;
|
|
569
1094
|
try {
|
|
570
|
-
entries =
|
|
1095
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
571
1096
|
} catch {
|
|
572
1097
|
return;
|
|
573
1098
|
}
|
|
574
1099
|
for (const entry of entries) {
|
|
575
1100
|
if (exclude.has(entry.name)) continue;
|
|
576
|
-
const fullPath =
|
|
1101
|
+
const fullPath = path7.join(dir, entry.name);
|
|
577
1102
|
if (entry.isDirectory()) {
|
|
578
1103
|
walk(fullPath, depth + 1);
|
|
579
1104
|
} else if (entry.isFile()) {
|
|
@@ -588,11 +1113,11 @@ function detectLanguages(files, root) {
|
|
|
588
1113
|
const results = [];
|
|
589
1114
|
const extCounts = /* @__PURE__ */ new Map();
|
|
590
1115
|
for (const f of files) {
|
|
591
|
-
const ext =
|
|
1116
|
+
const ext = path7.extname(f).toLowerCase();
|
|
592
1117
|
if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
|
|
593
1118
|
}
|
|
594
1119
|
const rootFiles = new Set(
|
|
595
|
-
files.filter((f) =>
|
|
1120
|
+
files.filter((f) => path7.dirname(f) === root).map((f) => path7.basename(f))
|
|
596
1121
|
);
|
|
597
1122
|
for (const rule of LANGUAGE_RULES) {
|
|
598
1123
|
const evidence = [];
|
|
@@ -636,18 +1161,18 @@ function detectLanguages(files, root) {
|
|
|
636
1161
|
}
|
|
637
1162
|
function detectShape(files, root) {
|
|
638
1163
|
const rootFiles = new Set(
|
|
639
|
-
files.filter((f) =>
|
|
1164
|
+
files.filter((f) => path7.dirname(f) === root).map((f) => path7.basename(f))
|
|
640
1165
|
);
|
|
641
1166
|
const rootDirs = /* @__PURE__ */ new Set();
|
|
642
1167
|
try {
|
|
643
|
-
for (const entry of
|
|
1168
|
+
for (const entry of fs3.readdirSync(root, { withFileTypes: true })) {
|
|
644
1169
|
if (entry.isDirectory()) rootDirs.add(entry.name);
|
|
645
1170
|
}
|
|
646
1171
|
} catch {
|
|
647
1172
|
}
|
|
648
1173
|
const evidence = [];
|
|
649
1174
|
if (rootFiles.has("package.json")) {
|
|
650
|
-
const pkg = readJsonSafe(
|
|
1175
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
651
1176
|
if (pkg?.workspaces) {
|
|
652
1177
|
evidence.push("package.json has workspaces");
|
|
653
1178
|
return { shape: "monorepo", evidence };
|
|
@@ -661,13 +1186,13 @@ function detectShape(files, root) {
|
|
|
661
1186
|
evidence.push("workspace manifest found");
|
|
662
1187
|
return { shape: "workspace", evidence };
|
|
663
1188
|
}
|
|
664
|
-
const cargoToml = readTomlWorkspace(
|
|
1189
|
+
const cargoToml = readTomlWorkspace(path7.join(root, "Cargo.toml"));
|
|
665
1190
|
if (cargoToml) {
|
|
666
1191
|
evidence.push("Cargo.toml has workspace");
|
|
667
1192
|
return { shape: "workspace", evidence };
|
|
668
1193
|
}
|
|
669
1194
|
if (rootFiles.has("package.json")) {
|
|
670
|
-
const pkg = readJsonSafe(
|
|
1195
|
+
const pkg = readJsonSafe(path7.join(root, "package.json"));
|
|
671
1196
|
if (pkg?.exports || pkg?.main) {
|
|
672
1197
|
if (pkg?.bin) {
|
|
673
1198
|
evidence.push("package.json has bin");
|
|
@@ -701,8 +1226,8 @@ function discoverDocs(files, root) {
|
|
|
701
1226
|
{ pattern: /^\.editorconfig$/i, kind: "config" }
|
|
702
1227
|
];
|
|
703
1228
|
for (const filePath of files) {
|
|
704
|
-
const basename =
|
|
705
|
-
const relPath =
|
|
1229
|
+
const basename = path7.basename(filePath).toLowerCase();
|
|
1230
|
+
const relPath = path7.relative(root, filePath);
|
|
706
1231
|
let kind;
|
|
707
1232
|
for (const { pattern, kind: k } of docPatterns) {
|
|
708
1233
|
if (pattern.test(basename)) {
|
|
@@ -714,14 +1239,14 @@ function discoverDocs(files, root) {
|
|
|
714
1239
|
kind = "docs";
|
|
715
1240
|
}
|
|
716
1241
|
if (!kind && (basename.endsWith(".md") || basename.endsWith(".mdx"))) {
|
|
717
|
-
if (
|
|
1242
|
+
if (path7.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
|
|
718
1243
|
kind = "docs";
|
|
719
1244
|
}
|
|
720
1245
|
}
|
|
721
1246
|
if (kind) {
|
|
722
1247
|
let size = 0;
|
|
723
1248
|
try {
|
|
724
|
-
size =
|
|
1249
|
+
size = fs3.statSync(filePath).size;
|
|
725
1250
|
} catch {
|
|
726
1251
|
}
|
|
727
1252
|
docs.push({
|
|
@@ -735,7 +1260,7 @@ function discoverDocs(files, root) {
|
|
|
735
1260
|
return docs;
|
|
736
1261
|
}
|
|
737
1262
|
function isUnderDocsDir(relPath) {
|
|
738
|
-
const parts = relPath.split(
|
|
1263
|
+
const parts = relPath.split(path7.sep);
|
|
739
1264
|
return parts[0] === "docs" || parts[0] === "doc" || parts[0] === "documentation";
|
|
740
1265
|
}
|
|
741
1266
|
function buildPlan(languages, shape, docs, _root) {
|
|
@@ -763,14 +1288,14 @@ function buildPlan(languages, shape, docs, _root) {
|
|
|
763
1288
|
}
|
|
764
1289
|
function readJsonSafe(filePath) {
|
|
765
1290
|
try {
|
|
766
|
-
return JSON.parse(
|
|
1291
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf8"));
|
|
767
1292
|
} catch {
|
|
768
1293
|
return null;
|
|
769
1294
|
}
|
|
770
1295
|
}
|
|
771
1296
|
function readTomlWorkspace(filePath) {
|
|
772
1297
|
try {
|
|
773
|
-
const content =
|
|
1298
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
774
1299
|
return content.includes("[workspace]");
|
|
775
1300
|
} catch {
|
|
776
1301
|
return false;
|
|
@@ -778,9 +1303,9 @@ function readTomlWorkspace(filePath) {
|
|
|
778
1303
|
}
|
|
779
1304
|
|
|
780
1305
|
// src/curation/index.ts
|
|
781
|
-
import
|
|
782
|
-
import
|
|
783
|
-
import
|
|
1306
|
+
import fs4 from "fs";
|
|
1307
|
+
import path8 from "path";
|
|
1308
|
+
import crypto3 from "crypto";
|
|
784
1309
|
async function curate(options) {
|
|
785
1310
|
const startTime = Date.now();
|
|
786
1311
|
const {
|
|
@@ -865,18 +1390,18 @@ async function curate(options) {
|
|
|
865
1390
|
};
|
|
866
1391
|
}
|
|
867
1392
|
function resolveTargets(targetPath) {
|
|
868
|
-
const stat =
|
|
1393
|
+
const stat = fs4.statSync(targetPath);
|
|
869
1394
|
if (stat.isFile()) return [targetPath];
|
|
870
1395
|
const results = [];
|
|
871
1396
|
const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
|
|
872
1397
|
function walk(dir) {
|
|
873
|
-
for (const entry of
|
|
874
|
-
const fullPath =
|
|
1398
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
1399
|
+
const fullPath = path8.join(dir, entry.name);
|
|
875
1400
|
if (entry.isDirectory()) {
|
|
876
1401
|
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
877
1402
|
walk(fullPath);
|
|
878
1403
|
}
|
|
879
|
-
} else if (extensions.has(
|
|
1404
|
+
} else if (extensions.has(path8.extname(entry.name).toLowerCase())) {
|
|
880
1405
|
results.push(fullPath);
|
|
881
1406
|
}
|
|
882
1407
|
}
|
|
@@ -885,7 +1410,7 @@ function resolveTargets(targetPath) {
|
|
|
885
1410
|
return results;
|
|
886
1411
|
}
|
|
887
1412
|
function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
|
|
888
|
-
const relativePath =
|
|
1413
|
+
const relativePath = path8.relative(projectRoot, filePath);
|
|
889
1414
|
const statements = [];
|
|
890
1415
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
891
1416
|
const paragraphs = content.split(/\n{2,}/).map((p) => p.trim()).filter((p) => p.length > 20 && p.length < 2e3);
|
|
@@ -976,11 +1501,11 @@ function findContradiction(stmt, existing) {
|
|
|
976
1501
|
}
|
|
977
1502
|
function loadExistingMemories(memoryDir) {
|
|
978
1503
|
const result = /* @__PURE__ */ new Map();
|
|
979
|
-
if (!
|
|
1504
|
+
if (!fs4.existsSync(memoryDir)) return result;
|
|
980
1505
|
const dirs = ALL_CATEGORY_DIRS;
|
|
981
1506
|
for (const dir of dirs) {
|
|
982
|
-
const fullDir =
|
|
983
|
-
if (!
|
|
1507
|
+
const fullDir = path8.join(memoryDir, dir);
|
|
1508
|
+
if (!fs4.existsSync(fullDir)) continue;
|
|
984
1509
|
walkFiles(fullDir, (filePath) => {
|
|
985
1510
|
const content = readFileSafe(filePath);
|
|
986
1511
|
if (!content) return;
|
|
@@ -1001,10 +1526,10 @@ function writeStatement(stmt, memoryDir) {
|
|
|
1001
1526
|
const now = /* @__PURE__ */ new Date();
|
|
1002
1527
|
const dateDir = now.toISOString().split("T")[0];
|
|
1003
1528
|
const categoryDir = getCategoryDir(memoryDir, stmt.category);
|
|
1004
|
-
const dir =
|
|
1005
|
-
|
|
1529
|
+
const dir = path8.join(categoryDir, dateDir);
|
|
1530
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1006
1531
|
const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
|
|
1007
|
-
const filePath =
|
|
1532
|
+
const filePath = path8.join(dir, fileName);
|
|
1008
1533
|
const frontmatter = [
|
|
1009
1534
|
"---",
|
|
1010
1535
|
`id: ${stmt.id}`,
|
|
@@ -1025,17 +1550,17 @@ function writeStatement(stmt, memoryDir) {
|
|
|
1025
1550
|
${stmt.content}
|
|
1026
1551
|
`;
|
|
1027
1552
|
try {
|
|
1028
|
-
|
|
1553
|
+
fs4.writeFileSync(filePath, body);
|
|
1029
1554
|
return filePath;
|
|
1030
1555
|
} catch {
|
|
1031
1556
|
return null;
|
|
1032
1557
|
}
|
|
1033
1558
|
}
|
|
1034
1559
|
function generateId() {
|
|
1035
|
-
return
|
|
1560
|
+
return crypto3.randomUUID();
|
|
1036
1561
|
}
|
|
1037
1562
|
function hashContent(content) {
|
|
1038
|
-
return
|
|
1563
|
+
return crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1039
1564
|
}
|
|
1040
1565
|
function tierFromConfidence(confidence) {
|
|
1041
1566
|
if (confidence >= 0.95) return "explicit";
|
|
@@ -1045,7 +1570,7 @@ function tierFromConfidence(confidence) {
|
|
|
1045
1570
|
}
|
|
1046
1571
|
function readFileSafe(filePath) {
|
|
1047
1572
|
try {
|
|
1048
|
-
return
|
|
1573
|
+
return fs4.readFileSync(filePath, "utf8");
|
|
1049
1574
|
} catch {
|
|
1050
1575
|
return null;
|
|
1051
1576
|
}
|
|
@@ -1074,8 +1599,8 @@ function extractBody2(content) {
|
|
|
1074
1599
|
return match ? match[1].trim() : content.trim();
|
|
1075
1600
|
}
|
|
1076
1601
|
function walkFiles(dir, callback) {
|
|
1077
|
-
for (const entry of
|
|
1078
|
-
const fullPath =
|
|
1602
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
1603
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1079
1604
|
if (entry.isDirectory()) {
|
|
1080
1605
|
walkFiles(fullPath, callback);
|
|
1081
1606
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -1085,9 +1610,9 @@ function walkFiles(dir, callback) {
|
|
|
1085
1610
|
}
|
|
1086
1611
|
|
|
1087
1612
|
// src/dedup/index.ts
|
|
1088
|
-
import
|
|
1089
|
-
import
|
|
1090
|
-
import
|
|
1613
|
+
import fs5 from "fs";
|
|
1614
|
+
import path9 from "path";
|
|
1615
|
+
import crypto4 from "crypto";
|
|
1091
1616
|
function findDuplicates(options) {
|
|
1092
1617
|
const startTime = Date.now();
|
|
1093
1618
|
const { memoryDir, threshold = 0.85, maxLoad = 1e4 } = options;
|
|
@@ -1214,8 +1739,8 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
|
|
|
1214
1739
|
const allCategories = categories ?? ALL_CATEGORY_DIRS;
|
|
1215
1740
|
for (const category of allCategories) {
|
|
1216
1741
|
if (result.length >= maxLoad) break;
|
|
1217
|
-
const dir =
|
|
1218
|
-
if (!
|
|
1742
|
+
const dir = path9.join(memoryDir, category);
|
|
1743
|
+
if (!fs5.existsSync(dir)) continue;
|
|
1219
1744
|
walkMdFiles(dir, (filePath) => {
|
|
1220
1745
|
if (result.length >= maxLoad) return;
|
|
1221
1746
|
const content = readFileSafe2(filePath);
|
|
@@ -1234,11 +1759,11 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
|
|
|
1234
1759
|
return result;
|
|
1235
1760
|
}
|
|
1236
1761
|
function hashContent2(content) {
|
|
1237
|
-
return
|
|
1762
|
+
return crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1238
1763
|
}
|
|
1239
1764
|
function readFileSafe2(filePath) {
|
|
1240
1765
|
try {
|
|
1241
|
-
return
|
|
1766
|
+
return fs5.readFileSync(filePath, "utf8");
|
|
1242
1767
|
} catch {
|
|
1243
1768
|
return null;
|
|
1244
1769
|
}
|
|
@@ -1261,8 +1786,8 @@ function extractBody3(content) {
|
|
|
1261
1786
|
return match ? match[1].trim() : content.trim();
|
|
1262
1787
|
}
|
|
1263
1788
|
function walkMdFiles(dir, callback) {
|
|
1264
|
-
for (const entry of
|
|
1265
|
-
const fullPath =
|
|
1789
|
+
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
1790
|
+
const fullPath = path9.join(dir, entry.name);
|
|
1266
1791
|
if (entry.isDirectory()) {
|
|
1267
1792
|
walkMdFiles(fullPath, callback);
|
|
1268
1793
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -1272,8 +1797,8 @@ function walkMdFiles(dir, callback) {
|
|
|
1272
1797
|
}
|
|
1273
1798
|
|
|
1274
1799
|
// src/review/index.ts
|
|
1275
|
-
import
|
|
1276
|
-
import
|
|
1800
|
+
import fs6 from "fs";
|
|
1801
|
+
import path10 from "path";
|
|
1277
1802
|
function listReviewItems(options) {
|
|
1278
1803
|
const startTime = Date.now();
|
|
1279
1804
|
const {
|
|
@@ -1283,8 +1808,8 @@ function listReviewItems(options) {
|
|
|
1283
1808
|
confidenceThreshold = 0.7
|
|
1284
1809
|
} = options;
|
|
1285
1810
|
const items = [];
|
|
1286
|
-
const suggestionsDir =
|
|
1287
|
-
if (
|
|
1811
|
+
const suggestionsDir = path10.join(memoryDir, "suggestions");
|
|
1812
|
+
if (fs6.existsSync(suggestionsDir)) {
|
|
1288
1813
|
walkMd(suggestionsDir, (filePath, content) => {
|
|
1289
1814
|
if (items.length >= limit) return;
|
|
1290
1815
|
const fm = parseFrontmatter4(content);
|
|
@@ -1303,8 +1828,8 @@ function listReviewItems(options) {
|
|
|
1303
1828
|
});
|
|
1304
1829
|
});
|
|
1305
1830
|
}
|
|
1306
|
-
const reviewDir =
|
|
1307
|
-
if (
|
|
1831
|
+
const reviewDir = path10.join(memoryDir, "review");
|
|
1832
|
+
if (fs6.existsSync(reviewDir)) {
|
|
1308
1833
|
walkMd(reviewDir, (filePath, content) => {
|
|
1309
1834
|
if (items.length >= limit) return;
|
|
1310
1835
|
const fm = parseFrontmatter4(content);
|
|
@@ -1327,8 +1852,8 @@ function listReviewItems(options) {
|
|
|
1327
1852
|
const categories = ALL_CATEGORY_DIRS;
|
|
1328
1853
|
for (const category of categories) {
|
|
1329
1854
|
if (items.length >= limit) break;
|
|
1330
|
-
const dir =
|
|
1331
|
-
if (!
|
|
1855
|
+
const dir = path10.join(memoryDir, category);
|
|
1856
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1332
1857
|
walkMd(dir, (filePath, content) => {
|
|
1333
1858
|
if (items.length >= limit) return;
|
|
1334
1859
|
const fm = parseFrontmatter4(content);
|
|
@@ -1370,22 +1895,22 @@ function performReview(memoryDir, itemId, action) {
|
|
|
1370
1895
|
function approveItem(memoryDir, itemId) {
|
|
1371
1896
|
const locations = ["suggestions", "review"];
|
|
1372
1897
|
for (const loc of locations) {
|
|
1373
|
-
const dir =
|
|
1374
|
-
if (!
|
|
1898
|
+
const dir = path10.join(memoryDir, loc);
|
|
1899
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1375
1900
|
const found = findFileById(dir, itemId);
|
|
1376
1901
|
if (!found) continue;
|
|
1377
|
-
const content =
|
|
1902
|
+
const content = fs6.readFileSync(found, "utf8");
|
|
1378
1903
|
const fm = parseFrontmatter4(content);
|
|
1379
1904
|
const body = extractBody4(content);
|
|
1380
1905
|
if (!fm) return { itemId, action: "approve", message: "Could not parse frontmatter" };
|
|
1381
1906
|
const category = fm.category ?? "fact";
|
|
1382
1907
|
const targetDir = getCategoryDir(memoryDir, category);
|
|
1383
1908
|
const dateDir = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1384
|
-
const outputPath =
|
|
1909
|
+
const outputPath = path10.join(targetDir, dateDir, path10.basename(found));
|
|
1385
1910
|
const updatedContent = content.replace(/confidence: [\d.]+/, "confidence: 0.9").replace(/confidenceTier: \w+/, "confidenceTier: high");
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1911
|
+
fs6.mkdirSync(path10.dirname(outputPath), { recursive: true });
|
|
1912
|
+
fs6.writeFileSync(outputPath, updatedContent);
|
|
1913
|
+
fs6.unlinkSync(found);
|
|
1389
1914
|
return {
|
|
1390
1915
|
itemId,
|
|
1391
1916
|
action: "approve",
|
|
@@ -1398,11 +1923,11 @@ function approveItem(memoryDir, itemId) {
|
|
|
1398
1923
|
function dismissItem(memoryDir, itemId) {
|
|
1399
1924
|
const locations = ["suggestions", "review"];
|
|
1400
1925
|
for (const loc of locations) {
|
|
1401
|
-
const dir =
|
|
1402
|
-
if (!
|
|
1926
|
+
const dir = path10.join(memoryDir, loc);
|
|
1927
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1403
1928
|
const found = findFileById(dir, itemId);
|
|
1404
1929
|
if (found) {
|
|
1405
|
-
|
|
1930
|
+
fs6.unlinkSync(found);
|
|
1406
1931
|
return { itemId, action: "dismiss", message: "Dismissed and removed" };
|
|
1407
1932
|
}
|
|
1408
1933
|
}
|
|
@@ -1411,11 +1936,11 @@ function dismissItem(memoryDir, itemId) {
|
|
|
1411
1936
|
function flagItem(memoryDir, itemId) {
|
|
1412
1937
|
const locations = ["suggestions", "review"];
|
|
1413
1938
|
for (const loc of locations) {
|
|
1414
|
-
const dir =
|
|
1415
|
-
if (!
|
|
1939
|
+
const dir = path10.join(memoryDir, loc);
|
|
1940
|
+
if (!fs6.existsSync(dir)) continue;
|
|
1416
1941
|
const found = findFileById(dir, itemId);
|
|
1417
1942
|
if (found) {
|
|
1418
|
-
const content =
|
|
1943
|
+
const content = fs6.readFileSync(found, "utf8");
|
|
1419
1944
|
const fixed = content.replace(
|
|
1420
1945
|
/^(---\n)/,
|
|
1421
1946
|
`---
|
|
@@ -1423,7 +1948,7 @@ flagged: true
|
|
|
1423
1948
|
flaggedAt: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1424
1949
|
`
|
|
1425
1950
|
);
|
|
1426
|
-
|
|
1951
|
+
fs6.writeFileSync(found, fixed);
|
|
1427
1952
|
return { itemId, action: "flag", message: "Flagged for further review" };
|
|
1428
1953
|
}
|
|
1429
1954
|
}
|
|
@@ -1449,7 +1974,7 @@ function parseConfidence(value, fallback) {
|
|
|
1449
1974
|
}
|
|
1450
1975
|
function readFileSafe3(filePath) {
|
|
1451
1976
|
try {
|
|
1452
|
-
return
|
|
1977
|
+
return fs6.readFileSync(filePath, "utf8");
|
|
1453
1978
|
} catch {
|
|
1454
1979
|
return null;
|
|
1455
1980
|
}
|
|
@@ -1472,8 +1997,8 @@ function extractBody4(content) {
|
|
|
1472
1997
|
return match ? match[1].trim() : content.trim();
|
|
1473
1998
|
}
|
|
1474
1999
|
function walkMd(dir, callback) {
|
|
1475
|
-
for (const entry of
|
|
1476
|
-
const fullPath =
|
|
2000
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
2001
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1477
2002
|
if (entry.isDirectory()) {
|
|
1478
2003
|
walkMd(fullPath, callback);
|
|
1479
2004
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -1484,8 +2009,8 @@ function walkMd(dir, callback) {
|
|
|
1484
2009
|
}
|
|
1485
2010
|
function walkMdPaths(dir) {
|
|
1486
2011
|
const results = [];
|
|
1487
|
-
for (const entry of
|
|
1488
|
-
const fullPath =
|
|
2012
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
2013
|
+
const fullPath = path10.join(dir, entry.name);
|
|
1489
2014
|
if (entry.isDirectory()) {
|
|
1490
2015
|
results.push(...walkMdPaths(fullPath));
|
|
1491
2016
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -1496,9 +2021,9 @@ function walkMdPaths(dir) {
|
|
|
1496
2021
|
}
|
|
1497
2022
|
|
|
1498
2023
|
// src/sync/index.ts
|
|
1499
|
-
import
|
|
1500
|
-
import
|
|
1501
|
-
import
|
|
2024
|
+
import fs7 from "fs";
|
|
2025
|
+
import path11 from "path";
|
|
2026
|
+
import crypto5 from "crypto";
|
|
1502
2027
|
var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
|
|
1503
2028
|
var DEFAULT_EXCLUDE2 = /* @__PURE__ */ new Set([
|
|
1504
2029
|
"node_modules",
|
|
@@ -1519,7 +2044,7 @@ function syncChanges(options) {
|
|
|
1519
2044
|
} = options;
|
|
1520
2045
|
const extSet = new Set(extensions);
|
|
1521
2046
|
const excludeSet = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDE2, ...excludeDirs]);
|
|
1522
|
-
const stateFilePath = options.stateFile ??
|
|
2047
|
+
const stateFilePath = options.stateFile ?? path11.join(memoryDir, ".sync-state.json");
|
|
1523
2048
|
const prevState = loadState(stateFilePath);
|
|
1524
2049
|
const currentFiles = scanFiles(sourceDir, extSet, excludeSet);
|
|
1525
2050
|
const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);
|
|
@@ -1535,8 +2060,8 @@ function syncChanges(options) {
|
|
|
1535
2060
|
for (const [relPath, hash] of Object.entries(currentFiles)) {
|
|
1536
2061
|
newState.fileHashes[relPath] = hash;
|
|
1537
2062
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
2063
|
+
fs7.mkdirSync(path11.dirname(stateFilePath), { recursive: true });
|
|
2064
|
+
fs7.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
|
|
1540
2065
|
}
|
|
1541
2066
|
return {
|
|
1542
2067
|
scanned: Object.keys(currentFiles).length,
|
|
@@ -1578,21 +2103,21 @@ function scanFiles(root, extensions, exclude) {
|
|
|
1578
2103
|
function walk(dir) {
|
|
1579
2104
|
let entries;
|
|
1580
2105
|
try {
|
|
1581
|
-
entries =
|
|
2106
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1582
2107
|
} catch {
|
|
1583
2108
|
return;
|
|
1584
2109
|
}
|
|
1585
2110
|
for (const entry of entries) {
|
|
1586
2111
|
if (exclude.has(entry.name)) continue;
|
|
1587
|
-
const fullPath =
|
|
2112
|
+
const fullPath = path11.join(dir, entry.name);
|
|
1588
2113
|
if (entry.isDirectory()) {
|
|
1589
2114
|
walk(fullPath);
|
|
1590
2115
|
} else if (entry.isFile()) {
|
|
1591
|
-
const ext =
|
|
2116
|
+
const ext = path11.extname(entry.name).toLowerCase();
|
|
1592
2117
|
if (!extensions.has(ext)) continue;
|
|
1593
|
-
const relPath =
|
|
2118
|
+
const relPath = path11.relative(root, fullPath);
|
|
1594
2119
|
try {
|
|
1595
|
-
const content =
|
|
2120
|
+
const content = fs7.readFileSync(fullPath, "utf8");
|
|
1596
2121
|
result[relPath] = hashContent3(content);
|
|
1597
2122
|
} catch {
|
|
1598
2123
|
}
|
|
@@ -1605,11 +2130,11 @@ function scanFiles(root, extensions, exclude) {
|
|
|
1605
2130
|
function computeDiff(current, previous, sourceDir) {
|
|
1606
2131
|
const changes = [];
|
|
1607
2132
|
for (const [relPath, hash] of Object.entries(current)) {
|
|
1608
|
-
const fullPath =
|
|
2133
|
+
const fullPath = path11.join(sourceDir, relPath);
|
|
1609
2134
|
if (!(relPath in previous)) {
|
|
1610
2135
|
let size = 0;
|
|
1611
2136
|
try {
|
|
1612
|
-
size =
|
|
2137
|
+
size = fs7.statSync(fullPath).size;
|
|
1613
2138
|
} catch {
|
|
1614
2139
|
}
|
|
1615
2140
|
changes.push({
|
|
@@ -1622,7 +2147,7 @@ function computeDiff(current, previous, sourceDir) {
|
|
|
1622
2147
|
} else if (previous[relPath] !== hash) {
|
|
1623
2148
|
let size = 0;
|
|
1624
2149
|
try {
|
|
1625
|
-
size =
|
|
2150
|
+
size = fs7.statSync(fullPath).size;
|
|
1626
2151
|
} catch {
|
|
1627
2152
|
}
|
|
1628
2153
|
changes.push({
|
|
@@ -1638,7 +2163,7 @@ function computeDiff(current, previous, sourceDir) {
|
|
|
1638
2163
|
for (const relPath of Object.keys(previous)) {
|
|
1639
2164
|
if (!(relPath in current)) {
|
|
1640
2165
|
changes.push({
|
|
1641
|
-
filePath:
|
|
2166
|
+
filePath: path11.join(sourceDir, relPath),
|
|
1642
2167
|
relativePath: relPath,
|
|
1643
2168
|
type: "deleted",
|
|
1644
2169
|
currentHash: "",
|
|
@@ -1650,7 +2175,7 @@ function computeDiff(current, previous, sourceDir) {
|
|
|
1650
2175
|
}
|
|
1651
2176
|
function loadState(stateFilePath) {
|
|
1652
2177
|
try {
|
|
1653
|
-
const raw =
|
|
2178
|
+
const raw = fs7.readFileSync(stateFilePath, "utf8");
|
|
1654
2179
|
return JSON.parse(raw);
|
|
1655
2180
|
} catch {
|
|
1656
2181
|
return {
|
|
@@ -1661,60 +2186,313 @@ function loadState(stateFilePath) {
|
|
|
1661
2186
|
}
|
|
1662
2187
|
}
|
|
1663
2188
|
function hashContent3(content) {
|
|
1664
|
-
return
|
|
2189
|
+
return crypto5.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
1665
2190
|
}
|
|
1666
2191
|
|
|
1667
2192
|
// src/connectors/index.ts
|
|
1668
|
-
import
|
|
1669
|
-
import
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2193
|
+
import fs8 from "fs";
|
|
2194
|
+
import path13 from "path";
|
|
2195
|
+
import os from "os";
|
|
2196
|
+
import { spawnSync } from "child_process";
|
|
2197
|
+
import { createRequire } from "module";
|
|
2198
|
+
import { fileURLToPath } from "url";
|
|
2199
|
+
|
|
2200
|
+
// src/connectors/codex-marketplace.ts
|
|
2201
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
2202
|
+
import path12 from "path";
|
|
2203
|
+
var MARKETPLACE_SCHEMA_VERSION = 1;
|
|
2204
|
+
var MARKETPLACE_MANIFEST_FILENAME = "marketplace.json";
|
|
2205
|
+
var VALID_INSTALL_TYPES = /* @__PURE__ */ new Set(["github", "git", "local", "url"]);
|
|
2206
|
+
function generateMarketplaceManifest(options) {
|
|
2207
|
+
const version = options?.packageVersion ?? readPackageVersion() ?? "0.0.0";
|
|
2208
|
+
return {
|
|
2209
|
+
version: MARKETPLACE_SCHEMA_VERSION,
|
|
2210
|
+
name: "remnic",
|
|
2211
|
+
description: "Remnic: Local-first AI memory with semantic search and consolidation",
|
|
2212
|
+
plugins: [
|
|
2213
|
+
{
|
|
2214
|
+
name: "remnic",
|
|
2215
|
+
version,
|
|
2216
|
+
description: "Persistent memory plugin for Codex CLI",
|
|
2217
|
+
repository: "joshuaswarren/remnic",
|
|
2218
|
+
installType: "github",
|
|
2219
|
+
entry: "packages/plugin-codex",
|
|
2220
|
+
configSchema: "openclaw.plugin.json"
|
|
2221
|
+
}
|
|
2222
|
+
]
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
function validateMarketplaceManifest(manifest) {
|
|
2226
|
+
const validation = checkMarketplaceManifest(manifest);
|
|
2227
|
+
if (!validation.valid) {
|
|
2228
|
+
throw new Error(
|
|
2229
|
+
`Invalid marketplace manifest: ${validation.errors.join("; ")}`
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
return manifest;
|
|
2233
|
+
}
|
|
2234
|
+
function checkMarketplaceManifest(manifest) {
|
|
2235
|
+
const errors = [];
|
|
2236
|
+
if (typeof manifest !== "object" || manifest === null) {
|
|
2237
|
+
return { valid: false, errors: ["manifest must be a non-null object"] };
|
|
2238
|
+
}
|
|
2239
|
+
const obj = manifest;
|
|
2240
|
+
if (obj.version !== MARKETPLACE_SCHEMA_VERSION) {
|
|
2241
|
+
errors.push(`version must be ${MARKETPLACE_SCHEMA_VERSION}, got ${JSON.stringify(obj.version)}`);
|
|
2242
|
+
}
|
|
2243
|
+
if (typeof obj.name !== "string" || obj.name.trim().length === 0) {
|
|
2244
|
+
errors.push("name must be a non-empty string");
|
|
2245
|
+
}
|
|
2246
|
+
if (typeof obj.description !== "string" || obj.description.trim().length === 0) {
|
|
2247
|
+
errors.push("description must be a non-empty string");
|
|
2248
|
+
}
|
|
2249
|
+
if (!Array.isArray(obj.plugins)) {
|
|
2250
|
+
errors.push("plugins must be an array");
|
|
2251
|
+
} else if (obj.plugins.length === 0) {
|
|
2252
|
+
errors.push("plugins must contain at least one entry");
|
|
2253
|
+
} else {
|
|
2254
|
+
for (let i = 0; i < obj.plugins.length; i++) {
|
|
2255
|
+
const plugin = obj.plugins[i];
|
|
2256
|
+
const prefix = `plugins[${i}]`;
|
|
2257
|
+
if (typeof plugin !== "object" || plugin === null) {
|
|
2258
|
+
errors.push(`${prefix} must be a non-null object`);
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
|
|
2262
|
+
errors.push(`${prefix}.name must be a non-empty string`);
|
|
2263
|
+
}
|
|
2264
|
+
if (typeof plugin.version !== "string" || plugin.version.trim().length === 0) {
|
|
2265
|
+
errors.push(`${prefix}.version must be a non-empty string`);
|
|
2266
|
+
}
|
|
2267
|
+
if (typeof plugin.description !== "string" || plugin.description.trim().length === 0) {
|
|
2268
|
+
errors.push(`${prefix}.description must be a non-empty string`);
|
|
2269
|
+
}
|
|
2270
|
+
if (typeof plugin.repository !== "string" || plugin.repository.trim().length === 0) {
|
|
2271
|
+
errors.push(`${prefix}.repository must be a non-empty string`);
|
|
2272
|
+
}
|
|
2273
|
+
if (typeof plugin.installType !== "string" || !VALID_INSTALL_TYPES.has(plugin.installType)) {
|
|
2274
|
+
errors.push(
|
|
2275
|
+
`${prefix}.installType must be one of: ${[...VALID_INSTALL_TYPES].join(", ")}; got ${JSON.stringify(plugin.installType)}`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
if ("manifestUrl" in plugin && plugin.manifestUrl !== void 0) {
|
|
2279
|
+
if (typeof plugin.manifestUrl !== "string" || plugin.manifestUrl.trim().length === 0) {
|
|
2280
|
+
errors.push(`${prefix}.manifestUrl must be a non-empty string when provided`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if ("entry" in plugin && plugin.entry !== void 0) {
|
|
2284
|
+
if (typeof plugin.entry !== "string" || plugin.entry.trim().length === 0) {
|
|
2285
|
+
errors.push(`${prefix}.entry must be a non-empty string when provided`);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if ("configSchema" in plugin && plugin.configSchema !== void 0) {
|
|
2289
|
+
if (typeof plugin.configSchema !== "string" || plugin.configSchema.trim().length === 0) {
|
|
2290
|
+
errors.push(`${prefix}.configSchema must be a non-empty string when provided`);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
return { valid: errors.length === 0, errors };
|
|
2296
|
+
}
|
|
2297
|
+
async function writeMarketplaceManifest(outputDir, manifest) {
|
|
2298
|
+
const validation = checkMarketplaceManifest(manifest);
|
|
2299
|
+
if (!validation.valid) {
|
|
2300
|
+
throw new Error(
|
|
2301
|
+
`Refusing to write invalid manifest: ${validation.errors.join("; ")}`
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
mkdirSync(outputDir, { recursive: true });
|
|
2305
|
+
const destPath = path12.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);
|
|
2306
|
+
const tmpPath = `${destPath}.tmp.${process.pid}`;
|
|
2307
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
2308
|
+
writeFileSync(tmpPath, content);
|
|
2309
|
+
renameSync(tmpPath, destPath);
|
|
2310
|
+
}
|
|
2311
|
+
async function installFromMarketplace(source, sourceType, config, logger) {
|
|
2312
|
+
const _log = logger ?? {
|
|
2313
|
+
info: (msg) => log.info(`[marketplace] ${msg}`),
|
|
2314
|
+
warn: (msg) => log.warn(`[marketplace] ${msg}`),
|
|
2315
|
+
debug: (msg) => log.debug(`[marketplace] ${msg}`)
|
|
2316
|
+
};
|
|
2317
|
+
if (!config.codexMarketplaceEnabled) {
|
|
2318
|
+
return {
|
|
2319
|
+
ok: false,
|
|
2320
|
+
message: "Codex marketplace is disabled in config (codexMarketplaceEnabled: false)",
|
|
2321
|
+
source,
|
|
2322
|
+
sourceType,
|
|
2323
|
+
pluginsFound: [],
|
|
2324
|
+
errors: ["marketplace_disabled"]
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
try {
|
|
2328
|
+
const manifest = await resolveManifest(source, sourceType, _log);
|
|
2329
|
+
const pluginNames = manifest.plugins.map((p) => p.name);
|
|
2330
|
+
_log.info(`marketplace install: found ${pluginNames.length} plugin(s) from ${sourceType}://${source}`);
|
|
2331
|
+
return {
|
|
2332
|
+
ok: true,
|
|
2333
|
+
message: `Successfully resolved ${pluginNames.length} plugin(s) from marketplace: ${pluginNames.join(", ")}`,
|
|
2334
|
+
source,
|
|
2335
|
+
sourceType,
|
|
2336
|
+
pluginsFound: pluginNames,
|
|
2337
|
+
errors: []
|
|
2338
|
+
};
|
|
2339
|
+
} catch (err) {
|
|
2340
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2341
|
+
_log.warn(`marketplace install failed: ${errMsg}`);
|
|
2342
|
+
return {
|
|
2343
|
+
ok: false,
|
|
2344
|
+
message: `Failed to install from marketplace: ${errMsg}`,
|
|
2345
|
+
source,
|
|
2346
|
+
sourceType,
|
|
2347
|
+
pluginsFound: [],
|
|
2348
|
+
errors: [errMsg]
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
async function resolveManifest(source, sourceType, logger) {
|
|
2353
|
+
switch (sourceType) {
|
|
2354
|
+
case "local":
|
|
2355
|
+
return resolveLocal(source, logger);
|
|
2356
|
+
case "url":
|
|
2357
|
+
return resolveUrl(source, logger);
|
|
2358
|
+
case "github":
|
|
2359
|
+
return resolveGithub(source, logger);
|
|
2360
|
+
case "git":
|
|
2361
|
+
return resolveGit(source, logger);
|
|
2362
|
+
default: {
|
|
2363
|
+
const _ = sourceType;
|
|
2364
|
+
throw new Error(`Invalid source type: ${String(_)}`);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function resolveLocal(dirPath, logger) {
|
|
2369
|
+
const manifestPath2 = path12.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);
|
|
2370
|
+
if (!existsSync(manifestPath2)) {
|
|
2371
|
+
throw new Error(`marketplace.json not found at ${manifestPath2}`);
|
|
2372
|
+
}
|
|
2373
|
+
logger.debug?.(`reading local marketplace manifest: ${manifestPath2}`);
|
|
2374
|
+
const raw = readFileSync(manifestPath2, "utf-8");
|
|
2375
|
+
let parsed;
|
|
2376
|
+
try {
|
|
2377
|
+
parsed = JSON.parse(raw);
|
|
2378
|
+
} catch {
|
|
2379
|
+
throw new Error(`Invalid JSON in ${manifestPath2}`);
|
|
2380
|
+
}
|
|
2381
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
2382
|
+
throw new Error(`marketplace.json at ${manifestPath2} is not a valid object`);
|
|
2383
|
+
}
|
|
2384
|
+
return validateMarketplaceManifest(parsed);
|
|
2385
|
+
}
|
|
2386
|
+
async function resolveUrl(url, logger) {
|
|
2387
|
+
logger.debug?.(`fetching marketplace manifest from URL: ${url}`);
|
|
2388
|
+
let parsedUrl;
|
|
2389
|
+
try {
|
|
2390
|
+
parsedUrl = new URL(url);
|
|
2391
|
+
} catch {
|
|
2392
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
2393
|
+
}
|
|
2394
|
+
if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
|
|
2395
|
+
throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol} (use https or http)`);
|
|
2396
|
+
}
|
|
2397
|
+
const response = await fetch(url);
|
|
2398
|
+
if (!response.ok) {
|
|
2399
|
+
throw new Error(`HTTP ${response.status} fetching ${url}`);
|
|
2400
|
+
}
|
|
2401
|
+
const body = await response.json();
|
|
2402
|
+
return validateMarketplaceManifest(body);
|
|
2403
|
+
}
|
|
2404
|
+
async function resolveGithub(repo, logger) {
|
|
2405
|
+
if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/u.test(repo)) {
|
|
2406
|
+
throw new Error(`Invalid GitHub repo format: "${repo}" (expected owner/repo)`);
|
|
2407
|
+
}
|
|
2408
|
+
const rawUrl = `https://raw.githubusercontent.com/${repo}/HEAD/${MARKETPLACE_MANIFEST_FILENAME}`;
|
|
2409
|
+
logger.debug?.(`fetching marketplace manifest from GitHub: ${rawUrl}`);
|
|
2410
|
+
return resolveUrl(rawUrl, logger);
|
|
2411
|
+
}
|
|
2412
|
+
async function resolveGit(gitUrl, logger) {
|
|
2413
|
+
const ghMatch = gitUrl.match(
|
|
2414
|
+
/^(?:https?:\/\/)?github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+?)(?:\.git)?$/u
|
|
2415
|
+
);
|
|
2416
|
+
if (ghMatch?.[1]) {
|
|
2417
|
+
logger.debug?.(`git URL looks like GitHub \u2014 delegating to github resolver`);
|
|
2418
|
+
return resolveGithub(ghMatch[1], logger);
|
|
2419
|
+
}
|
|
2420
|
+
throw new Error(
|
|
2421
|
+
`Git URL resolution requires a GitHub-format URL for now. Got: ${gitUrl}. Use --type github or --type url instead.`
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
function readPackageVersion() {
|
|
2425
|
+
const candidates = [
|
|
2426
|
+
path12.resolve(import.meta.dirname ?? ".", "../../../.."),
|
|
2427
|
+
path12.resolve(import.meta.dirname ?? ".", "../../../../.."),
|
|
2428
|
+
path12.resolve(import.meta.dirname ?? ".", "..")
|
|
2429
|
+
];
|
|
2430
|
+
for (const candidate of candidates) {
|
|
2431
|
+
const pkgPath = path12.join(candidate, "package.json");
|
|
2432
|
+
try {
|
|
2433
|
+
if (!existsSync(pkgPath)) continue;
|
|
2434
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
2435
|
+
const parsed = JSON.parse(raw);
|
|
2436
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.version === "string") {
|
|
2437
|
+
return parsed.version;
|
|
2438
|
+
}
|
|
2439
|
+
} catch {
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return void 0;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// src/connectors/index.ts
|
|
2446
|
+
var BUILTIN_CONNECTORS = [
|
|
2447
|
+
{
|
|
2448
|
+
id: "claude-code",
|
|
2449
|
+
name: "Claude Code",
|
|
2450
|
+
version: "1.0.0",
|
|
2451
|
+
description: "Anthropic's Claude Code CLI \u2014 direct memory access via MCP",
|
|
2452
|
+
capabilities: {
|
|
2453
|
+
observe: true,
|
|
2454
|
+
recall: true,
|
|
2455
|
+
store: true,
|
|
2456
|
+
search: true,
|
|
2457
|
+
entities: true,
|
|
2458
|
+
realtimeSync: true,
|
|
2459
|
+
batch: false,
|
|
2460
|
+
maxBudgetChars: 32e3,
|
|
2461
|
+
connectionType: "mcp"
|
|
2462
|
+
},
|
|
2463
|
+
configSchema: {
|
|
2464
|
+
mcpServerUrl: "URL of the MCP Remnic server",
|
|
2465
|
+
namespace: "Optional namespace (default: 'default')"
|
|
2466
|
+
},
|
|
2467
|
+
homepage: "https://claude.ai/code",
|
|
2468
|
+
author: "Anthropic",
|
|
2469
|
+
tags: ["official", "ai", "claude"],
|
|
2470
|
+
requiresToken: true
|
|
2471
|
+
},
|
|
2472
|
+
{
|
|
2473
|
+
id: "codex-cli",
|
|
2474
|
+
name: "Codex CLI",
|
|
2475
|
+
version: "1.0.0",
|
|
2476
|
+
description: "OpenAI Codex CLI \u2014 memory via MCP tool",
|
|
2477
|
+
capabilities: {
|
|
2478
|
+
observe: true,
|
|
2479
|
+
recall: true,
|
|
2480
|
+
store: true,
|
|
2481
|
+
search: false,
|
|
2482
|
+
entities: false,
|
|
2483
|
+
realtimeSync: false,
|
|
2484
|
+
batch: true,
|
|
2485
|
+
maxBudgetChars: 8e3,
|
|
2486
|
+
connectionType: "mcp"
|
|
2487
|
+
},
|
|
2488
|
+
configSchema: {
|
|
1712
2489
|
mcpServerUrl: "URL of the MCP Remnic server",
|
|
1713
2490
|
namespace: "Optional namespace"
|
|
1714
2491
|
},
|
|
1715
2492
|
homepage: "https://openai.com/codex",
|
|
1716
2493
|
author: "OpenAI",
|
|
1717
|
-
tags: ["official", "ai", "codex"]
|
|
2494
|
+
tags: ["official", "ai", "codex"],
|
|
2495
|
+
requiresToken: true
|
|
1718
2496
|
},
|
|
1719
2497
|
{
|
|
1720
2498
|
id: "cursor",
|
|
@@ -1878,7 +2656,8 @@ var BUILTIN_CONNECTORS = [
|
|
|
1878
2656
|
},
|
|
1879
2657
|
homepage: "https://replit.com",
|
|
1880
2658
|
author: "Replit",
|
|
1881
|
-
tags: ["official", "cloud"]
|
|
2659
|
+
tags: ["official", "cloud"],
|
|
2660
|
+
requiresToken: true
|
|
1882
2661
|
},
|
|
1883
2662
|
{
|
|
1884
2663
|
id: "generic-mcp",
|
|
@@ -1903,17 +2682,72 @@ var BUILTIN_CONNECTORS = [
|
|
|
1903
2682
|
},
|
|
1904
2683
|
homepage: "https://github.com/joshuaswarren/remnic",
|
|
1905
2684
|
author: "Remnic",
|
|
1906
|
-
tags: ["generic", "mcp"]
|
|
2685
|
+
tags: ["generic", "mcp"],
|
|
2686
|
+
requiresToken: true
|
|
2687
|
+
},
|
|
2688
|
+
{
|
|
2689
|
+
id: "weclone",
|
|
2690
|
+
name: "WeClone Avatar",
|
|
2691
|
+
version: "1.0.0",
|
|
2692
|
+
description: "Memory-aware OpenAI-compatible proxy for deployed WeClone avatars \u2014 injects Remnic recall into chat completions and buffers turns via observe",
|
|
2693
|
+
capabilities: {
|
|
2694
|
+
observe: true,
|
|
2695
|
+
recall: true,
|
|
2696
|
+
store: false,
|
|
2697
|
+
search: false,
|
|
2698
|
+
entities: false,
|
|
2699
|
+
realtimeSync: false,
|
|
2700
|
+
batch: false,
|
|
2701
|
+
maxBudgetChars: 32e3,
|
|
2702
|
+
connectionType: "http"
|
|
2703
|
+
},
|
|
2704
|
+
configSchema: {
|
|
2705
|
+
wecloneApiUrl: "Base URL of the WeClone OpenAI-compatible API (e.g. http://localhost:8000/v1)",
|
|
2706
|
+
proxyPort: "Local port where the memory proxy will listen (default 8100)",
|
|
2707
|
+
remnicDaemonUrl: "URL of the Remnic daemon exposing /engram/v1/recall and /engram/v1/observe",
|
|
2708
|
+
sessionStrategy: "Per-caller session mapping strategy: 'caller-id' | 'single'",
|
|
2709
|
+
wecloneModelName: "Optional fine-tuned model name passed through to WeClone"
|
|
2710
|
+
},
|
|
2711
|
+
homepage: "https://github.com/xming521/weclone",
|
|
2712
|
+
author: "Remnic",
|
|
2713
|
+
tags: ["official", "ai", "weclone", "proxy"],
|
|
2714
|
+
requiresToken: true
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
id: "hermes",
|
|
2718
|
+
name: "Hermes Agent",
|
|
2719
|
+
version: "1.0.0",
|
|
2720
|
+
description: "Hermes Agent MemoryProvider \u2014 automatic recall/observe on every turn via Python plugin protocol",
|
|
2721
|
+
capabilities: {
|
|
2722
|
+
observe: true,
|
|
2723
|
+
recall: true,
|
|
2724
|
+
store: true,
|
|
2725
|
+
search: true,
|
|
2726
|
+
entities: false,
|
|
2727
|
+
realtimeSync: true,
|
|
2728
|
+
batch: false,
|
|
2729
|
+
maxBudgetChars: 32e3,
|
|
2730
|
+
connectionType: "http"
|
|
2731
|
+
},
|
|
2732
|
+
configSchema: {
|
|
2733
|
+
host: "Remnic daemon host (default: 127.0.0.1)",
|
|
2734
|
+
port: "Remnic daemon port (default: 4318)",
|
|
2735
|
+
profile: "Hermes profile name (default: default)"
|
|
2736
|
+
},
|
|
2737
|
+
homepage: "https://github.com/joshuaswarren/remnic/tree/main/packages/plugin-hermes",
|
|
2738
|
+
author: "Remnic",
|
|
2739
|
+
tags: ["official", "python", "hermes"],
|
|
2740
|
+
requiresToken: true
|
|
1907
2741
|
}
|
|
1908
2742
|
];
|
|
1909
2743
|
var REGISTRY_DIR_NAME = ".engram-connectors";
|
|
1910
2744
|
function getRegistryPath() {
|
|
1911
|
-
const configDir = process.env.XDG_CONFIG_HOME ?
|
|
1912
|
-
return
|
|
2745
|
+
const configDir = process.env.XDG_CONFIG_HOME ? path13.join(process.env.XDG_CONFIG_HOME, "engram") : path13.join(process.env.HOME ?? "~", ".config", "engram");
|
|
2746
|
+
return path13.join(configDir, REGISTRY_DIR_NAME, "registry.json");
|
|
1913
2747
|
}
|
|
1914
2748
|
function loadRegistry() {
|
|
1915
2749
|
const regPath = getRegistryPath();
|
|
1916
|
-
if (!
|
|
2750
|
+
if (!fs8.existsSync(regPath)) {
|
|
1917
2751
|
const registry = {
|
|
1918
2752
|
connectors: BUILTIN_CONNECTORS,
|
|
1919
2753
|
registryPath: regPath
|
|
@@ -1921,14 +2755,12 @@ function loadRegistry() {
|
|
|
1921
2755
|
saveRegistry(registry);
|
|
1922
2756
|
return registry;
|
|
1923
2757
|
}
|
|
1924
|
-
const raw =
|
|
2758
|
+
const raw = fs8.readFileSync(regPath, "utf8");
|
|
1925
2759
|
try {
|
|
1926
2760
|
const parsed = JSON.parse(raw);
|
|
1927
|
-
const
|
|
1928
|
-
const
|
|
1929
|
-
|
|
1930
|
-
...parsed.connectors ?? []
|
|
1931
|
-
];
|
|
2761
|
+
const builtinIds = new Set(BUILTIN_CONNECTORS.map((b) => b.id));
|
|
2762
|
+
const customOnly = (parsed.connectors ?? []).filter((c) => !builtinIds.has(c.id));
|
|
2763
|
+
const merged = [...BUILTIN_CONNECTORS, ...customOnly];
|
|
1932
2764
|
return {
|
|
1933
2765
|
connectors: merged,
|
|
1934
2766
|
registryPath: regPath
|
|
@@ -1944,19 +2776,19 @@ function loadRegistry() {
|
|
|
1944
2776
|
}
|
|
1945
2777
|
function saveRegistry(registry) {
|
|
1946
2778
|
const regPath = registry.registryPath;
|
|
1947
|
-
|
|
1948
|
-
|
|
2779
|
+
fs8.mkdirSync(path13.dirname(regPath), { recursive: true });
|
|
2780
|
+
fs8.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
|
|
1949
2781
|
}
|
|
1950
2782
|
function listConnectors() {
|
|
1951
2783
|
const registry = loadRegistry();
|
|
1952
2784
|
const connectorsDir = getConnectorsDir();
|
|
1953
2785
|
const installedIds = /* @__PURE__ */ new Set();
|
|
1954
|
-
if (
|
|
1955
|
-
for (const entry of
|
|
2786
|
+
if (fs8.existsSync(connectorsDir)) {
|
|
2787
|
+
for (const entry of fs8.readdirSync(connectorsDir)) {
|
|
1956
2788
|
if (entry.endsWith(".json")) {
|
|
1957
2789
|
try {
|
|
1958
2790
|
const config = JSON.parse(
|
|
1959
|
-
|
|
2791
|
+
fs8.readFileSync(path13.join(connectorsDir, entry), "utf8")
|
|
1960
2792
|
);
|
|
1961
2793
|
installedIds.add(config.connectorId);
|
|
1962
2794
|
} catch {
|
|
@@ -1970,14 +2802,15 @@ function listConnectors() {
|
|
|
1970
2802
|
}));
|
|
1971
2803
|
const installed = [];
|
|
1972
2804
|
for (const id of installedIds) {
|
|
1973
|
-
const configPath =
|
|
2805
|
+
const configPath = path13.join(connectorsDir, `${id}.json`);
|
|
1974
2806
|
try {
|
|
1975
|
-
const
|
|
2807
|
+
const raw = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
2808
|
+
const { token: _redacted, ...config } = raw;
|
|
1976
2809
|
installed.push({
|
|
1977
2810
|
connectorId: id,
|
|
1978
2811
|
config,
|
|
1979
2812
|
status: "installed",
|
|
1980
|
-
installedAt:
|
|
2813
|
+
installedAt: raw.installedAt
|
|
1981
2814
|
});
|
|
1982
2815
|
} catch {
|
|
1983
2816
|
}
|
|
@@ -2005,41 +2838,893 @@ function installConnector(options) {
|
|
|
2005
2838
|
};
|
|
2006
2839
|
}
|
|
2007
2840
|
const configDir = getConnectorsDir();
|
|
2008
|
-
|
|
2009
|
-
const configPath =
|
|
2841
|
+
fs8.mkdirSync(configDir, { recursive: true });
|
|
2842
|
+
const configPath = path13.join(configDir, `${options.connectorId}.json`);
|
|
2843
|
+
let hermesSavedProfile;
|
|
2844
|
+
let hermesSavedHost;
|
|
2845
|
+
let hermesSavedPort;
|
|
2846
|
+
let hermesResolvedProfile;
|
|
2847
|
+
let hermesResolvedHost;
|
|
2848
|
+
let hermesResolvedPort;
|
|
2849
|
+
if (options.connectorId === "hermes") {
|
|
2850
|
+
if (fs8.existsSync(configPath)) {
|
|
2851
|
+
try {
|
|
2852
|
+
const prev = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
2853
|
+
if (prev?.profile != null) {
|
|
2854
|
+
try {
|
|
2855
|
+
hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));
|
|
2856
|
+
} catch {
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
if (prev?.host != null) {
|
|
2860
|
+
try {
|
|
2861
|
+
hermesSavedHost = sanitizeHermesHost(String(prev.host));
|
|
2862
|
+
} catch {
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if (prev?.port != null) {
|
|
2866
|
+
try {
|
|
2867
|
+
const coercedPort = Number(String(prev.port));
|
|
2868
|
+
hermesSavedPort = sanitizeHermesPort(coercedPort);
|
|
2869
|
+
} catch {
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
} catch {
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
hermesResolvedProfile = hermesSavedProfile ?? "default";
|
|
2876
|
+
hermesResolvedHost = hermesSavedHost ?? "127.0.0.1";
|
|
2877
|
+
if (options.config?.port !== void 0) {
|
|
2878
|
+
try {
|
|
2879
|
+
hermesResolvedPort = sanitizeHermesPort(Number(String(options.config.port)));
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
return {
|
|
2882
|
+
connectorId: options.connectorId,
|
|
2883
|
+
status: "error",
|
|
2884
|
+
message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
if (hermesResolvedPort === void 0) {
|
|
2889
|
+
hermesResolvedPort = hermesSavedPort ?? 4318;
|
|
2890
|
+
}
|
|
2891
|
+
if (options.config?.profile !== void 0) {
|
|
2892
|
+
try {
|
|
2893
|
+
hermesResolvedProfile = sanitizeHermesProfile(String(options.config.profile));
|
|
2894
|
+
} catch (err) {
|
|
2895
|
+
return {
|
|
2896
|
+
connectorId: options.connectorId,
|
|
2897
|
+
status: "error",
|
|
2898
|
+
message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
if (options.config?.host !== void 0) {
|
|
2903
|
+
try {
|
|
2904
|
+
hermesResolvedHost = sanitizeHermesHost(String(options.config.host));
|
|
2905
|
+
} catch (err) {
|
|
2906
|
+
return {
|
|
2907
|
+
connectorId: options.connectorId,
|
|
2908
|
+
status: "error",
|
|
2909
|
+
message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
const nonHermesPriorTokenStore = options.connectorId !== "hermes" && manifest.requiresToken ? loadTokenStore() : null;
|
|
2915
|
+
let tokenEntry = null;
|
|
2916
|
+
if (options.connectorId === "hermes") {
|
|
2917
|
+
try {
|
|
2918
|
+
tokenEntry = buildTokenEntry(options.connectorId);
|
|
2919
|
+
} catch {
|
|
2920
|
+
}
|
|
2921
|
+
} else if (manifest.requiresToken) {
|
|
2922
|
+
try {
|
|
2923
|
+
tokenEntry = generateToken(options.connectorId);
|
|
2924
|
+
} catch {
|
|
2925
|
+
if (nonHermesPriorTokenStore !== null) {
|
|
2926
|
+
try {
|
|
2927
|
+
saveTokenStore(nonHermesPriorTokenStore);
|
|
2928
|
+
} catch {
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
if (options.connectorId !== "hermes" && manifest.requiresToken && tokenEntry === null) {
|
|
2934
|
+
return {
|
|
2935
|
+
connectorId: options.connectorId,
|
|
2936
|
+
status: "error",
|
|
2937
|
+
message: `${manifest.name} install aborted: token generation failed. Run \`remnic token generate ${options.connectorId}\` to create the token, then reinstall.`
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
const { token: _callerToken, ...safeUserConfig } = options.config ?? {};
|
|
2010
2941
|
const resolvedConfig = {
|
|
2011
2942
|
connectorId: options.connectorId,
|
|
2012
2943
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2013
|
-
...
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2944
|
+
...safeUserConfig,
|
|
2945
|
+
// For hermes, always overlay the sanitized/coerced resolved values so that
|
|
2946
|
+
// the connector JSON always has a numeric port and validated profile/host.
|
|
2947
|
+
// This also ensures options.config string values (from --config=port=5555)
|
|
2948
|
+
// are replaced with their sanitized numeric equivalents (Fix 2 root cause).
|
|
2949
|
+
...hermesResolvedProfile !== void 0 ? {
|
|
2950
|
+
profile: hermesResolvedProfile,
|
|
2951
|
+
host: hermesResolvedHost,
|
|
2952
|
+
port: hermesResolvedPort
|
|
2953
|
+
} : {}
|
|
2021
2954
|
};
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2955
|
+
if (options.connectorId === "hermes") {
|
|
2956
|
+
const rawProfile = hermesResolvedProfile;
|
|
2957
|
+
const hermesHost = hermesResolvedHost;
|
|
2958
|
+
const hermesPort = hermesResolvedPort;
|
|
2959
|
+
let hermesProfile;
|
|
2960
|
+
try {
|
|
2961
|
+
hermesProfile = sanitizeHermesProfile(rawProfile);
|
|
2962
|
+
} catch (err) {
|
|
2963
|
+
return {
|
|
2964
|
+
connectorId: options.connectorId,
|
|
2965
|
+
status: "error",
|
|
2966
|
+
message: `Hermes install aborted: ${err instanceof Error ? err.message : String(err)}`
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
if (!tokenEntry) {
|
|
2970
|
+
return {
|
|
2971
|
+
connectorId: options.connectorId,
|
|
2972
|
+
status: "error",
|
|
2973
|
+
message: "Hermes install aborted: token store unavailable. Run `remnic token generate hermes` then reinstall to complete setup."
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
let yamlResult;
|
|
2977
|
+
try {
|
|
2978
|
+
yamlResult = upsertHermesConfig({
|
|
2979
|
+
profile: hermesProfile,
|
|
2980
|
+
host: hermesHost,
|
|
2981
|
+
port: hermesPort,
|
|
2982
|
+
token: tokenEntry.token
|
|
2983
|
+
});
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
return {
|
|
2986
|
+
connectorId: options.connectorId,
|
|
2987
|
+
status: "error",
|
|
2988
|
+
message: `Hermes install aborted: config.yaml write failed \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
if (!yamlResult.updated) {
|
|
2992
|
+
return {
|
|
2993
|
+
connectorId: options.connectorId,
|
|
2994
|
+
status: "error",
|
|
2995
|
+
message: `Hermes install aborted: ${yamlResult.reason ?? "config.yaml not written"}. Create the Hermes profile directory first, then reinstall.`
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
const priorTokenStore = loadTokenStore();
|
|
2999
|
+
let committed = false;
|
|
3000
|
+
try {
|
|
3001
|
+
commitTokenEntry(tokenEntry);
|
|
3002
|
+
committed = true;
|
|
3003
|
+
} catch (commitErr) {
|
|
3004
|
+
let tokensRolledBack = true;
|
|
3005
|
+
let tokensRollbackErrMsg = "";
|
|
3006
|
+
try {
|
|
3007
|
+
saveTokenStore(priorTokenStore);
|
|
3008
|
+
} catch (tokenRestoreErr) {
|
|
3009
|
+
tokensRolledBack = false;
|
|
3010
|
+
tokensRollbackErrMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
|
|
3011
|
+
}
|
|
3012
|
+
let yamlRolledBack = true;
|
|
3013
|
+
let yamlRollbackErrMsg = "";
|
|
3014
|
+
try {
|
|
3015
|
+
if (yamlResult.priorContent === null) {
|
|
3016
|
+
fs8.unlinkSync(yamlResult.configPath);
|
|
3017
|
+
} else if (typeof yamlResult.priorContent === "string") {
|
|
3018
|
+
writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
|
|
3019
|
+
}
|
|
3020
|
+
} catch (yamlRestoreErr) {
|
|
3021
|
+
yamlRolledBack = false;
|
|
3022
|
+
yamlRollbackErrMsg = yamlRestoreErr instanceof Error ? yamlRestoreErr.message : String(yamlRestoreErr);
|
|
3023
|
+
}
|
|
3024
|
+
const commitErrMsg = commitErr instanceof Error ? commitErr.message : String(commitErr);
|
|
3025
|
+
let message;
|
|
3026
|
+
if (tokensRolledBack && yamlRolledBack) {
|
|
3027
|
+
message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. config.yaml and tokens.json restored to prior state. Resolve the tokens.json access issue, then reinstall.`;
|
|
3028
|
+
} else if (!yamlRolledBack && tokensRolledBack) {
|
|
3029
|
+
message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. tokens.json restored but config.yaml rollback ALSO failed (${yamlRollbackErrMsg}). Hermes daemon may be in an inconsistent state: config references a stale token. Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and reinstall.`;
|
|
3030
|
+
} else if (yamlRolledBack && !tokensRolledBack) {
|
|
3031
|
+
message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. config.yaml restored but tokens.json rollback ALSO failed (${tokensRollbackErrMsg}). Hermes daemon may be in an inconsistent state: tokens.json is corrupt or incomplete. Manually inspect ~/.remnic/tokens.json and reinstall.`;
|
|
3032
|
+
} else {
|
|
3033
|
+
message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. BOTH rollbacks failed: config.yaml rollback failed (${yamlRollbackErrMsg}); tokens.json rollback failed (${tokensRollbackErrMsg}). Hermes daemon is likely in an inconsistent state. Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and ~/.remnic/tokens.json, then reinstall.`;
|
|
3034
|
+
}
|
|
3035
|
+
return {
|
|
3036
|
+
connectorId: options.connectorId,
|
|
3037
|
+
status: "error",
|
|
3038
|
+
message
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
try {
|
|
3042
|
+
writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
|
|
3043
|
+
} catch (writeErr) {
|
|
3044
|
+
let tokenRollbackFailed = false;
|
|
3045
|
+
let tokenRollbackMsg = "token store restored to pre-install snapshot";
|
|
3046
|
+
try {
|
|
3047
|
+
saveTokenStore(priorTokenStore);
|
|
3048
|
+
} catch (tokenRestoreErr) {
|
|
3049
|
+
tokenRollbackFailed = true;
|
|
3050
|
+
tokenRollbackMsg = `token rollback failed: ${tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr)}`;
|
|
3051
|
+
}
|
|
3052
|
+
let yamlRollbackMsg = "config.yaml restored";
|
|
3053
|
+
try {
|
|
3054
|
+
if (yamlResult.priorContent === null) {
|
|
3055
|
+
let unlinkSucceeded = false;
|
|
3056
|
+
let unlinkErr;
|
|
3057
|
+
try {
|
|
3058
|
+
fs8.unlinkSync(yamlResult.configPath);
|
|
3059
|
+
unlinkSucceeded = true;
|
|
3060
|
+
} catch (err) {
|
|
3061
|
+
unlinkErr = err;
|
|
3062
|
+
}
|
|
3063
|
+
if (unlinkSucceeded) {
|
|
3064
|
+
yamlRollbackMsg = "config.yaml removed (was newly created)";
|
|
3065
|
+
} else {
|
|
3066
|
+
const unlinkMsg = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
|
|
3067
|
+
yamlRollbackMsg = `config.yaml rollback failed: could not remove newly-created file \u2014 ${unlinkMsg}`;
|
|
3068
|
+
}
|
|
3069
|
+
} else if (typeof yamlResult.priorContent === "string") {
|
|
3070
|
+
writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
|
|
3071
|
+
yamlRollbackMsg = "config.yaml restored to prior content";
|
|
3072
|
+
}
|
|
3073
|
+
} catch (yamlRollbackErr) {
|
|
3074
|
+
yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;
|
|
3075
|
+
}
|
|
3076
|
+
const urgentSuffix = tokenRollbackFailed ? ` tokens.json may be in an inconsistent state \u2014 manually restore hermes token with 'remnic token generate hermes'.` : "";
|
|
3077
|
+
return {
|
|
3078
|
+
connectorId: options.connectorId,
|
|
3079
|
+
status: "error",
|
|
3080
|
+
message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
const notes = [];
|
|
3084
|
+
notes.push(`Updated Hermes config: ${yamlResult.configPath}`);
|
|
3085
|
+
let oldProfileResolvesToDifferentFile = false;
|
|
3086
|
+
if (hermesSavedProfile !== void 0) {
|
|
3087
|
+
try {
|
|
3088
|
+
oldProfileResolvesToDifferentFile = hermesConfigPath(hermesSavedProfile) !== hermesConfigPath(hermesProfile);
|
|
3089
|
+
} catch {
|
|
3090
|
+
oldProfileResolvesToDifferentFile = false;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
if (oldProfileResolvesToDifferentFile) {
|
|
3094
|
+
try {
|
|
3095
|
+
const oldCleanResult = removeHermesConfig({ profile: hermesSavedProfile });
|
|
3096
|
+
if (oldCleanResult.updated) {
|
|
3097
|
+
notes.push(`Cleaned stale remnic: block from previous profile: ${oldCleanResult.configPath}`);
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
notes.push(`Note: could not clean stale remnic: block from previous profile "${hermesSavedProfile}"`);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
if (committed && tokenEntry) {
|
|
3104
|
+
const daemonOk = checkDaemonHealth(hermesHost, hermesPort, tokenEntry.token);
|
|
3105
|
+
if (daemonOk) {
|
|
3106
|
+
notes.push("Daemon health check: OK");
|
|
3107
|
+
} else {
|
|
3108
|
+
notes.push(
|
|
3109
|
+
`Daemon not reachable at ${hermesHost}:${hermesPort} \u2014 start with: remnic daemon start`
|
|
3110
|
+
);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
const suffix = notes.length > 0 ? `
|
|
3114
|
+
${notes.join("\n ")}` : "";
|
|
2027
3115
|
return {
|
|
2028
|
-
connectorId,
|
|
3116
|
+
connectorId: options.connectorId,
|
|
3117
|
+
status: "installed",
|
|
2029
3118
|
configPath,
|
|
2030
|
-
message:
|
|
3119
|
+
message: `Installed ${manifest.name} v${manifest.version}${suffix}`
|
|
2031
3120
|
};
|
|
2032
3121
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
3122
|
+
let extensionMessage = "";
|
|
3123
|
+
let extensionInstalled = false;
|
|
3124
|
+
let extensionHandle = null;
|
|
3125
|
+
if (options.connectorId === "codex-cli") {
|
|
3126
|
+
const coerced = coerceInstallExtension(resolvedConfig.installExtension);
|
|
3127
|
+
if (coerced !== void 0) {
|
|
3128
|
+
resolvedConfig.installExtension = coerced;
|
|
3129
|
+
}
|
|
3130
|
+
const shouldInstall = resolvedConfig.installExtension !== false;
|
|
3131
|
+
resolvedConfig.installExtension = shouldInstall;
|
|
3132
|
+
const codexHomeOverride = typeof resolvedConfig.codexHome === "string" && resolvedConfig.codexHome.length > 0 ? resolvedConfig.codexHome : null;
|
|
3133
|
+
const resolvedCodexHome = resolveCodexHome(codexHomeOverride);
|
|
3134
|
+
resolvedConfig.codexHome = resolvedCodexHome;
|
|
3135
|
+
if (shouldInstall) {
|
|
3136
|
+
try {
|
|
3137
|
+
const extensionSourceOverride = typeof resolvedConfig.extensionSourceDir === "string" && resolvedConfig.extensionSourceDir.length > 0 ? resolvedConfig.extensionSourceDir : null;
|
|
3138
|
+
const extResult = installCodexMemoryExtension({
|
|
3139
|
+
codexHome: resolvedCodexHome,
|
|
3140
|
+
sourceDir: extensionSourceOverride
|
|
3141
|
+
});
|
|
3142
|
+
extensionMessage = ` (memory extension: ${extResult.remnicExtensionDir})`;
|
|
3143
|
+
extensionInstalled = true;
|
|
3144
|
+
extensionHandle = extResult;
|
|
3145
|
+
} catch (err) {
|
|
3146
|
+
const errMsg = err instanceof Error ? err.message : "unknown error";
|
|
3147
|
+
let extensionErrTokenRolledBack = false;
|
|
3148
|
+
let extensionErrTokenRollbackMsg = "";
|
|
3149
|
+
if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
|
|
3150
|
+
try {
|
|
3151
|
+
saveTokenStore(nonHermesPriorTokenStore);
|
|
3152
|
+
extensionErrTokenRolledBack = true;
|
|
3153
|
+
} catch (tokenRestoreErr) {
|
|
3154
|
+
extensionErrTokenRolledBack = false;
|
|
3155
|
+
extensionErrTokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
const tokenRollbackSuffix = manifest.requiresToken ? extensionErrTokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${extensionErrTokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
|
|
3159
|
+
return {
|
|
3160
|
+
connectorId: options.connectorId,
|
|
3161
|
+
status: "error",
|
|
3162
|
+
message: `Memory extension install failed \u2014 ${errMsg}.${tokenRollbackSuffix} Resolve the issue, then reinstall.`
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
} else {
|
|
3166
|
+
extensionMessage = " (memory extension: skipped via installExtension=false)";
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
let weCloneProxyHandleRollback = null;
|
|
3170
|
+
if (options.connectorId === "weclone") {
|
|
3171
|
+
try {
|
|
3172
|
+
let proxyConfigPath = null;
|
|
3173
|
+
if (existing && fs8.existsSync(configPath)) {
|
|
3174
|
+
try {
|
|
3175
|
+
const savedRegistryConfig = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
3176
|
+
if (typeof savedRegistryConfig.proxyConfigPath === "string" && savedRegistryConfig.proxyConfigPath.length > 0) {
|
|
3177
|
+
proxyConfigPath = savedRegistryConfig.proxyConfigPath;
|
|
3178
|
+
}
|
|
3179
|
+
} catch {
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
if (proxyConfigPath === null) {
|
|
3183
|
+
proxyConfigPath = resolveWeCloneProxyConfigPath();
|
|
3184
|
+
}
|
|
3185
|
+
const prior = readWeCloneProxyConfigIfExists(proxyConfigPath);
|
|
3186
|
+
const proxyConfig = buildWeCloneProxyConfig({
|
|
3187
|
+
userConfig: safeUserConfig,
|
|
3188
|
+
priorConfig: prior ? safeParseJson(prior) : null,
|
|
3189
|
+
authToken: tokenEntry?.token
|
|
3190
|
+
});
|
|
3191
|
+
fs8.mkdirSync(path13.dirname(proxyConfigPath), { recursive: true });
|
|
3192
|
+
weCloneProxyHandleRollback = () => {
|
|
3193
|
+
try {
|
|
3194
|
+
if (prior === null) {
|
|
3195
|
+
if (fs8.existsSync(proxyConfigPath)) {
|
|
3196
|
+
fs8.unlinkSync(proxyConfigPath);
|
|
3197
|
+
}
|
|
3198
|
+
} else {
|
|
3199
|
+
writeSecretFileSync(proxyConfigPath, prior);
|
|
3200
|
+
}
|
|
3201
|
+
} catch {
|
|
3202
|
+
}
|
|
3203
|
+
};
|
|
3204
|
+
try {
|
|
3205
|
+
writeSecretFileSync(
|
|
3206
|
+
proxyConfigPath,
|
|
3207
|
+
JSON.stringify(proxyConfig, null, 2)
|
|
3208
|
+
);
|
|
3209
|
+
} catch (writeErr) {
|
|
3210
|
+
try {
|
|
3211
|
+
weCloneProxyHandleRollback();
|
|
3212
|
+
} catch {
|
|
3213
|
+
}
|
|
3214
|
+
weCloneProxyHandleRollback = null;
|
|
3215
|
+
throw writeErr;
|
|
3216
|
+
}
|
|
3217
|
+
resolvedConfig.proxyConfigPath = proxyConfigPath;
|
|
3218
|
+
resolvedConfig.proxyPort = proxyConfig.proxyPort;
|
|
3219
|
+
resolvedConfig.wecloneApiUrl = proxyConfig.wecloneApiUrl;
|
|
3220
|
+
resolvedConfig.remnicDaemonUrl = proxyConfig.remnicDaemonUrl;
|
|
3221
|
+
resolvedConfig.sessionStrategy = proxyConfig.sessionStrategy;
|
|
3222
|
+
} catch (weCloneErr) {
|
|
3223
|
+
let tokenRolledBack = false;
|
|
3224
|
+
let tokenRollbackMsg = "";
|
|
3225
|
+
if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
|
|
3226
|
+
try {
|
|
3227
|
+
saveTokenStore(nonHermesPriorTokenStore);
|
|
3228
|
+
tokenRolledBack = true;
|
|
3229
|
+
} catch (tokenRestoreErr) {
|
|
3230
|
+
tokenRolledBack = false;
|
|
3231
|
+
tokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
const tokenSuffix = manifest.requiresToken && tokenEntry !== null ? tokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${tokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
|
|
3235
|
+
return {
|
|
3236
|
+
connectorId: options.connectorId,
|
|
3237
|
+
status: "error",
|
|
3238
|
+
message: `WeClone install aborted: proxy config write failed \u2014 ${weCloneErr instanceof Error ? weCloneErr.message : String(weCloneErr)}.${tokenSuffix} Resolve the write permission issue on ~/.remnic/connectors/, then reinstall.`
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
const INTERNAL_KEYS_DENYLIST = [
|
|
3243
|
+
"extensionSourceDir"
|
|
3244
|
+
// test-only override for the plugin-codex source path
|
|
3245
|
+
];
|
|
3246
|
+
for (const key of INTERNAL_KEYS_DENYLIST) {
|
|
3247
|
+
delete resolvedConfig[key];
|
|
3248
|
+
}
|
|
3249
|
+
try {
|
|
3250
|
+
writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
|
|
3251
|
+
} catch (writeErr) {
|
|
3252
|
+
let configWriteTokenRolledBack = false;
|
|
3253
|
+
let configWriteTokenRollbackMsg = "";
|
|
3254
|
+
if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
|
|
3255
|
+
try {
|
|
3256
|
+
saveTokenStore(nonHermesPriorTokenStore);
|
|
3257
|
+
configWriteTokenRolledBack = true;
|
|
3258
|
+
} catch (tokenRestoreErr) {
|
|
3259
|
+
configWriteTokenRolledBack = false;
|
|
3260
|
+
configWriteTokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
if (extensionInstalled && extensionHandle !== null) {
|
|
3264
|
+
try {
|
|
3265
|
+
extensionHandle.rollback();
|
|
3266
|
+
} catch {
|
|
3267
|
+
console.warn(
|
|
3268
|
+
"[remnic/connectors] installConnector: config write failed and extension rollback also failed \u2014 manual cleanup of memories_extensions/remnic may be required."
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
if (weCloneProxyHandleRollback !== null) {
|
|
3273
|
+
try {
|
|
3274
|
+
weCloneProxyHandleRollback();
|
|
3275
|
+
} catch {
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
const configWriteTokenSuffix = manifest.requiresToken && tokenEntry !== null ? configWriteTokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${configWriteTokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
|
|
3279
|
+
return {
|
|
3280
|
+
connectorId: options.connectorId,
|
|
3281
|
+
status: "error",
|
|
3282
|
+
message: `${manifest.name} install aborted: connector config write failed \u2014 ${writeErr instanceof Error ? writeErr.message : String(writeErr)}.${configWriteTokenSuffix} Resolve the write permission issue, then reinstall.`
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
if (extensionInstalled && extensionHandle !== null) {
|
|
3286
|
+
extensionHandle.commit();
|
|
3287
|
+
}
|
|
3288
|
+
return {
|
|
3289
|
+
connectorId: options.connectorId,
|
|
3290
|
+
status: "installed",
|
|
3291
|
+
configPath,
|
|
3292
|
+
message: `Installed ${manifest.name} v${manifest.version}${extensionMessage}`
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
function removeConnector(connectorId) {
|
|
3296
|
+
const configDir = getConnectorsDir();
|
|
3297
|
+
const configPath = path13.join(configDir, `${connectorId}.json`);
|
|
3298
|
+
let codexHomeOverride = null;
|
|
3299
|
+
let savedInstallExtension = void 0;
|
|
3300
|
+
let configParsed = false;
|
|
3301
|
+
if (connectorId === "codex-cli" && fs8.existsSync(configPath)) {
|
|
3302
|
+
try {
|
|
3303
|
+
const parsed = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
3304
|
+
configParsed = true;
|
|
3305
|
+
if (typeof parsed.codexHome === "string" && parsed.codexHome.length > 0) {
|
|
3306
|
+
codexHomeOverride = parsed.codexHome;
|
|
3307
|
+
}
|
|
3308
|
+
const coerced = coerceInstallExtension(parsed.installExtension);
|
|
3309
|
+
if (coerced !== void 0) {
|
|
3310
|
+
savedInstallExtension = coerced;
|
|
3311
|
+
}
|
|
3312
|
+
} catch {
|
|
3313
|
+
console.debug(
|
|
3314
|
+
"[remnic/connectors] removeConnector: codex-cli.json parse failed \u2014 skipping extension removal to avoid touching unverified paths"
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
if (!fs8.existsSync(configPath)) {
|
|
3319
|
+
let staleTokenRevoked = false;
|
|
3320
|
+
try {
|
|
3321
|
+
staleTokenRevoked = revokeToken(connectorId);
|
|
3322
|
+
} catch {
|
|
3323
|
+
}
|
|
3324
|
+
const message = staleTokenRevoked ? `${connectorId} is not installed. Removed stale token entry for ${connectorId}.` : "Not installed";
|
|
3325
|
+
return {
|
|
3326
|
+
connectorId,
|
|
3327
|
+
configPath,
|
|
3328
|
+
status: "not_found",
|
|
3329
|
+
message
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
let storedProfile = "default";
|
|
3333
|
+
if (connectorId === "hermes") {
|
|
3334
|
+
try {
|
|
3335
|
+
const stored = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
3336
|
+
if (typeof stored?.profile === "string") storedProfile = stored.profile;
|
|
3337
|
+
} catch {
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
let weCloneProxyConfigPath = null;
|
|
3341
|
+
let weCloneRegistryParseFailed = false;
|
|
3342
|
+
if (connectorId === "weclone") {
|
|
3343
|
+
try {
|
|
3344
|
+
const stored = JSON.parse(fs8.readFileSync(configPath, "utf8"));
|
|
3345
|
+
if (typeof stored.proxyConfigPath === "string" && stored.proxyConfigPath.length > 0) {
|
|
3346
|
+
weCloneProxyConfigPath = stored.proxyConfigPath;
|
|
3347
|
+
}
|
|
3348
|
+
} catch {
|
|
3349
|
+
weCloneRegistryParseFailed = true;
|
|
3350
|
+
}
|
|
3351
|
+
if (weCloneProxyConfigPath === null && !weCloneRegistryParseFailed) {
|
|
3352
|
+
try {
|
|
3353
|
+
weCloneProxyConfigPath = resolveWeCloneProxyConfigPath();
|
|
3354
|
+
} catch {
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
if (connectorId === "weclone" && weCloneRegistryParseFailed) {
|
|
3359
|
+
console.warn(
|
|
3360
|
+
"[remnic/connectors] removeConnector: weclone.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
|
|
3361
|
+
);
|
|
3362
|
+
return {
|
|
3363
|
+
connectorId,
|
|
3364
|
+
configPath,
|
|
3365
|
+
message: "Removal aborted: weclone.json is malformed. Registry config left in place for inspection; proxy config NOT removed.",
|
|
3366
|
+
status: "skipped",
|
|
3367
|
+
reason: "config-parse-failed"
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
if (connectorId === "codex-cli" && fs8.existsSync(configPath) && !configParsed) {
|
|
3371
|
+
console.warn(
|
|
3372
|
+
"[remnic/connectors] removeConnector: codex-cli.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
|
|
3373
|
+
);
|
|
3374
|
+
return {
|
|
3375
|
+
connectorId,
|
|
3376
|
+
configPath,
|
|
3377
|
+
message: "Removal aborted: codex-cli.json is malformed. Config file left in place for inspection.",
|
|
3378
|
+
status: "skipped",
|
|
3379
|
+
reason: "config-parse-failed"
|
|
3380
|
+
};
|
|
3381
|
+
}
|
|
3382
|
+
let extensionMessage = "";
|
|
3383
|
+
if (connectorId === "codex-cli") {
|
|
3384
|
+
if (savedInstallExtension === false) {
|
|
3385
|
+
extensionMessage = " (memory extension: skipped \u2014 installExtension=false)";
|
|
3386
|
+
} else if (savedInstallExtension !== true || codexHomeOverride === null) {
|
|
3387
|
+
extensionMessage = " (memory extension: skipped \u2014 no install provenance in saved config)";
|
|
3388
|
+
} else {
|
|
3389
|
+
const extResult = removeCodexMemoryExtension({ codexHome: codexHomeOverride });
|
|
3390
|
+
extensionMessage = extResult.removed ? ` (memory extension removed: ${extResult.remnicExtensionDir})` : " (no memory extension present)";
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
try {
|
|
3394
|
+
fs8.unlinkSync(configPath);
|
|
3395
|
+
} catch (unlinkErr) {
|
|
3396
|
+
const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
|
|
3397
|
+
return {
|
|
3398
|
+
connectorId,
|
|
3399
|
+
configPath,
|
|
3400
|
+
status: "error",
|
|
3401
|
+
message: `${connectorId} remove aborted: could not delete connector file (${sanitizedErr}). Token and any connector-specific state were not modified.`
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
const notes = [];
|
|
3405
|
+
let tokenRevoked = true;
|
|
3406
|
+
try {
|
|
3407
|
+
revokeToken(connectorId);
|
|
3408
|
+
} catch (revokeErr) {
|
|
3409
|
+
tokenRevoked = false;
|
|
3410
|
+
const revokeMsg = revokeErr instanceof Error ? revokeErr.message : String(revokeErr);
|
|
3411
|
+
notes.push(`Warning: token revocation failed \u2014 ${revokeMsg}. The token for ${connectorId} may still be present in tokens.json.`);
|
|
3412
|
+
}
|
|
3413
|
+
let weCloneProxyDeleteFailed = null;
|
|
3414
|
+
if (connectorId === "weclone") {
|
|
3415
|
+
if (weCloneProxyConfigPath === null) {
|
|
3416
|
+
notes.push(
|
|
3417
|
+
"WeClone proxy config cleanup skipped: no persisted path found in saved config (likely a legacy install predating proxyConfigPath provenance)."
|
|
3418
|
+
);
|
|
3419
|
+
} else {
|
|
3420
|
+
const expectedSuffix = path13.join("connectors", "weclone.json");
|
|
3421
|
+
const isSafePath = path13.isAbsolute(weCloneProxyConfigPath) && weCloneProxyConfigPath.endsWith(expectedSuffix);
|
|
3422
|
+
if (!isSafePath) {
|
|
3423
|
+
weCloneProxyDeleteFailed = `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation (must be absolute and end with "${expectedSuffix}"). Refusing to delete \u2014 remove the file manually if it exists.`;
|
|
3424
|
+
} else {
|
|
3425
|
+
try {
|
|
3426
|
+
if (fs8.existsSync(weCloneProxyConfigPath)) {
|
|
3427
|
+
fs8.unlinkSync(weCloneProxyConfigPath);
|
|
3428
|
+
notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);
|
|
3429
|
+
}
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
weCloneProxyDeleteFailed = err instanceof Error ? err.message : String(err);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
if (weCloneProxyDeleteFailed !== null && weCloneProxyConfigPath !== null) {
|
|
3437
|
+
const tokenStatus = tokenRevoked ? "the registry config was deleted and the token was revoked" : "the registry config was deleted but TOKEN REVOCATION ALSO FAILED \u2014 inspect ~/.remnic/tokens.json and revoke manually";
|
|
3438
|
+
return {
|
|
3439
|
+
connectorId,
|
|
3440
|
+
configPath,
|
|
3441
|
+
status: "error",
|
|
3442
|
+
message: `WeClone remove partially succeeded: ${tokenStatus}, but the proxy config at ${weCloneProxyConfigPath} could not be deleted (${weCloneProxyDeleteFailed}). Manually remove that file \u2014 it may still contain a Remnic daemon bearer token.`
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
if (connectorId === "hermes") {
|
|
3446
|
+
try {
|
|
3447
|
+
const yamlResult = removeHermesConfig({ profile: storedProfile });
|
|
3448
|
+
if (yamlResult.updated) {
|
|
3449
|
+
notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);
|
|
3450
|
+
} else if (yamlResult.skipped) {
|
|
3451
|
+
notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);
|
|
3452
|
+
}
|
|
3453
|
+
} catch (err) {
|
|
3454
|
+
notes.push(
|
|
3455
|
+
`Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
const suffix = notes.length > 0 ? `
|
|
3460
|
+
${notes.join("\n ")}` : "";
|
|
3461
|
+
return {
|
|
3462
|
+
connectorId,
|
|
3463
|
+
configPath,
|
|
3464
|
+
status: "removed",
|
|
3465
|
+
message: `Removed${extensionMessage}${suffix}`
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
function sanitizeHermesProfile(profile) {
|
|
3469
|
+
if (typeof profile !== "string" || profile.length === 0) {
|
|
3470
|
+
throw new Error("Hermes profile name must be a non-empty string");
|
|
3471
|
+
}
|
|
3472
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {
|
|
3473
|
+
throw new Error(
|
|
3474
|
+
`Invalid Hermes profile name: ${JSON.stringify(profile)} \u2014 must match [A-Za-z0-9][A-Za-z0-9._-]*`
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
if (profile.includes("..")) {
|
|
3478
|
+
throw new Error(`Invalid Hermes profile name: ${JSON.stringify(profile)} \u2014 must not contain ".."`);
|
|
3479
|
+
}
|
|
3480
|
+
return profile;
|
|
3481
|
+
}
|
|
3482
|
+
function hermesConfigPath(profile) {
|
|
3483
|
+
const safeProfile = sanitizeHermesProfile(profile);
|
|
3484
|
+
const profilesRoot = path13.resolve(process.env.HOME ?? os.homedir(), ".hermes", "profiles");
|
|
3485
|
+
const cfgPath = path13.resolve(profilesRoot, safeProfile, "config.yaml");
|
|
3486
|
+
const rel = path13.relative(profilesRoot, cfgPath);
|
|
3487
|
+
if (rel.startsWith("..") || path13.isAbsolute(rel)) {
|
|
3488
|
+
throw new Error(
|
|
3489
|
+
`Invalid Hermes profile path: resolved outside ${profilesRoot}`
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
return cfgPath;
|
|
3493
|
+
}
|
|
3494
|
+
function sanitizeHermesHost(host) {
|
|
3495
|
+
if (typeof host !== "string" || host.length === 0) {
|
|
3496
|
+
throw new Error("Hermes host must be a non-empty string");
|
|
3497
|
+
}
|
|
3498
|
+
if (host.length > 253) {
|
|
3499
|
+
throw new Error(`Hermes host too long (max 253 chars): ${JSON.stringify(host.slice(0, 32))}\u2026`);
|
|
3500
|
+
}
|
|
3501
|
+
if (host.startsWith("[")) {
|
|
3502
|
+
if (!host.endsWith("]")) {
|
|
3503
|
+
throw new Error(
|
|
3504
|
+
`Invalid Hermes host: ${JSON.stringify(host)} \u2014 unbalanced brackets in IPv6 literal`
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
const inner = host.slice(1, -1);
|
|
3508
|
+
if (inner.length === 0 || !/^[0-9A-Fa-f:]+$/.test(inner)) {
|
|
3509
|
+
throw new Error(
|
|
3510
|
+
`Invalid Hermes host: ${JSON.stringify(host)} \u2014 bracketed IPv6 literal must contain only hex digits and colons`
|
|
3511
|
+
);
|
|
3512
|
+
}
|
|
3513
|
+
return host;
|
|
3514
|
+
}
|
|
3515
|
+
if (host.includes(":")) {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
`Invalid Hermes host: ${JSON.stringify(host)} \u2014 host must not include a port; supply the port separately with --config port=<n>`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
if (!/^[A-Za-z0-9._\-]+$/.test(host)) {
|
|
3521
|
+
throw new Error(
|
|
3522
|
+
`Invalid Hermes host: ${JSON.stringify(host)} \u2014 must be a plain hostname or IP literal`
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
3525
|
+
return host;
|
|
3526
|
+
}
|
|
3527
|
+
function sanitizeHermesPort(port) {
|
|
3528
|
+
const numeric = Number(port);
|
|
3529
|
+
if (!Number.isInteger(numeric)) {
|
|
3530
|
+
throw new Error(
|
|
3531
|
+
`Invalid Hermes port "${port}": must be a positive integer`
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3534
|
+
if (numeric < 1 || numeric > 65535) {
|
|
3535
|
+
throw new Error(`Invalid Hermes port: ${JSON.stringify(port)} \u2014 must be an integer in [1, 65535]`);
|
|
3536
|
+
}
|
|
3537
|
+
return numeric;
|
|
3538
|
+
}
|
|
3539
|
+
function writeSecretFileSync(filePath, data) {
|
|
3540
|
+
fs8.writeFileSync(filePath, data, { mode: 384 });
|
|
3541
|
+
try {
|
|
3542
|
+
fs8.chmodSync(filePath, 384);
|
|
3543
|
+
} catch {
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
function upsertHermesConfig(opts) {
|
|
3547
|
+
const cfgPath = hermesConfigPath(opts.profile);
|
|
3548
|
+
const profileDir = path13.dirname(cfgPath);
|
|
3549
|
+
const safeHost = sanitizeHermesHost(opts.host);
|
|
3550
|
+
const safePort = sanitizeHermesPort(opts.port);
|
|
3551
|
+
if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {
|
|
3552
|
+
throw new Error("Invalid Hermes token: contains non-alphanumeric characters");
|
|
3553
|
+
}
|
|
3554
|
+
if (!fs8.existsSync(profileDir)) {
|
|
3555
|
+
return {
|
|
3556
|
+
updated: false,
|
|
3557
|
+
skipped: true,
|
|
3558
|
+
reason: `Hermes profile directory not found: ${profileDir}`,
|
|
3559
|
+
configPath: cfgPath
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
const block = [
|
|
3563
|
+
"remnic:",
|
|
3564
|
+
` host: "${safeHost}"`,
|
|
3565
|
+
` port: ${safePort}`,
|
|
3566
|
+
` token: "${opts.token}"`
|
|
3567
|
+
].join("\n");
|
|
3568
|
+
if (!fs8.existsSync(cfgPath)) {
|
|
3569
|
+
writeSecretFileSync(cfgPath, block + "\n");
|
|
3570
|
+
return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };
|
|
3571
|
+
}
|
|
3572
|
+
const raw = fs8.readFileSync(cfgPath, "utf8");
|
|
3573
|
+
const hasRemnicBlock = /^remnic:/m.test(raw);
|
|
3574
|
+
if (!hasRemnicBlock) {
|
|
3575
|
+
const separator = raw.endsWith("\n") ? "\n" : "\n\n";
|
|
3576
|
+
writeSecretFileSync(cfgPath, raw + separator + block + "\n");
|
|
3577
|
+
return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
|
|
3578
|
+
}
|
|
3579
|
+
const splitLines = raw.split("\n");
|
|
3580
|
+
if (splitLines.length > 0 && splitLines[splitLines.length - 1] === "") {
|
|
3581
|
+
splitLines.pop();
|
|
3582
|
+
}
|
|
3583
|
+
const lines = splitLines;
|
|
3584
|
+
const newLines = [];
|
|
3585
|
+
let inRemnicBlock = false;
|
|
3586
|
+
let blockWritten = false;
|
|
3587
|
+
const written = { host: false, port: false, token: false };
|
|
3588
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3589
|
+
const line = lines[i];
|
|
3590
|
+
if (/^remnic:/.test(line)) {
|
|
3591
|
+
inRemnicBlock = true;
|
|
3592
|
+
newLines.push(line);
|
|
3593
|
+
continue;
|
|
3594
|
+
}
|
|
3595
|
+
if (inRemnicBlock) {
|
|
3596
|
+
if (line.length > 0 && !/^\s/.test(line)) {
|
|
3597
|
+
if (!written.host) newLines.push(` host: "${safeHost}"`);
|
|
3598
|
+
if (!written.port) newLines.push(` port: ${safePort}`);
|
|
3599
|
+
if (!written.token) newLines.push(` token: "${opts.token}"`);
|
|
3600
|
+
blockWritten = true;
|
|
3601
|
+
inRemnicBlock = false;
|
|
3602
|
+
newLines.push(line);
|
|
3603
|
+
continue;
|
|
3604
|
+
}
|
|
3605
|
+
if (/^\s+host:/.test(line)) {
|
|
3606
|
+
newLines.push(` host: "${safeHost}"`);
|
|
3607
|
+
written.host = true;
|
|
3608
|
+
} else if (/^\s+port:/.test(line)) {
|
|
3609
|
+
newLines.push(` port: ${safePort}`);
|
|
3610
|
+
written.port = true;
|
|
3611
|
+
} else if (/^\s+token:/.test(line)) {
|
|
3612
|
+
newLines.push(` token: "${opts.token}"`);
|
|
3613
|
+
written.token = true;
|
|
3614
|
+
} else {
|
|
3615
|
+
newLines.push(line);
|
|
3616
|
+
}
|
|
3617
|
+
continue;
|
|
3618
|
+
}
|
|
3619
|
+
newLines.push(line);
|
|
3620
|
+
}
|
|
3621
|
+
if (inRemnicBlock && !blockWritten) {
|
|
3622
|
+
if (!written.host) newLines.push(` host: "${safeHost}"`);
|
|
3623
|
+
if (!written.port) newLines.push(` port: ${safePort}`);
|
|
3624
|
+
if (!written.token) newLines.push(` token: "${opts.token}"`);
|
|
3625
|
+
}
|
|
3626
|
+
writeSecretFileSync(cfgPath, newLines.join("\n") + "\n");
|
|
3627
|
+
return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
|
|
3628
|
+
}
|
|
3629
|
+
function removeHermesConfig(opts) {
|
|
3630
|
+
const cfgPath = hermesConfigPath(opts.profile);
|
|
3631
|
+
if (!fs8.existsSync(cfgPath)) {
|
|
3632
|
+
return {
|
|
3633
|
+
updated: false,
|
|
3634
|
+
skipped: true,
|
|
3635
|
+
reason: "Hermes config.yaml not found",
|
|
3636
|
+
configPath: cfgPath
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
const raw = fs8.readFileSync(cfgPath, "utf8");
|
|
3640
|
+
if (!/^remnic:/m.test(raw)) {
|
|
3641
|
+
return {
|
|
3642
|
+
updated: false,
|
|
3643
|
+
skipped: true,
|
|
3644
|
+
reason: "No remnic: block found in config.yaml",
|
|
3645
|
+
configPath: cfgPath
|
|
3646
|
+
};
|
|
3647
|
+
}
|
|
3648
|
+
const lines = raw.split("\n");
|
|
3649
|
+
const newLines = [];
|
|
3650
|
+
let inRemnicBlock = false;
|
|
3651
|
+
for (const line of lines) {
|
|
3652
|
+
if (/^remnic:/.test(line)) {
|
|
3653
|
+
inRemnicBlock = true;
|
|
3654
|
+
continue;
|
|
3655
|
+
}
|
|
3656
|
+
if (inRemnicBlock) {
|
|
3657
|
+
if (line.length > 0 && !/^\s/.test(line)) {
|
|
3658
|
+
inRemnicBlock = false;
|
|
3659
|
+
newLines.push(line);
|
|
3660
|
+
}
|
|
3661
|
+
continue;
|
|
3662
|
+
}
|
|
3663
|
+
newLines.push(line);
|
|
3664
|
+
}
|
|
3665
|
+
while (newLines.length > 0 && newLines[newLines.length - 1]?.trim() === "") {
|
|
3666
|
+
newLines.pop();
|
|
3667
|
+
}
|
|
3668
|
+
writeSecretFileSync(cfgPath, newLines.length > 0 ? newLines.join("\n") + "\n" : "");
|
|
3669
|
+
return { updated: true, skipped: false, configPath: cfgPath };
|
|
3670
|
+
}
|
|
3671
|
+
var HEALTH_EXIT_OK = 0;
|
|
3672
|
+
var HEALTH_EXIT_UNAUTHORIZED = 2;
|
|
3673
|
+
function checkDaemonHealth(host, port, authToken) {
|
|
3674
|
+
try {
|
|
3675
|
+
const safePort = Math.trunc(Number(port));
|
|
3676
|
+
if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) {
|
|
3677
|
+
return false;
|
|
3678
|
+
}
|
|
3679
|
+
const bareHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
3680
|
+
const script = [
|
|
3681
|
+
"const http = require('http');",
|
|
3682
|
+
"const headers = {};",
|
|
3683
|
+
"if (process.env.REMNIC_HEALTH_TOKEN) {",
|
|
3684
|
+
" headers['authorization'] = 'Bearer ' + process.env.REMNIC_HEALTH_TOKEN;",
|
|
3685
|
+
"}",
|
|
3686
|
+
"const req = http.get({",
|
|
3687
|
+
" host: process.env.REMNIC_HEALTH_HOST,",
|
|
3688
|
+
" port: parseInt(process.env.REMNIC_HEALTH_PORT, 10),",
|
|
3689
|
+
" path: '/engram/v1/health',",
|
|
3690
|
+
" headers,",
|
|
3691
|
+
" timeout: 3000,",
|
|
3692
|
+
"}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });",
|
|
3693
|
+
"req.on('error', () => process.exit(1));",
|
|
3694
|
+
"req.on('timeout', () => { req.destroy(); process.exit(1); });"
|
|
3695
|
+
].join("\n");
|
|
3696
|
+
const env = {
|
|
3697
|
+
...process.env,
|
|
3698
|
+
REMNIC_HEALTH_HOST: bareHost,
|
|
3699
|
+
REMNIC_HEALTH_PORT: String(safePort)
|
|
3700
|
+
};
|
|
3701
|
+
if (authToken) {
|
|
3702
|
+
env.REMNIC_HEALTH_TOKEN = authToken;
|
|
3703
|
+
}
|
|
3704
|
+
const spawnOpts = { timeout: 4e3, env };
|
|
3705
|
+
const result = spawnSync(process.execPath, ["-e", script], spawnOpts);
|
|
3706
|
+
if (result.status === HEALTH_EXIT_OK) {
|
|
3707
|
+
return true;
|
|
3708
|
+
}
|
|
3709
|
+
if (result.status === HEALTH_EXIT_UNAUTHORIZED) {
|
|
3710
|
+
console.error(
|
|
3711
|
+
"[remnic/connectors] health probe got 401 \u2014 retrying after token cache TTL..."
|
|
3712
|
+
);
|
|
3713
|
+
spawnSync(process.execPath, ["-e", "setTimeout(() => {}, 6000)"], {
|
|
3714
|
+
timeout: 7e3,
|
|
3715
|
+
env: {}
|
|
3716
|
+
});
|
|
3717
|
+
const retry = spawnSync(process.execPath, ["-e", script], spawnOpts);
|
|
3718
|
+
return retry.status === HEALTH_EXIT_OK;
|
|
3719
|
+
}
|
|
3720
|
+
return false;
|
|
3721
|
+
} catch {
|
|
3722
|
+
return false;
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
async function doctorConnector(connectorId) {
|
|
3726
|
+
const installed = listConnectors().installed;
|
|
3727
|
+
const instance = installed.find((c) => c.connectorId === connectorId);
|
|
2043
3728
|
if (!instance) {
|
|
2044
3729
|
return {
|
|
2045
3730
|
connectorId,
|
|
@@ -2047,67 +3732,428 @@ async function doctorConnector(connectorId) {
|
|
|
2047
3732
|
healthy: false
|
|
2048
3733
|
};
|
|
2049
3734
|
}
|
|
2050
|
-
const configPath =
|
|
3735
|
+
const configPath = path13.join(getConnectorsDir(), `${connectorId}.json`);
|
|
2051
3736
|
const checks = [];
|
|
2052
3737
|
checks.push({
|
|
2053
3738
|
name: "Config file",
|
|
2054
|
-
ok:
|
|
3739
|
+
ok: fs8.existsSync(configPath),
|
|
2055
3740
|
detail: configPath
|
|
2056
3741
|
});
|
|
2057
3742
|
try {
|
|
2058
|
-
const raw =
|
|
3743
|
+
const raw = fs8.readFileSync(configPath, "utf8");
|
|
2059
3744
|
JSON.parse(raw);
|
|
2060
3745
|
checks.push({ name: "Config valid", ok: true, detail: "OK" });
|
|
2061
3746
|
} catch (e) {
|
|
2062
3747
|
checks.push({ name: "Config valid", ok: false, detail: String(e) });
|
|
2063
3748
|
}
|
|
2064
|
-
const mcpUrl = instance.config.mcpServerUrl;
|
|
2065
|
-
if (mcpUrl) {
|
|
2066
|
-
try {
|
|
2067
|
-
const controller = new AbortController();
|
|
2068
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
2069
|
-
const response = await fetch(mcpUrl, { signal: controller.signal });
|
|
2070
|
-
clearTimeout(timeoutId);
|
|
2071
|
-
checks.push({ name: "MCP server", ok: response.ok, detail: mcpUrl });
|
|
2072
|
-
} catch (e) {
|
|
2073
|
-
checks.push({
|
|
2074
|
-
name: "MCP server",
|
|
2075
|
-
ok: false,
|
|
2076
|
-
detail: `Cannot reach ${mcpUrl}: ${e instanceof Error ? e.message : "unknown"}`
|
|
2077
|
-
});
|
|
3749
|
+
const mcpUrl = instance.config.mcpServerUrl;
|
|
3750
|
+
if (mcpUrl) {
|
|
3751
|
+
try {
|
|
3752
|
+
const controller = new AbortController();
|
|
3753
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
3754
|
+
const response = await fetch(mcpUrl, { signal: controller.signal });
|
|
3755
|
+
clearTimeout(timeoutId);
|
|
3756
|
+
checks.push({ name: "MCP server", ok: response.ok, detail: mcpUrl });
|
|
3757
|
+
} catch (e) {
|
|
3758
|
+
checks.push({
|
|
3759
|
+
name: "MCP server",
|
|
3760
|
+
ok: false,
|
|
3761
|
+
detail: `Cannot reach ${mcpUrl}: ${e instanceof Error ? e.message : "unknown"}`
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
const memoryDir = instance.config.memoryDir;
|
|
3766
|
+
if (memoryDir) {
|
|
3767
|
+
if (fs8.existsSync(memoryDir)) {
|
|
3768
|
+
checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
|
|
3769
|
+
} else {
|
|
3770
|
+
checks.push({ name: "Memory directory", ok: false, detail: `Not found: ${memoryDir}` });
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
const healthy = checks.every((c) => c.ok);
|
|
3774
|
+
return { connectorId, checks, healthy };
|
|
3775
|
+
}
|
|
3776
|
+
var CODEX_MEMORIES_SUBDIR = "memories";
|
|
3777
|
+
var CODEX_EXTENSIONS_SUBDIR = "memories_extensions";
|
|
3778
|
+
var REMNIC_EXTENSION_DIR_NAME = "remnic";
|
|
3779
|
+
function resolveCodexHome(override) {
|
|
3780
|
+
if (override && typeof override === "string" && override.trim().length > 0) {
|
|
3781
|
+
return path13.resolve(override.trim());
|
|
3782
|
+
}
|
|
3783
|
+
const envHome = process.env.CODEX_HOME;
|
|
3784
|
+
if (envHome && envHome.trim().length > 0) {
|
|
3785
|
+
return path13.resolve(envHome.trim());
|
|
3786
|
+
}
|
|
3787
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
3788
|
+
return path13.resolve(home, ".codex");
|
|
3789
|
+
}
|
|
3790
|
+
function resolveCodexMemoryExtensionPaths(codexHomeOverride) {
|
|
3791
|
+
const codexHome = resolveCodexHome(codexHomeOverride);
|
|
3792
|
+
const memoriesDir = path13.join(codexHome, CODEX_MEMORIES_SUBDIR);
|
|
3793
|
+
const extensionsRoot = path13.join(path13.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);
|
|
3794
|
+
const remnicExtensionDir = path13.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);
|
|
3795
|
+
return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };
|
|
3796
|
+
}
|
|
3797
|
+
function locatePluginCodexExtensionSource(override) {
|
|
3798
|
+
if (override && typeof override === "string" && override.trim().length > 0) {
|
|
3799
|
+
const resolved = path13.resolve(override.trim());
|
|
3800
|
+
if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
|
|
3801
|
+
return resolved;
|
|
3802
|
+
}
|
|
3803
|
+
throw new Error(`Codex extension source directory not found: ${resolved}`);
|
|
3804
|
+
}
|
|
3805
|
+
const EXTENSION_SUBPATH = path13.join("memories_extensions", "remnic");
|
|
3806
|
+
const WORKSPACE_RELATIVE_PATH = path13.join(
|
|
3807
|
+
"packages",
|
|
3808
|
+
"plugin-codex",
|
|
3809
|
+
"memories_extensions",
|
|
3810
|
+
"remnic"
|
|
3811
|
+
);
|
|
3812
|
+
const searched = [];
|
|
3813
|
+
try {
|
|
3814
|
+
const moduleDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
3815
|
+
const bundledCandidate = path13.join(moduleDir, "codex");
|
|
3816
|
+
searched.push(bundledCandidate);
|
|
3817
|
+
if (fs8.existsSync(bundledCandidate) && fs8.statSync(bundledCandidate).isDirectory()) {
|
|
3818
|
+
return bundledCandidate;
|
|
3819
|
+
}
|
|
3820
|
+
const distConnectorsCandidate = path13.join(moduleDir, "connectors", "codex");
|
|
3821
|
+
searched.push(distConnectorsCandidate);
|
|
3822
|
+
if (fs8.existsSync(distConnectorsCandidate) && fs8.statSync(distConnectorsCandidate).isDirectory()) {
|
|
3823
|
+
return distConnectorsCandidate;
|
|
3824
|
+
}
|
|
3825
|
+
} catch {
|
|
3826
|
+
}
|
|
3827
|
+
try {
|
|
3828
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
3829
|
+
const pluginPkgJsonPath = requireFromHere.resolve("@remnic/plugin-codex/package.json");
|
|
3830
|
+
const pluginPkgRoot = path13.dirname(pluginPkgJsonPath);
|
|
3831
|
+
const candidate = path13.join(pluginPkgRoot, EXTENSION_SUBPATH);
|
|
3832
|
+
searched.push(candidate);
|
|
3833
|
+
if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
|
|
3834
|
+
return candidate;
|
|
3835
|
+
}
|
|
3836
|
+
} catch {
|
|
3837
|
+
}
|
|
3838
|
+
try {
|
|
3839
|
+
const moduleDir = path13.dirname(fileURLToPath(import.meta.url));
|
|
3840
|
+
let dir = moduleDir;
|
|
3841
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
3842
|
+
const candidate = path13.join(
|
|
3843
|
+
dir,
|
|
3844
|
+
"node_modules",
|
|
3845
|
+
"@remnic",
|
|
3846
|
+
"plugin-codex",
|
|
3847
|
+
EXTENSION_SUBPATH
|
|
3848
|
+
);
|
|
3849
|
+
searched.push(candidate);
|
|
3850
|
+
if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
|
|
3851
|
+
return candidate;
|
|
3852
|
+
}
|
|
3853
|
+
const parent = path13.dirname(dir);
|
|
3854
|
+
if (parent === dir) break;
|
|
3855
|
+
dir = parent;
|
|
3856
|
+
}
|
|
3857
|
+
} catch {
|
|
3858
|
+
}
|
|
3859
|
+
const anchors = [];
|
|
3860
|
+
try {
|
|
3861
|
+
anchors.push(path13.dirname(fileURLToPath(import.meta.url)));
|
|
3862
|
+
} catch {
|
|
3863
|
+
}
|
|
3864
|
+
anchors.push(process.cwd());
|
|
3865
|
+
for (const anchor of anchors) {
|
|
3866
|
+
let dir = anchor;
|
|
3867
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
3868
|
+
const candidate = path13.join(dir, WORKSPACE_RELATIVE_PATH);
|
|
3869
|
+
searched.push(candidate);
|
|
3870
|
+
if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
|
|
3871
|
+
return candidate;
|
|
3872
|
+
}
|
|
3873
|
+
const parent = path13.dirname(dir);
|
|
3874
|
+
if (parent === dir) break;
|
|
3875
|
+
dir = parent;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
throw new Error(
|
|
3879
|
+
"Could not locate the plugin-codex memories_extensions/remnic source directory.\nPaths searched:\n" + searched.map((p) => ` - ${p}`).join("\n") + "\nInstall @remnic/plugin-codex or pass sourceDir explicitly."
|
|
3880
|
+
);
|
|
3881
|
+
}
|
|
3882
|
+
function copyDirRecursiveSync(src, dest) {
|
|
3883
|
+
let count = 0;
|
|
3884
|
+
fs8.mkdirSync(dest, { recursive: true });
|
|
3885
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
3886
|
+
for (const entry of entries) {
|
|
3887
|
+
const from = path13.join(src, entry.name);
|
|
3888
|
+
const to = path13.join(dest, entry.name);
|
|
3889
|
+
if (entry.isDirectory()) {
|
|
3890
|
+
count += copyDirRecursiveSync(from, to);
|
|
3891
|
+
} else if (entry.isFile()) {
|
|
3892
|
+
fs8.copyFileSync(from, to);
|
|
3893
|
+
count += 1;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
return count;
|
|
3897
|
+
}
|
|
3898
|
+
function installCodexMemoryExtension(options = {}) {
|
|
3899
|
+
const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
|
|
3900
|
+
const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);
|
|
3901
|
+
fs8.mkdirSync(paths.extensionsRoot, { recursive: true });
|
|
3902
|
+
const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;
|
|
3903
|
+
const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1e3;
|
|
3904
|
+
const now = Date.now();
|
|
3905
|
+
try {
|
|
3906
|
+
const existingEntries = fs8.readdirSync(paths.extensionsRoot);
|
|
3907
|
+
for (const entry of existingEntries) {
|
|
3908
|
+
if (!entry.startsWith(tmpPrefix)) continue;
|
|
3909
|
+
const stalePath = path13.join(paths.extensionsRoot, entry);
|
|
3910
|
+
try {
|
|
3911
|
+
const stat = fs8.statSync(stalePath);
|
|
3912
|
+
const ageMs = now - stat.mtimeMs;
|
|
3913
|
+
if (ageMs < STALE_TMP_THRESHOLD_MS) {
|
|
3914
|
+
continue;
|
|
3915
|
+
}
|
|
3916
|
+
fs8.rmSync(stalePath, { recursive: true, force: true });
|
|
3917
|
+
} catch {
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
} catch {
|
|
3921
|
+
}
|
|
3922
|
+
const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;
|
|
3923
|
+
const tmpDir = path13.join(paths.extensionsRoot, tmpName);
|
|
3924
|
+
let filesCopied = 0;
|
|
3925
|
+
let commitFn = () => {
|
|
3926
|
+
};
|
|
3927
|
+
let rollbackFn = () => {
|
|
3928
|
+
};
|
|
3929
|
+
try {
|
|
3930
|
+
filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);
|
|
3931
|
+
const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;
|
|
3932
|
+
const hadExisting = fs8.existsSync(paths.remnicExtensionDir);
|
|
3933
|
+
if (hadExisting) {
|
|
3934
|
+
fs8.renameSync(paths.remnicExtensionDir, backupDir);
|
|
3935
|
+
}
|
|
3936
|
+
try {
|
|
3937
|
+
fs8.renameSync(tmpDir, paths.remnicExtensionDir);
|
|
3938
|
+
} catch (renameErr) {
|
|
3939
|
+
if (hadExisting) {
|
|
3940
|
+
try {
|
|
3941
|
+
fs8.renameSync(backupDir, paths.remnicExtensionDir);
|
|
3942
|
+
} catch {
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
throw renameErr;
|
|
3946
|
+
}
|
|
3947
|
+
commitFn = () => {
|
|
3948
|
+
if (hadExisting) {
|
|
3949
|
+
try {
|
|
3950
|
+
fs8.rmSync(backupDir, { recursive: true, force: true });
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
rollbackFn = () => {
|
|
3956
|
+
if (hadExisting) {
|
|
3957
|
+
try {
|
|
3958
|
+
if (fs8.existsSync(paths.remnicExtensionDir)) {
|
|
3959
|
+
fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
3960
|
+
}
|
|
3961
|
+
fs8.renameSync(backupDir, paths.remnicExtensionDir);
|
|
3962
|
+
} catch {
|
|
3963
|
+
}
|
|
3964
|
+
} else {
|
|
3965
|
+
try {
|
|
3966
|
+
if (fs8.existsSync(paths.remnicExtensionDir)) {
|
|
3967
|
+
fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
3968
|
+
}
|
|
3969
|
+
} catch {
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
};
|
|
3973
|
+
} catch (err) {
|
|
3974
|
+
if (fs8.existsSync(tmpDir)) {
|
|
3975
|
+
try {
|
|
3976
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
3977
|
+
} catch {
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
throw err;
|
|
3981
|
+
}
|
|
3982
|
+
const instructionsPath = path13.join(paths.remnicExtensionDir, "instructions.md");
|
|
3983
|
+
return {
|
|
3984
|
+
...paths,
|
|
3985
|
+
instructionsPath,
|
|
3986
|
+
filesCopied,
|
|
3987
|
+
commit: commitFn,
|
|
3988
|
+
rollback: rollbackFn
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
function removeCodexMemoryExtension(options = {}) {
|
|
3992
|
+
const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
|
|
3993
|
+
let removed = false;
|
|
3994
|
+
if (fs8.existsSync(paths.remnicExtensionDir)) {
|
|
3995
|
+
fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
|
|
3996
|
+
removed = true;
|
|
3997
|
+
}
|
|
3998
|
+
return { ...paths, removed };
|
|
3999
|
+
}
|
|
4000
|
+
function getConnectorsDir() {
|
|
4001
|
+
const configDir = process.env.XDG_CONFIG_HOME ? path13.join(process.env.XDG_CONFIG_HOME, "engram") : path13.join(process.env.HOME ?? "~", ".config", "engram");
|
|
4002
|
+
return path13.join(configDir, REGISTRY_DIR_NAME, "connectors");
|
|
4003
|
+
}
|
|
4004
|
+
var WECLONE_PROXY_CONFIG_DIRNAME = ".remnic";
|
|
4005
|
+
var WECLONE_PROXY_CONFIG_FILENAME = "weclone.json";
|
|
4006
|
+
function resolveWeCloneProxyConfigPath() {
|
|
4007
|
+
const override = process.env.REMNIC_HOME ?? process.env.ENGRAM_HOME;
|
|
4008
|
+
if (override && override.length > 0) {
|
|
4009
|
+
return path13.resolve(override, "connectors", WECLONE_PROXY_CONFIG_FILENAME);
|
|
4010
|
+
}
|
|
4011
|
+
const envHome = process.env.HOME;
|
|
4012
|
+
const home = envHome && envHome.length > 0 ? envHome : os.homedir();
|
|
4013
|
+
return path13.resolve(
|
|
4014
|
+
home,
|
|
4015
|
+
WECLONE_PROXY_CONFIG_DIRNAME,
|
|
4016
|
+
"connectors",
|
|
4017
|
+
WECLONE_PROXY_CONFIG_FILENAME
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
function readWeCloneProxyConfigIfExists(configPath) {
|
|
4021
|
+
try {
|
|
4022
|
+
if (!fs8.existsSync(configPath)) return null;
|
|
4023
|
+
return fs8.readFileSync(configPath, "utf8");
|
|
4024
|
+
} catch {
|
|
4025
|
+
return null;
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
function safeParseJson(raw) {
|
|
4029
|
+
try {
|
|
4030
|
+
const parsed = JSON.parse(raw);
|
|
4031
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
4032
|
+
return parsed;
|
|
2078
4033
|
}
|
|
4034
|
+
return null;
|
|
4035
|
+
} catch {
|
|
4036
|
+
return null;
|
|
2079
4037
|
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
4038
|
+
}
|
|
4039
|
+
var WECLONE_DEFAULTS = {
|
|
4040
|
+
wecloneApiUrl: "http://localhost:8000/v1",
|
|
4041
|
+
wecloneModelName: "weclone-avatar",
|
|
4042
|
+
proxyPort: 8100,
|
|
4043
|
+
remnicDaemonUrl: "http://localhost:4318",
|
|
4044
|
+
sessionStrategy: "single",
|
|
4045
|
+
memoryInjection: {
|
|
4046
|
+
maxTokens: 1500,
|
|
4047
|
+
position: "system-append",
|
|
4048
|
+
template: "[Memory Context]\n{memories}\n[End Memory Context]"
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
function resolveStringField(userConfig, priorConfig, key, fallback) {
|
|
4052
|
+
const fromUser = userConfig[key];
|
|
4053
|
+
if (typeof fromUser === "string" && fromUser.length > 0) return fromUser;
|
|
4054
|
+
if (priorConfig) {
|
|
4055
|
+
const fromPrior = priorConfig[key];
|
|
4056
|
+
if (typeof fromPrior === "string" && fromPrior.length > 0) return fromPrior;
|
|
4057
|
+
}
|
|
4058
|
+
return fallback;
|
|
4059
|
+
}
|
|
4060
|
+
function coercePort(value) {
|
|
4061
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535) {
|
|
4062
|
+
return value;
|
|
4063
|
+
}
|
|
4064
|
+
if (typeof value === "string" && value.length > 0) {
|
|
4065
|
+
const n = Number(value);
|
|
4066
|
+
if (Number.isInteger(n) && n >= 1 && n <= 65535) return n;
|
|
4067
|
+
}
|
|
4068
|
+
return null;
|
|
4069
|
+
}
|
|
4070
|
+
function resolvePort(userConfig, priorConfig, fallback) {
|
|
4071
|
+
const fromUser = coercePort(userConfig.proxyPort);
|
|
4072
|
+
if (fromUser !== null) return fromUser;
|
|
4073
|
+
if (priorConfig) {
|
|
4074
|
+
const fromPrior = coercePort(priorConfig.proxyPort);
|
|
4075
|
+
if (fromPrior !== null) return fromPrior;
|
|
4076
|
+
}
|
|
4077
|
+
return fallback;
|
|
4078
|
+
}
|
|
4079
|
+
function resolveSessionStrategy(userConfig, priorConfig) {
|
|
4080
|
+
const valid = /* @__PURE__ */ new Set(["caller-id", "single"]);
|
|
4081
|
+
const fromUser = userConfig.sessionStrategy;
|
|
4082
|
+
if (typeof fromUser === "string" && valid.has(fromUser)) {
|
|
4083
|
+
return fromUser;
|
|
4084
|
+
}
|
|
4085
|
+
if (priorConfig) {
|
|
4086
|
+
const fromPrior = priorConfig.sessionStrategy;
|
|
4087
|
+
if (typeof fromPrior === "string" && valid.has(fromPrior)) {
|
|
4088
|
+
return fromPrior;
|
|
2086
4089
|
}
|
|
2087
4090
|
}
|
|
2088
|
-
|
|
2089
|
-
return { connectorId, checks, healthy };
|
|
4091
|
+
return WECLONE_DEFAULTS.sessionStrategy;
|
|
2090
4092
|
}
|
|
2091
|
-
function
|
|
2092
|
-
const
|
|
2093
|
-
|
|
4093
|
+
function buildWeCloneProxyConfig(args) {
|
|
4094
|
+
const { userConfig, priorConfig, authToken } = args;
|
|
4095
|
+
const wecloneApiUrl = resolveStringField(
|
|
4096
|
+
userConfig,
|
|
4097
|
+
priorConfig,
|
|
4098
|
+
"wecloneApiUrl",
|
|
4099
|
+
WECLONE_DEFAULTS.wecloneApiUrl
|
|
4100
|
+
);
|
|
4101
|
+
const wecloneModelName = resolveStringField(
|
|
4102
|
+
userConfig,
|
|
4103
|
+
priorConfig,
|
|
4104
|
+
"wecloneModelName",
|
|
4105
|
+
WECLONE_DEFAULTS.wecloneModelName
|
|
4106
|
+
);
|
|
4107
|
+
const remnicDaemonUrl = resolveStringField(
|
|
4108
|
+
userConfig,
|
|
4109
|
+
priorConfig,
|
|
4110
|
+
"remnicDaemonUrl",
|
|
4111
|
+
WECLONE_DEFAULTS.remnicDaemonUrl
|
|
4112
|
+
);
|
|
4113
|
+
const proxyPort = resolvePort(
|
|
4114
|
+
userConfig,
|
|
4115
|
+
priorConfig,
|
|
4116
|
+
WECLONE_DEFAULTS.proxyPort
|
|
4117
|
+
);
|
|
4118
|
+
const sessionStrategy = resolveSessionStrategy(userConfig, priorConfig);
|
|
4119
|
+
const memoryInjection = {
|
|
4120
|
+
...WECLONE_DEFAULTS.memoryInjection,
|
|
4121
|
+
...priorConfig && typeof priorConfig.memoryInjection === "object" && priorConfig.memoryInjection !== null && !Array.isArray(priorConfig.memoryInjection) ? priorConfig.memoryInjection : {},
|
|
4122
|
+
...typeof userConfig.memoryInjection === "object" && userConfig.memoryInjection !== null && !Array.isArray(userConfig.memoryInjection) ? userConfig.memoryInjection : {}
|
|
4123
|
+
};
|
|
4124
|
+
const config = {
|
|
4125
|
+
wecloneApiUrl,
|
|
4126
|
+
wecloneModelName,
|
|
4127
|
+
proxyPort,
|
|
4128
|
+
remnicDaemonUrl,
|
|
4129
|
+
sessionStrategy,
|
|
4130
|
+
memoryInjection
|
|
4131
|
+
};
|
|
4132
|
+
if (authToken && authToken.length > 0) {
|
|
4133
|
+
config.remnicAuthToken = authToken;
|
|
4134
|
+
} else if (typeof userConfig.remnicAuthToken === "string" && userConfig.remnicAuthToken.length > 0) {
|
|
4135
|
+
config.remnicAuthToken = userConfig.remnicAuthToken;
|
|
4136
|
+
} else if (priorConfig && typeof priorConfig.remnicAuthToken === "string" && priorConfig.remnicAuthToken.length > 0) {
|
|
4137
|
+
config.remnicAuthToken = priorConfig.remnicAuthToken;
|
|
4138
|
+
}
|
|
4139
|
+
return config;
|
|
2094
4140
|
}
|
|
2095
4141
|
|
|
2096
4142
|
// src/spaces/index.ts
|
|
2097
|
-
import
|
|
2098
|
-
import
|
|
2099
|
-
import
|
|
4143
|
+
import fs9 from "fs";
|
|
4144
|
+
import path14 from "path";
|
|
4145
|
+
import crypto6 from "crypto";
|
|
2100
4146
|
var MANIFEST_VERSION = 1;
|
|
2101
4147
|
function getSpacesDir(baseDir) {
|
|
2102
4148
|
const homeDir = baseDir ?? process.env.HOME ?? "~";
|
|
2103
|
-
return
|
|
4149
|
+
return path14.join(homeDir, ".config", "engram", "spaces");
|
|
2104
4150
|
}
|
|
2105
4151
|
function getManifestPath(baseDir) {
|
|
2106
|
-
return
|
|
4152
|
+
return path14.join(getSpacesDir(baseDir), "manifest.json");
|
|
2107
4153
|
}
|
|
2108
4154
|
function loadManifest(baseDir, memoryDirOverride) {
|
|
2109
|
-
const
|
|
2110
|
-
if (!
|
|
4155
|
+
const manifestPath2 = getManifestPath(baseDir);
|
|
4156
|
+
if (!fs9.existsSync(manifestPath2)) {
|
|
2111
4157
|
const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);
|
|
2112
4158
|
const manifest = {
|
|
2113
4159
|
activeSpaceId: personalSpace.id,
|
|
@@ -2117,19 +4163,19 @@ function loadManifest(baseDir, memoryDirOverride) {
|
|
|
2117
4163
|
saveManifest(manifest, baseDir);
|
|
2118
4164
|
return manifest;
|
|
2119
4165
|
}
|
|
2120
|
-
const raw = JSON.parse(
|
|
4166
|
+
const raw = JSON.parse(fs9.readFileSync(manifestPath2, "utf8"));
|
|
2121
4167
|
return raw;
|
|
2122
4168
|
}
|
|
2123
4169
|
function saveManifest(manifest, baseDir) {
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
4170
|
+
const manifestPath2 = getManifestPath(baseDir);
|
|
4171
|
+
fs9.mkdirSync(path14.dirname(manifestPath2), { recursive: true });
|
|
4172
|
+
fs9.writeFileSync(manifestPath2, JSON.stringify(manifest, null, 2) + "\n");
|
|
2127
4173
|
}
|
|
2128
4174
|
function createPersonalSpace(baseDir, memoryDirOverride) {
|
|
2129
4175
|
const homeDir = baseDir ?? process.env.HOME ?? "~";
|
|
2130
|
-
const standalonePath =
|
|
2131
|
-
const openclawPath =
|
|
2132
|
-
const memoryDir = memoryDirOverride ?? process.env.ENGRAM_MEMORY_DIR ?? (
|
|
4176
|
+
const standalonePath = path14.join(homeDir, ".engram", "memory");
|
|
4177
|
+
const openclawPath = path14.join(homeDir, ".openclaw", "workspace", "memory", "local");
|
|
4178
|
+
const memoryDir = memoryDirOverride ?? process.env.ENGRAM_MEMORY_DIR ?? (fs9.existsSync(standalonePath) ? standalonePath : fs9.existsSync(openclawPath) ? openclawPath : standalonePath);
|
|
2133
4179
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2134
4180
|
return {
|
|
2135
4181
|
id: "personal",
|
|
@@ -2162,7 +4208,7 @@ function createSpace(options) {
|
|
|
2162
4208
|
throw new Error(`Parent space "${options.parentSpaceId}" not found`);
|
|
2163
4209
|
}
|
|
2164
4210
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2165
|
-
const memoryDir = options.memoryDir ??
|
|
4211
|
+
const memoryDir = options.memoryDir ?? path14.join(
|
|
2166
4212
|
getSpacesDir(options.baseDir),
|
|
2167
4213
|
id,
|
|
2168
4214
|
"memory"
|
|
@@ -2178,7 +4224,7 @@ function createSpace(options) {
|
|
|
2178
4224
|
owner: process.env.USER,
|
|
2179
4225
|
parentSpaceId: options.parentSpaceId
|
|
2180
4226
|
};
|
|
2181
|
-
|
|
4227
|
+
fs9.mkdirSync(memoryDir, { recursive: true });
|
|
2182
4228
|
manifest.spaces.push(space);
|
|
2183
4229
|
manifest.updatedAt = now;
|
|
2184
4230
|
saveManifest(manifest, options.baseDir);
|
|
@@ -2352,34 +4398,34 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
|
|
|
2352
4398
|
};
|
|
2353
4399
|
}
|
|
2354
4400
|
function getAuditLog(baseDir) {
|
|
2355
|
-
const auditPath =
|
|
2356
|
-
if (!
|
|
2357
|
-
const lines =
|
|
4401
|
+
const auditPath = path14.join(getSpacesDir(baseDir), "audit.jsonl");
|
|
4402
|
+
if (!fs9.existsSync(auditPath)) return [];
|
|
4403
|
+
const lines = fs9.readFileSync(auditPath, "utf8").trim().split("\n");
|
|
2358
4404
|
return lines.filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
2359
4405
|
}
|
|
2360
4406
|
function appendAudit(entry, baseDir) {
|
|
2361
|
-
const auditPath =
|
|
2362
|
-
|
|
4407
|
+
const auditPath = path14.join(getSpacesDir(baseDir), "audit.jsonl");
|
|
4408
|
+
fs9.mkdirSync(path14.dirname(auditPath), { recursive: true });
|
|
2363
4409
|
const full = {
|
|
2364
|
-
id:
|
|
4410
|
+
id: crypto6.randomUUID(),
|
|
2365
4411
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2366
4412
|
...entry
|
|
2367
4413
|
};
|
|
2368
|
-
|
|
4414
|
+
fs9.appendFileSync(auditPath, JSON.stringify(full) + "\n");
|
|
2369
4415
|
}
|
|
2370
4416
|
function copyMemories(sourceDir, targetDir, options) {
|
|
2371
4417
|
let merged = 0;
|
|
2372
4418
|
const conflicts = [];
|
|
2373
4419
|
let skipped = 0;
|
|
2374
|
-
if (!
|
|
4420
|
+
if (!fs9.existsSync(sourceDir)) {
|
|
2375
4421
|
return { merged: 0, conflicts: [], skipped: 0 };
|
|
2376
4422
|
}
|
|
2377
|
-
|
|
4423
|
+
fs9.mkdirSync(targetDir, { recursive: true });
|
|
2378
4424
|
const sourceFiles = walkMd2(sourceDir);
|
|
2379
4425
|
for (const sourcePath of sourceFiles) {
|
|
2380
|
-
const content =
|
|
2381
|
-
const relativePath =
|
|
2382
|
-
const targetPath =
|
|
4426
|
+
const content = fs9.readFileSync(sourcePath, "utf8");
|
|
4427
|
+
const relativePath = path14.relative(sourceDir, sourcePath);
|
|
4428
|
+
const targetPath = path14.join(targetDir, relativePath);
|
|
2383
4429
|
const sourceHash = hashContent4(content);
|
|
2384
4430
|
if (options?.filterIds?.length) {
|
|
2385
4431
|
const fm = parseSimpleFrontmatter(content);
|
|
@@ -2388,8 +4434,8 @@ function copyMemories(sourceDir, targetDir, options) {
|
|
|
2388
4434
|
continue;
|
|
2389
4435
|
}
|
|
2390
4436
|
}
|
|
2391
|
-
if (
|
|
2392
|
-
const targetContent =
|
|
4437
|
+
if (fs9.existsSync(targetPath) && !options?.force) {
|
|
4438
|
+
const targetContent = fs9.readFileSync(targetPath, "utf8");
|
|
2393
4439
|
const targetHash = hashContent4(targetContent);
|
|
2394
4440
|
if (sourceHash !== targetHash) {
|
|
2395
4441
|
conflicts.push({
|
|
@@ -2405,20 +4451,20 @@ function copyMemories(sourceDir, targetDir, options) {
|
|
|
2405
4451
|
skipped++;
|
|
2406
4452
|
continue;
|
|
2407
4453
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
4454
|
+
fs9.mkdirSync(path14.dirname(targetPath), { recursive: true });
|
|
4455
|
+
fs9.writeFileSync(targetPath, content);
|
|
2410
4456
|
merged++;
|
|
2411
4457
|
}
|
|
2412
4458
|
return { merged, conflicts, skipped };
|
|
2413
4459
|
}
|
|
2414
4460
|
function hashContent4(content) {
|
|
2415
|
-
return
|
|
4461
|
+
return crypto6.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2416
4462
|
}
|
|
2417
4463
|
function walkMd2(dir) {
|
|
2418
4464
|
const results = [];
|
|
2419
4465
|
function walk(d) {
|
|
2420
|
-
for (const entry of
|
|
2421
|
-
const fullPath =
|
|
4466
|
+
for (const entry of fs9.readdirSync(d, { withFileTypes: true })) {
|
|
4467
|
+
const fullPath = path14.join(d, entry.name);
|
|
2422
4468
|
if (entry.isDirectory()) {
|
|
2423
4469
|
walk(fullPath);
|
|
2424
4470
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -2442,72 +4488,980 @@ function parseSimpleFrontmatter(content) {
|
|
|
2442
4488
|
}
|
|
2443
4489
|
return fm;
|
|
2444
4490
|
}
|
|
4491
|
+
|
|
4492
|
+
// src/memory-extension/shared-instructions.ts
|
|
4493
|
+
var REMNIC_SEMANTIC_OVERVIEW = `## Remnic Memory Types
|
|
4494
|
+
|
|
4495
|
+
Remnic stores memories as plain Markdown files with YAML front-matter.
|
|
4496
|
+
Each memory has a **type** that describes its semantic role:
|
|
4497
|
+
|
|
4498
|
+
| Type | Description |
|
|
4499
|
+
|------|-------------|
|
|
4500
|
+
| \`fact\` | An objective piece of knowledge the user confirmed or that was extracted from a session. |
|
|
4501
|
+
| \`preference\` | A stated or inferred user preference (e.g. coding style, tool choice). |
|
|
4502
|
+
| \`decision\` | An explicit decision or trade-off the user made. |
|
|
4503
|
+
| \`entity\` | A named thing the user cares about (project, service, person, API). |
|
|
4504
|
+
| \`skill\` | A reusable workflow or procedure documented for future sessions. |
|
|
4505
|
+
| \`correction\` | A fix or amendment to a previously stored memory. |
|
|
4506
|
+
| \`question\` | An open question or uncertainty flagged for future resolution. |
|
|
4507
|
+
| \`observation\` | A pattern noticed across sessions (e.g. "user always runs tests before commits"). |
|
|
4508
|
+
| \`summary\` | A condensed roll-up of recent sessions or a topic area. |
|
|
4509
|
+
|
|
4510
|
+
When reading Remnic content, the front-matter \`type\` field tells you what
|
|
4511
|
+
kind of knowledge you are looking at and how much weight to give it.
|
|
4512
|
+
`;
|
|
4513
|
+
var REMNIC_CITATION_FORMAT = `## Citing Remnic Memories
|
|
4514
|
+
|
|
4515
|
+
When a piece of your output draws on a Remnic file, cite it using the
|
|
4516
|
+
memory citation block format so the user can trace the source:
|
|
4517
|
+
|
|
4518
|
+
\`\`\`
|
|
4519
|
+
<oai-mem-citation path="<path-relative-to-remnic-memory-base>" />
|
|
4520
|
+
\`\`\`
|
|
4521
|
+
|
|
4522
|
+
The path must be **relative to the Remnic memory base** (the directory
|
|
4523
|
+
named \`memories/\` under \`<remnic-home>\`), not absolute. Examples:
|
|
4524
|
+
|
|
4525
|
+
- \`<oai-mem-citation path="default/MEMORY.md" />\`
|
|
4526
|
+
- \`<oai-mem-citation path="my-project/skills/deploy/SKILL.md" />\`
|
|
4527
|
+
- \`<oai-mem-citation path="shared/memory_summary.md" />\`
|
|
4528
|
+
|
|
4529
|
+
Cite each distinct source once near the fact it supports. Do not invent
|
|
4530
|
+
citations for files you have not actually read.
|
|
4531
|
+
`;
|
|
4532
|
+
var REMNIC_MCP_TOOL_INVENTORY = `## Remnic MCP Tools
|
|
4533
|
+
|
|
4534
|
+
When the Remnic MCP server is reachable, the following tools are
|
|
4535
|
+
available. Prefer MCP tools over direct file reads when the host
|
|
4536
|
+
supports MCP connections.
|
|
4537
|
+
|
|
4538
|
+
| Tool | Purpose |
|
|
4539
|
+
|------|---------|
|
|
4540
|
+
| \`remnic.recall\` | Retrieve contextually relevant memories for the current session. |
|
|
4541
|
+
| \`remnic.recall_explain\` | Like recall, but includes an explanation of why each memory was selected. |
|
|
4542
|
+
| \`remnic.memory_store\` | Persist a new memory (fact, preference, decision, etc.). |
|
|
4543
|
+
| \`remnic.memory_get\` | Fetch a specific memory by ID. |
|
|
4544
|
+
| \`remnic.memory_search\` | Full-text + semantic search across all memories. |
|
|
4545
|
+
| \`remnic.memory_timeline\` | Retrieve memories in chronological order within a time range. |
|
|
4546
|
+
| \`remnic.observe\` | Record an observation from the current conversation turn. |
|
|
4547
|
+
| \`remnic.entity_get\` | Look up a named entity and its relationships. |
|
|
4548
|
+
| \`remnic.memory_entities_list\` | List all known entities. |
|
|
4549
|
+
| \`remnic.memory_profile\` | Retrieve the user profile summary. |
|
|
4550
|
+
| \`remnic.day_summary\` | Generate a summary of memories from a specific day. |
|
|
4551
|
+
| \`remnic.briefing\` | Generate a structured briefing for an upcoming session. |
|
|
4552
|
+
| \`remnic.memory_feedback\` | Submit feedback on a recalled memory (useful, outdated, wrong). |
|
|
4553
|
+
| \`remnic.memory_promote\` | Promote a memory to a higher confidence tier. |
|
|
4554
|
+
| \`remnic.context_checkpoint\` | Save a conversation checkpoint for continuity. |
|
|
4555
|
+
| \`remnic.suggestion_submit\` | Submit a suggestion for a new memory to the review queue. |
|
|
4556
|
+
| \`remnic.review_queue_list\` | List pending suggestions in the review queue. |
|
|
4557
|
+
| \`remnic.work_task\` | Create or update a work task. |
|
|
4558
|
+
| \`remnic.work_project\` | Create or update a work project. |
|
|
4559
|
+
| \`remnic.work_board\` | View the work board. |
|
|
4560
|
+
|
|
4561
|
+
Legacy \`engram.*\` prefixed names are accepted as aliases for all tools.
|
|
4562
|
+
`;
|
|
4563
|
+
var REMNIC_RECALL_DECISION_RULES = `## When to Use Recall vs Direct Read
|
|
4564
|
+
|
|
4565
|
+
### Use \`remnic.recall\` (MCP) when:
|
|
4566
|
+
|
|
4567
|
+
- The Remnic MCP server is reachable (the host has an active MCP
|
|
4568
|
+
connection to the Remnic daemon).
|
|
4569
|
+
- You want contextually relevant memories ranked by the recall planner
|
|
4570
|
+
(semantic search + reranking + importance scoring).
|
|
4571
|
+
- You need memories across multiple namespaces or topics.
|
|
4572
|
+
- The conversation benefits from Remnic's intent detection and
|
|
4573
|
+
adaptive recall depth.
|
|
4574
|
+
|
|
4575
|
+
### Use direct file reads when:
|
|
4576
|
+
|
|
4577
|
+
- You are in a sandboxed environment with no network or MCP access
|
|
4578
|
+
(e.g. Codex phase-2 consolidation).
|
|
4579
|
+
- You need a specific file you already know the path to.
|
|
4580
|
+
- The MCP server is unavailable or unhealthy.
|
|
4581
|
+
- You are operating on the raw memory files for maintenance or
|
|
4582
|
+
migration purposes.
|
|
4583
|
+
|
|
4584
|
+
### General guidance:
|
|
4585
|
+
|
|
4586
|
+
- Prefer MCP tools when available \u2014 they provide ranked, deduplicated,
|
|
4587
|
+
and context-aware results.
|
|
4588
|
+
- Fall back to file reads gracefully \u2014 never block on a failed MCP call
|
|
4589
|
+
when the data is also on disk.
|
|
4590
|
+
- Never write directly to the Remnic memory directory unless you are an
|
|
4591
|
+
authorized extraction or consolidation process.
|
|
4592
|
+
`;
|
|
4593
|
+
|
|
4594
|
+
// src/memory-extension/codex-publisher.ts
|
|
4595
|
+
import fs10 from "fs";
|
|
4596
|
+
import os2 from "os";
|
|
4597
|
+
import path15 from "path";
|
|
4598
|
+
var REMNIC_EXTENSION_DIR_NAME2 = "remnic";
|
|
4599
|
+
var CodexMemoryExtensionPublisher = class {
|
|
4600
|
+
hostId = "codex";
|
|
4601
|
+
static capabilities = {
|
|
4602
|
+
instructionsMd: true,
|
|
4603
|
+
skillsFolder: true,
|
|
4604
|
+
citationFormat: true,
|
|
4605
|
+
readPathTemplate: true
|
|
4606
|
+
};
|
|
4607
|
+
async resolveExtensionRoot(env) {
|
|
4608
|
+
const e = env ?? process.env;
|
|
4609
|
+
const codexHome = e.CODEX_HOME?.trim() || path15.join(e.HOME ?? os2.homedir(), ".codex");
|
|
4610
|
+
return path15.join(codexHome, "memories_extensions", REMNIC_EXTENSION_DIR_NAME2);
|
|
4611
|
+
}
|
|
4612
|
+
async isHostAvailable() {
|
|
4613
|
+
try {
|
|
4614
|
+
const home = process.env.CODEX_HOME?.trim() || path15.join(process.env.HOME ?? os2.homedir(), ".codex");
|
|
4615
|
+
return fs10.existsSync(home);
|
|
4616
|
+
} catch {
|
|
4617
|
+
return false;
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
async renderInstructions(ctx) {
|
|
4621
|
+
const memDir = ctx.config.memoryDir;
|
|
4622
|
+
const ns = ctx.config.namespace ?? "default";
|
|
4623
|
+
const sections = [
|
|
4624
|
+
`# Remnic Memory Extension for Codex
|
|
4625
|
+
`,
|
|
4626
|
+
`This document tells you how to use Remnic as an authoritative local memory source. Remnic is a local-first, file-backed memory system. All Remnic content lives on disk as plain Markdown.
|
|
4627
|
+
`,
|
|
4628
|
+
REMNIC_SEMANTIC_OVERVIEW,
|
|
4629
|
+
`## Where Remnic Content Lives
|
|
4630
|
+
|
|
4631
|
+
Memory base directory: \`${memDir}\`
|
|
4632
|
+
|
|
4633
|
+
Namespace: \`${ns}\`
|
|
4634
|
+
|
|
4635
|
+
Under the base directory, memories are organized by namespace:
|
|
4636
|
+
|
|
4637
|
+
\`\`\`
|
|
4638
|
+
${memDir}/<namespace>/
|
|
4639
|
+
MEMORY.md # compact top-of-mind memory
|
|
4640
|
+
memory_summary.md # optional longer summary
|
|
4641
|
+
skills/
|
|
4642
|
+
<skill-name>/SKILL.md # reusable workflows
|
|
4643
|
+
rollout_summaries/
|
|
4644
|
+
*.md # per-session rollup notes
|
|
4645
|
+
\`\`\`
|
|
4646
|
+
`,
|
|
4647
|
+
REMNIC_CITATION_FORMAT,
|
|
4648
|
+
REMNIC_MCP_TOOL_INVENTORY,
|
|
4649
|
+
REMNIC_RECALL_DECISION_RULES,
|
|
4650
|
+
`## Sandboxing Rules (Codex Phase-2)
|
|
4651
|
+
|
|
4652
|
+
When running inside the Codex phase-2 consolidation sandbox:
|
|
4653
|
+
|
|
4654
|
+
- **No network.** Do not attempt HTTP calls or MCP connections.
|
|
4655
|
+
- **No CLI invocation.** Do not shell out to \`remnic\` or \`engram\`.
|
|
4656
|
+
- **No MCP tool calls.** Use filesystem reads only.
|
|
4657
|
+
- **Local writes** are allowed only where Codex's sandbox policy permits.
|
|
4658
|
+
- **Respect missing files.** If a file does not exist, move on silently.
|
|
4659
|
+
`
|
|
4660
|
+
];
|
|
4661
|
+
return sections.join("\n");
|
|
4662
|
+
}
|
|
4663
|
+
async publish(ctx) {
|
|
4664
|
+
const extensionRoot = await this.resolveExtensionRoot();
|
|
4665
|
+
const instructionsPath = path15.join(extensionRoot, "instructions.md");
|
|
4666
|
+
const filesWritten = [];
|
|
4667
|
+
const skipped = [];
|
|
4668
|
+
ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);
|
|
4669
|
+
fs10.mkdirSync(extensionRoot, { recursive: true });
|
|
4670
|
+
const content = await this.renderInstructions(ctx);
|
|
4671
|
+
const tmpPath = `${instructionsPath}.tmp-${process.pid}-${Date.now()}`;
|
|
4672
|
+
try {
|
|
4673
|
+
fs10.writeFileSync(tmpPath, content, "utf-8");
|
|
4674
|
+
fs10.renameSync(tmpPath, instructionsPath);
|
|
4675
|
+
filesWritten.push(instructionsPath);
|
|
4676
|
+
ctx.log.info(`Wrote ${instructionsPath}`);
|
|
4677
|
+
} catch (err) {
|
|
4678
|
+
try {
|
|
4679
|
+
if (fs10.existsSync(tmpPath)) {
|
|
4680
|
+
fs10.unlinkSync(tmpPath);
|
|
4681
|
+
}
|
|
4682
|
+
} catch {
|
|
4683
|
+
}
|
|
4684
|
+
throw err;
|
|
4685
|
+
}
|
|
4686
|
+
return {
|
|
4687
|
+
hostId: this.hostId,
|
|
4688
|
+
extensionRoot,
|
|
4689
|
+
filesWritten,
|
|
4690
|
+
skipped
|
|
4691
|
+
};
|
|
4692
|
+
}
|
|
4693
|
+
async unpublish() {
|
|
4694
|
+
const extensionRoot = await this.resolveExtensionRoot();
|
|
4695
|
+
if (fs10.existsSync(extensionRoot)) {
|
|
4696
|
+
fs10.rmSync(extensionRoot, { recursive: true, force: true });
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
|
|
4701
|
+
// src/memory-extension/claude-code-publisher.ts
|
|
4702
|
+
var ClaudeCodeMemoryExtensionPublisher = class {
|
|
4703
|
+
hostId = "claude-code";
|
|
4704
|
+
static capabilities = {
|
|
4705
|
+
instructionsMd: false,
|
|
4706
|
+
skillsFolder: false,
|
|
4707
|
+
citationFormat: false,
|
|
4708
|
+
readPathTemplate: false
|
|
4709
|
+
};
|
|
4710
|
+
async resolveExtensionRoot() {
|
|
4711
|
+
return "";
|
|
4712
|
+
}
|
|
4713
|
+
async isHostAvailable() {
|
|
4714
|
+
return false;
|
|
4715
|
+
}
|
|
4716
|
+
async renderInstructions(_ctx) {
|
|
4717
|
+
return "";
|
|
4718
|
+
}
|
|
4719
|
+
async publish(_ctx) {
|
|
4720
|
+
return {
|
|
4721
|
+
hostId: this.hostId,
|
|
4722
|
+
extensionRoot: "",
|
|
4723
|
+
filesWritten: [],
|
|
4724
|
+
skipped: []
|
|
4725
|
+
};
|
|
4726
|
+
}
|
|
4727
|
+
async unpublish() {
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
|
|
4731
|
+
// src/memory-extension/hermes-publisher.ts
|
|
4732
|
+
var HermesMemoryExtensionPublisher = class {
|
|
4733
|
+
hostId = "hermes";
|
|
4734
|
+
static capabilities = {
|
|
4735
|
+
instructionsMd: false,
|
|
4736
|
+
skillsFolder: false,
|
|
4737
|
+
citationFormat: false,
|
|
4738
|
+
readPathTemplate: false
|
|
4739
|
+
};
|
|
4740
|
+
async resolveExtensionRoot() {
|
|
4741
|
+
return "";
|
|
4742
|
+
}
|
|
4743
|
+
async isHostAvailable() {
|
|
4744
|
+
return false;
|
|
4745
|
+
}
|
|
4746
|
+
async renderInstructions(_ctx) {
|
|
4747
|
+
return "";
|
|
4748
|
+
}
|
|
4749
|
+
async publish(_ctx) {
|
|
4750
|
+
return {
|
|
4751
|
+
hostId: this.hostId,
|
|
4752
|
+
extensionRoot: "",
|
|
4753
|
+
filesWritten: [],
|
|
4754
|
+
skipped: []
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
async unpublish() {
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4760
|
+
|
|
4761
|
+
// src/memory-extension/index.ts
|
|
4762
|
+
var PUBLISHERS = {};
|
|
4763
|
+
function registerPublisher(hostId, factory) {
|
|
4764
|
+
PUBLISHERS[hostId] = factory;
|
|
4765
|
+
}
|
|
4766
|
+
var CONNECTOR_TO_HOST = {
|
|
4767
|
+
"codex-cli": "codex"
|
|
4768
|
+
};
|
|
4769
|
+
function hostIdForConnector(connectorId) {
|
|
4770
|
+
return CONNECTOR_TO_HOST[connectorId] ?? connectorId;
|
|
4771
|
+
}
|
|
4772
|
+
function publisherFor(hostId) {
|
|
4773
|
+
const factory = PUBLISHERS[hostId];
|
|
4774
|
+
return factory ? factory() : void 0;
|
|
4775
|
+
}
|
|
4776
|
+
function publisherForConnector(connectorId) {
|
|
4777
|
+
return publisherFor(hostIdForConnector(connectorId));
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// src/taxonomy/default-taxonomy.ts
|
|
4781
|
+
var DEFAULT_TAXONOMY = {
|
|
4782
|
+
version: 1,
|
|
4783
|
+
categories: [
|
|
4784
|
+
{
|
|
4785
|
+
id: "corrections",
|
|
4786
|
+
name: "Corrections",
|
|
4787
|
+
description: "Corrections to previously stored information",
|
|
4788
|
+
filingRules: ["Any update that supersedes a prior fact"],
|
|
4789
|
+
priority: 10,
|
|
4790
|
+
memoryCategories: ["correction"]
|
|
4791
|
+
},
|
|
4792
|
+
{
|
|
4793
|
+
id: "principles",
|
|
4794
|
+
name: "Principles",
|
|
4795
|
+
description: "Rules, guidelines, and recurring patterns",
|
|
4796
|
+
filingRules: ["A guiding principle, rule, or skill"],
|
|
4797
|
+
priority: 20,
|
|
4798
|
+
memoryCategories: ["principle", "rule", "skill"]
|
|
4799
|
+
},
|
|
4800
|
+
{
|
|
4801
|
+
id: "procedures",
|
|
4802
|
+
name: "Procedures",
|
|
4803
|
+
description: "Ordered multi-step workflows the user repeats",
|
|
4804
|
+
filingRules: ["A repeatable sequence of steps or commands for a task"],
|
|
4805
|
+
priority: 25,
|
|
4806
|
+
memoryCategories: ["procedure"]
|
|
4807
|
+
},
|
|
4808
|
+
{
|
|
4809
|
+
id: "entities",
|
|
4810
|
+
name: "Entities",
|
|
4811
|
+
description: "People, organizations, places, projects",
|
|
4812
|
+
filingRules: ["Named entity with attributes"],
|
|
4813
|
+
priority: 30,
|
|
4814
|
+
memoryCategories: ["entity", "relationship"]
|
|
4815
|
+
},
|
|
4816
|
+
{
|
|
4817
|
+
id: "decisions",
|
|
4818
|
+
name: "Decisions",
|
|
4819
|
+
description: "Choices made and their rationale",
|
|
4820
|
+
filingRules: ["A decision or commitment with reasoning"],
|
|
4821
|
+
priority: 35,
|
|
4822
|
+
memoryCategories: ["decision", "commitment"]
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
id: "preferences",
|
|
4826
|
+
name: "Preferences",
|
|
4827
|
+
description: "User likes, dislikes, and style choices",
|
|
4828
|
+
filingRules: ["Anything expressing a preference or taste"],
|
|
4829
|
+
priority: 40,
|
|
4830
|
+
memoryCategories: ["preference"]
|
|
4831
|
+
},
|
|
4832
|
+
{
|
|
4833
|
+
id: "facts",
|
|
4834
|
+
name: "Facts",
|
|
4835
|
+
description: "Objective statements about the world",
|
|
4836
|
+
filingRules: ["Any factual claim or piece of information"],
|
|
4837
|
+
priority: 50,
|
|
4838
|
+
memoryCategories: ["fact"]
|
|
4839
|
+
},
|
|
4840
|
+
{
|
|
4841
|
+
id: "moments",
|
|
4842
|
+
name: "Moments",
|
|
4843
|
+
description: "Significant events or experiences",
|
|
4844
|
+
filingRules: ["A specific event worth remembering"],
|
|
4845
|
+
priority: 60,
|
|
4846
|
+
memoryCategories: ["moment"]
|
|
4847
|
+
}
|
|
4848
|
+
]
|
|
4849
|
+
};
|
|
4850
|
+
|
|
4851
|
+
// src/taxonomy/resolver-doc-generator.ts
|
|
4852
|
+
function generateResolverDocument(taxonomy) {
|
|
4853
|
+
const sorted = [...taxonomy.categories].sort((a, b) => {
|
|
4854
|
+
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
4855
|
+
return a.id.localeCompare(b.id);
|
|
4856
|
+
});
|
|
4857
|
+
const lines = [
|
|
4858
|
+
"# Memory Filing Resolver",
|
|
4859
|
+
"",
|
|
4860
|
+
"Given a new piece of knowledge, follow this tree to determine where it belongs.",
|
|
4861
|
+
""
|
|
4862
|
+
];
|
|
4863
|
+
let step = 1;
|
|
4864
|
+
for (const cat of sorted) {
|
|
4865
|
+
lines.push(`## Step ${step}: ${cat.description}?`);
|
|
4866
|
+
lines.push("");
|
|
4867
|
+
for (const rule of cat.filingRules) {
|
|
4868
|
+
lines.push(`- ${rule}`);
|
|
4869
|
+
}
|
|
4870
|
+
lines.push("");
|
|
4871
|
+
lines.push(
|
|
4872
|
+
`> YES: File under **${cat.id}/** (priority ${cat.priority})`
|
|
4873
|
+
);
|
|
4874
|
+
lines.push("");
|
|
4875
|
+
step++;
|
|
4876
|
+
}
|
|
4877
|
+
lines.push("## Tie-breaking");
|
|
4878
|
+
lines.push("");
|
|
4879
|
+
lines.push(
|
|
4880
|
+
"If a fact could go in multiple categories, file under the one with the **lowest priority number**."
|
|
4881
|
+
);
|
|
4882
|
+
lines.push("");
|
|
4883
|
+
lines.push(`---`);
|
|
4884
|
+
lines.push(`*Generated from taxonomy v${taxonomy.version}*`);
|
|
4885
|
+
lines.push("");
|
|
4886
|
+
return lines.join("\n");
|
|
4887
|
+
}
|
|
4888
|
+
|
|
4889
|
+
// src/taxonomy/taxonomy-loader.ts
|
|
4890
|
+
import { readFile, mkdir, writeFile } from "fs/promises";
|
|
4891
|
+
import path16 from "path";
|
|
4892
|
+
var TAXONOMY_DIR = ".taxonomy";
|
|
4893
|
+
var TAXONOMY_FILE = "taxonomy.json";
|
|
4894
|
+
var MAX_SLUG_LENGTH = 32;
|
|
4895
|
+
var SLUG_RE = /^[a-z][a-z0-9-]*$/;
|
|
4896
|
+
function validateSlug(slug) {
|
|
4897
|
+
if (slug.length === 0) {
|
|
4898
|
+
throw new Error("Taxonomy category ID must not be empty");
|
|
4899
|
+
}
|
|
4900
|
+
if (slug.length > MAX_SLUG_LENGTH) {
|
|
4901
|
+
throw new Error(
|
|
4902
|
+
`Taxonomy category ID "${slug}" exceeds ${MAX_SLUG_LENGTH} characters`
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4905
|
+
if (!SLUG_RE.test(slug)) {
|
|
4906
|
+
throw new Error(
|
|
4907
|
+
`Taxonomy category ID "${slug}" is invalid: must be lowercase letters, digits, and hyphens, starting with a letter`
|
|
4908
|
+
);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
function validateTaxonomy(taxonomy) {
|
|
4912
|
+
if (typeof taxonomy.version !== "number" || taxonomy.version < 1) {
|
|
4913
|
+
throw new Error("Taxonomy version must be a positive integer");
|
|
4914
|
+
}
|
|
4915
|
+
if (!Array.isArray(taxonomy.categories)) {
|
|
4916
|
+
throw new Error("Taxonomy categories must be an array");
|
|
4917
|
+
}
|
|
4918
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4919
|
+
for (const cat of taxonomy.categories) {
|
|
4920
|
+
validateSlug(cat.id);
|
|
4921
|
+
if (seenIds.has(cat.id)) {
|
|
4922
|
+
throw new Error(`Duplicate taxonomy category ID: "${cat.id}"`);
|
|
4923
|
+
}
|
|
4924
|
+
seenIds.add(cat.id);
|
|
4925
|
+
if (typeof cat.name !== "string" || cat.name.trim().length === 0) {
|
|
4926
|
+
throw new Error(`Taxonomy category "${cat.id}" must have a non-empty name`);
|
|
4927
|
+
}
|
|
4928
|
+
if (typeof cat.description !== "string" || cat.description.trim().length === 0) {
|
|
4929
|
+
throw new Error(`Taxonomy category "${cat.id}" must have a non-empty description`);
|
|
4930
|
+
}
|
|
4931
|
+
if (!Array.isArray(cat.filingRules)) {
|
|
4932
|
+
throw new Error(`Taxonomy category "${cat.id}" filingRules must be an array`);
|
|
4933
|
+
}
|
|
4934
|
+
if (typeof cat.priority !== "number" || !Number.isFinite(cat.priority)) {
|
|
4935
|
+
throw new Error(`Taxonomy category "${cat.id}" must have a finite numeric priority`);
|
|
4936
|
+
}
|
|
4937
|
+
if (!Array.isArray(cat.memoryCategories)) {
|
|
4938
|
+
throw new Error(`Taxonomy category "${cat.id}" memoryCategories must be an array`);
|
|
4939
|
+
}
|
|
4940
|
+
if (cat.parentId !== void 0) {
|
|
4941
|
+
if (typeof cat.parentId !== "string") {
|
|
4942
|
+
throw new Error(`Taxonomy category "${cat.id}" parentId must be a string if set`);
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4946
|
+
for (const cat of taxonomy.categories) {
|
|
4947
|
+
if (cat.parentId !== void 0 && !seenIds.has(cat.parentId)) {
|
|
4948
|
+
throw new Error(
|
|
4949
|
+
`Taxonomy category "${cat.id}" references unknown parentId "${cat.parentId}"`
|
|
4950
|
+
);
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
async function loadTaxonomy(memoryDir) {
|
|
4955
|
+
const taxonomyPath = path16.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
|
|
4956
|
+
let raw;
|
|
4957
|
+
try {
|
|
4958
|
+
raw = await readFile(taxonomyPath, "utf-8");
|
|
4959
|
+
} catch (err) {
|
|
4960
|
+
if (err instanceof Error && err.code === "ENOENT") {
|
|
4961
|
+
return structuredClone(DEFAULT_TAXONOMY);
|
|
4962
|
+
}
|
|
4963
|
+
throw err;
|
|
4964
|
+
}
|
|
4965
|
+
const parsed = JSON.parse(raw);
|
|
4966
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4967
|
+
throw new Error("taxonomy.json must be a JSON object");
|
|
4968
|
+
}
|
|
4969
|
+
const obj = parsed;
|
|
4970
|
+
const userVersion = typeof obj.version === "number" ? obj.version : DEFAULT_TAXONOMY.version;
|
|
4971
|
+
const userCategories = Array.isArray(obj.categories) ? obj.categories : [];
|
|
4972
|
+
const userIdCounts = /* @__PURE__ */ new Map();
|
|
4973
|
+
for (const cat of userCategories) {
|
|
4974
|
+
const id = typeof cat.id === "string" ? cat.id : String(cat.id);
|
|
4975
|
+
userIdCounts.set(id, (userIdCounts.get(id) ?? 0) + 1);
|
|
4976
|
+
}
|
|
4977
|
+
const duplicateIds = [...userIdCounts.entries()].filter(([, count]) => count > 1).map(([id]) => id);
|
|
4978
|
+
if (duplicateIds.length > 0) {
|
|
4979
|
+
throw new Error(
|
|
4980
|
+
`Duplicate category IDs in taxonomy.json: ${duplicateIds.map((id) => `"${id}"`).join(", ")}`
|
|
4981
|
+
);
|
|
4982
|
+
}
|
|
4983
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
4984
|
+
for (const cat of DEFAULT_TAXONOMY.categories) {
|
|
4985
|
+
mergedMap.set(cat.id, { ...cat });
|
|
4986
|
+
}
|
|
4987
|
+
for (const cat of userCategories) {
|
|
4988
|
+
mergedMap.set(cat.id, cat);
|
|
4989
|
+
}
|
|
4990
|
+
const merged = {
|
|
4991
|
+
version: userVersion,
|
|
4992
|
+
categories: [...mergedMap.values()]
|
|
4993
|
+
};
|
|
4994
|
+
validateTaxonomy(merged);
|
|
4995
|
+
return merged;
|
|
4996
|
+
}
|
|
4997
|
+
async function saveTaxonomy(memoryDir, taxonomy) {
|
|
4998
|
+
validateTaxonomy(taxonomy);
|
|
4999
|
+
const dir = path16.join(memoryDir, TAXONOMY_DIR);
|
|
5000
|
+
await mkdir(dir, { recursive: true });
|
|
5001
|
+
const filePath = path16.join(dir, TAXONOMY_FILE);
|
|
5002
|
+
await writeFile(filePath, JSON.stringify(taxonomy, null, 2) + "\n", "utf-8");
|
|
5003
|
+
}
|
|
5004
|
+
function getTaxonomyDir(memoryDir) {
|
|
5005
|
+
return path16.join(memoryDir, TAXONOMY_DIR);
|
|
5006
|
+
}
|
|
5007
|
+
function getTaxonomyFilePath(memoryDir) {
|
|
5008
|
+
return path16.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
// src/enrichment/types.ts
|
|
5012
|
+
function defaultEnrichmentPipelineConfig() {
|
|
5013
|
+
return {
|
|
5014
|
+
enabled: false,
|
|
5015
|
+
providers: [],
|
|
5016
|
+
importanceThresholds: {
|
|
5017
|
+
critical: [],
|
|
5018
|
+
high: [],
|
|
5019
|
+
normal: [],
|
|
5020
|
+
low: []
|
|
5021
|
+
},
|
|
5022
|
+
maxCandidatesPerEntity: 20,
|
|
5023
|
+
autoEnrichOnCreate: false,
|
|
5024
|
+
scheduleIntervalMs: 36e5
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
|
|
5028
|
+
// src/enrichment/provider-registry.ts
|
|
5029
|
+
var EnrichmentProviderRegistry = class {
|
|
5030
|
+
providers = /* @__PURE__ */ new Map();
|
|
5031
|
+
/** Register a provider. Overwrites any existing provider with the same id. */
|
|
5032
|
+
register(provider) {
|
|
5033
|
+
this.providers.set(provider.id, provider);
|
|
5034
|
+
}
|
|
5035
|
+
/** Look up a single provider by id. */
|
|
5036
|
+
get(id) {
|
|
5037
|
+
return this.providers.get(id);
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Return all registered providers whose id appears in the config's
|
|
5041
|
+
* `providers` list with `enabled: true`.
|
|
5042
|
+
*/
|
|
5043
|
+
listEnabled(config) {
|
|
5044
|
+
const enabledIds = new Set(
|
|
5045
|
+
config.providers.filter((p) => p.enabled).map((p) => p.id)
|
|
5046
|
+
);
|
|
5047
|
+
const result = [];
|
|
5048
|
+
for (const [id, provider] of this.providers.entries()) {
|
|
5049
|
+
if (enabledIds.has(id)) {
|
|
5050
|
+
result.push(provider);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
5053
|
+
return result;
|
|
5054
|
+
}
|
|
5055
|
+
/**
|
|
5056
|
+
* Return providers that should run for a given importance level.
|
|
5057
|
+
* Providers are resolved from `config.importanceThresholds[level]` and
|
|
5058
|
+
* filtered to only those that are both registered and enabled.
|
|
5059
|
+
*/
|
|
5060
|
+
getForImportance(level, config) {
|
|
5061
|
+
if (level === "trivial") return [];
|
|
5062
|
+
const thresholds = config.importanceThresholds;
|
|
5063
|
+
const providerIds = level === "critical" ? thresholds.critical : level === "high" ? thresholds.high : level === "normal" ? thresholds.normal : thresholds.low;
|
|
5064
|
+
const enabledIds = new Set(
|
|
5065
|
+
config.providers.filter((p) => p.enabled).map((p) => p.id)
|
|
5066
|
+
);
|
|
5067
|
+
const result = [];
|
|
5068
|
+
for (const id of providerIds) {
|
|
5069
|
+
if (!enabledIds.has(id)) continue;
|
|
5070
|
+
const provider = this.providers.get(id);
|
|
5071
|
+
if (provider) {
|
|
5072
|
+
result.push(provider);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
return result;
|
|
5076
|
+
}
|
|
5077
|
+
};
|
|
5078
|
+
|
|
5079
|
+
// src/enrichment/web-search-provider.ts
|
|
5080
|
+
var WebSearchProvider = class {
|
|
5081
|
+
id = "web-search";
|
|
5082
|
+
costTier = "cheap";
|
|
5083
|
+
searchFn;
|
|
5084
|
+
constructor(options = {}) {
|
|
5085
|
+
this.searchFn = options.searchFn;
|
|
5086
|
+
}
|
|
5087
|
+
async isAvailable() {
|
|
5088
|
+
return this.searchFn !== void 0;
|
|
5089
|
+
}
|
|
5090
|
+
async enrich(entity) {
|
|
5091
|
+
if (!this.searchFn) return [];
|
|
5092
|
+
const query = `${entity.name} ${entity.type}`;
|
|
5093
|
+
let snippets;
|
|
5094
|
+
try {
|
|
5095
|
+
snippets = await this.searchFn(query);
|
|
5096
|
+
} catch {
|
|
5097
|
+
return [];
|
|
5098
|
+
}
|
|
5099
|
+
return snippets.filter((s) => typeof s === "string" && s.trim().length > 0).map((snippet) => ({
|
|
5100
|
+
text: snippet.trim(),
|
|
5101
|
+
source: this.id,
|
|
5102
|
+
sourceUrl: void 0,
|
|
5103
|
+
confidence: 0.5,
|
|
5104
|
+
category: "fact",
|
|
5105
|
+
tags: ["web-search"]
|
|
5106
|
+
}));
|
|
5107
|
+
}
|
|
5108
|
+
};
|
|
5109
|
+
|
|
5110
|
+
// src/enrichment/pipeline.ts
|
|
5111
|
+
function isRateLimited(provider, config, buckets) {
|
|
5112
|
+
const providerCfg = config.providers.find((p) => p.id === provider.id);
|
|
5113
|
+
if (!providerCfg?.rateLimit) return false;
|
|
5114
|
+
const now = Date.now();
|
|
5115
|
+
let bucket = buckets.get(provider.id);
|
|
5116
|
+
if (!bucket) {
|
|
5117
|
+
bucket = {
|
|
5118
|
+
minuteCount: 0,
|
|
5119
|
+
minuteReset: now + 6e4,
|
|
5120
|
+
dayCount: 0,
|
|
5121
|
+
dayReset: now + 864e5
|
|
5122
|
+
};
|
|
5123
|
+
buckets.set(provider.id, bucket);
|
|
5124
|
+
}
|
|
5125
|
+
if (now >= bucket.minuteReset) {
|
|
5126
|
+
bucket.minuteCount = 0;
|
|
5127
|
+
bucket.minuteReset = now + 6e4;
|
|
5128
|
+
}
|
|
5129
|
+
if (now >= bucket.dayReset) {
|
|
5130
|
+
bucket.dayCount = 0;
|
|
5131
|
+
bucket.dayReset = now + 864e5;
|
|
5132
|
+
}
|
|
5133
|
+
const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;
|
|
5134
|
+
return bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay;
|
|
5135
|
+
}
|
|
5136
|
+
function recordCall(providerId, buckets) {
|
|
5137
|
+
const bucket = buckets.get(providerId);
|
|
5138
|
+
if (bucket) {
|
|
5139
|
+
bucket.minuteCount += 1;
|
|
5140
|
+
bucket.dayCount += 1;
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
async function runEnrichmentPipeline(entities, registry, config, log2) {
|
|
5144
|
+
if (!config.enabled) return [];
|
|
5145
|
+
if (entities.length === 0) return [];
|
|
5146
|
+
const rateBuckets = /* @__PURE__ */ new Map();
|
|
5147
|
+
const results = [];
|
|
5148
|
+
for (const entity of entities) {
|
|
5149
|
+
const providers = registry.getForImportance(entity.importanceLevel, config);
|
|
5150
|
+
for (const provider of providers) {
|
|
5151
|
+
const start = Date.now();
|
|
5152
|
+
let available;
|
|
5153
|
+
try {
|
|
5154
|
+
available = await provider.isAvailable();
|
|
5155
|
+
} catch {
|
|
5156
|
+
available = false;
|
|
5157
|
+
}
|
|
5158
|
+
if (!available) {
|
|
5159
|
+
log2.debug?.(
|
|
5160
|
+
`enrichment: skipping provider ${provider.id} for ${entity.name} \u2014 unavailable`
|
|
5161
|
+
);
|
|
5162
|
+
results.push({
|
|
5163
|
+
entityName: entity.name,
|
|
5164
|
+
provider: provider.id,
|
|
5165
|
+
candidatesFound: 0,
|
|
5166
|
+
candidatesAccepted: 0,
|
|
5167
|
+
candidatesRejected: 0,
|
|
5168
|
+
acceptedCandidates: [],
|
|
5169
|
+
elapsed: Date.now() - start
|
|
5170
|
+
});
|
|
5171
|
+
continue;
|
|
5172
|
+
}
|
|
5173
|
+
if (isRateLimited(provider, config, rateBuckets)) {
|
|
5174
|
+
log2.debug?.(
|
|
5175
|
+
`enrichment: skipping provider ${provider.id} for ${entity.name} \u2014 rate limited`
|
|
5176
|
+
);
|
|
5177
|
+
results.push({
|
|
5178
|
+
entityName: entity.name,
|
|
5179
|
+
provider: provider.id,
|
|
5180
|
+
candidatesFound: 0,
|
|
5181
|
+
candidatesAccepted: 0,
|
|
5182
|
+
candidatesRejected: 0,
|
|
5183
|
+
acceptedCandidates: [],
|
|
5184
|
+
elapsed: Date.now() - start
|
|
5185
|
+
});
|
|
5186
|
+
continue;
|
|
5187
|
+
}
|
|
5188
|
+
let candidates;
|
|
5189
|
+
try {
|
|
5190
|
+
candidates = await provider.enrich(entity);
|
|
5191
|
+
} catch (err) {
|
|
5192
|
+
recordCall(provider.id, rateBuckets);
|
|
5193
|
+
log2.error?.(
|
|
5194
|
+
`enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
5195
|
+
);
|
|
5196
|
+
results.push({
|
|
5197
|
+
entityName: entity.name,
|
|
5198
|
+
provider: provider.id,
|
|
5199
|
+
candidatesFound: 0,
|
|
5200
|
+
candidatesAccepted: 0,
|
|
5201
|
+
candidatesRejected: 0,
|
|
5202
|
+
acceptedCandidates: [],
|
|
5203
|
+
elapsed: Date.now() - start
|
|
5204
|
+
});
|
|
5205
|
+
continue;
|
|
5206
|
+
}
|
|
5207
|
+
recordCall(provider.id, rateBuckets);
|
|
5208
|
+
for (const candidate of candidates) {
|
|
5209
|
+
candidate.source = provider.id;
|
|
5210
|
+
}
|
|
5211
|
+
const maxCandidates = config.maxCandidatesPerEntity;
|
|
5212
|
+
let accepted;
|
|
5213
|
+
if (maxCandidates === 0) {
|
|
5214
|
+
accepted = [];
|
|
5215
|
+
} else if (maxCandidates > 0 && candidates.length > maxCandidates) {
|
|
5216
|
+
accepted = candidates.slice(0, maxCandidates);
|
|
5217
|
+
} else {
|
|
5218
|
+
accepted = candidates;
|
|
5219
|
+
}
|
|
5220
|
+
const rejected = candidates.length - accepted.length;
|
|
5221
|
+
results.push({
|
|
5222
|
+
entityName: entity.name,
|
|
5223
|
+
provider: provider.id,
|
|
5224
|
+
candidatesFound: candidates.length,
|
|
5225
|
+
candidatesAccepted: accepted.length,
|
|
5226
|
+
candidatesRejected: rejected,
|
|
5227
|
+
acceptedCandidates: accepted,
|
|
5228
|
+
elapsed: Date.now() - start
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
return results;
|
|
5233
|
+
}
|
|
5234
|
+
|
|
5235
|
+
// src/enrichment/audit.ts
|
|
5236
|
+
import { mkdir as mkdir2, readFile as readFile2, appendFile } from "fs/promises";
|
|
5237
|
+
import { existsSync as existsSync2 } from "fs";
|
|
5238
|
+
import path17 from "path";
|
|
5239
|
+
var AUDIT_FILENAME = "enrichment-audit.jsonl";
|
|
5240
|
+
function auditFilePath(auditDir) {
|
|
5241
|
+
return path17.join(auditDir, AUDIT_FILENAME);
|
|
5242
|
+
}
|
|
5243
|
+
async function appendAuditEntry(auditDir, entry) {
|
|
5244
|
+
await mkdir2(auditDir, { recursive: true });
|
|
5245
|
+
const line = JSON.stringify(entry) + "\n";
|
|
5246
|
+
await appendFile(auditFilePath(auditDir), line, "utf-8");
|
|
5247
|
+
}
|
|
5248
|
+
async function readAuditLog(auditDir, since) {
|
|
5249
|
+
const filePath = auditFilePath(auditDir);
|
|
5250
|
+
if (!existsSync2(filePath)) return [];
|
|
5251
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
5252
|
+
const entries = [];
|
|
5253
|
+
for (const line of raw.split("\n")) {
|
|
5254
|
+
const trimmed = line.trim();
|
|
5255
|
+
if (trimmed.length === 0) continue;
|
|
5256
|
+
try {
|
|
5257
|
+
const parsed = JSON.parse(trimmed);
|
|
5258
|
+
if (typeof parsed === "object" && parsed !== null && "timestamp" in parsed && "entityName" in parsed) {
|
|
5259
|
+
const entry = parsed;
|
|
5260
|
+
if (since && entry.timestamp < since) continue;
|
|
5261
|
+
entries.push(entry);
|
|
5262
|
+
}
|
|
5263
|
+
} catch {
|
|
5264
|
+
}
|
|
5265
|
+
}
|
|
5266
|
+
return entries;
|
|
5267
|
+
}
|
|
2445
5268
|
export {
|
|
5269
|
+
BRIEFING_FORMAT_ALLOWED,
|
|
2446
5270
|
BootstrapEngine,
|
|
5271
|
+
CITATION_UNKNOWN,
|
|
5272
|
+
CODEX_THREAD_KEY_PREFIX,
|
|
5273
|
+
ClaudeCodeMemoryExtensionPublisher,
|
|
5274
|
+
CodexMemoryExtensionPublisher,
|
|
5275
|
+
DEFAULT_CITATION_FORMAT,
|
|
5276
|
+
DEFAULT_GRACE_PERIOD_DAYS,
|
|
5277
|
+
DEFAULT_MAX_BINARY_SIZE_BYTES,
|
|
5278
|
+
DEFAULT_SCAN_PATTERNS,
|
|
5279
|
+
DEFAULT_TAXONOMY,
|
|
5280
|
+
FILTER_LABELS as DIRECT_ANSWER_FILTER_LABELS,
|
|
2447
5281
|
EngramAccessHttpServer,
|
|
2448
5282
|
EngramAccessInputError,
|
|
2449
5283
|
EngramAccessService,
|
|
2450
5284
|
EngramMcpServer,
|
|
5285
|
+
EnrichmentProviderRegistry,
|
|
2451
5286
|
ExtractionEngine,
|
|
5287
|
+
FallbackLlmClient,
|
|
5288
|
+
FileCalendarSource,
|
|
5289
|
+
FilesystemBackend,
|
|
5290
|
+
HermesMemoryExtensionPublisher,
|
|
5291
|
+
LEGACY_PLUGIN_ID,
|
|
2452
5292
|
LanceDbBackend,
|
|
5293
|
+
MARKETPLACE_MANIFEST_FILENAME,
|
|
5294
|
+
MARKETPLACE_SCHEMA_VERSION,
|
|
5295
|
+
MATERIALIZE_VERSION,
|
|
2453
5296
|
MeilisearchBackend,
|
|
5297
|
+
NoneBackend,
|
|
2454
5298
|
OramaBackend,
|
|
2455
5299
|
Orchestrator,
|
|
5300
|
+
PLUGIN_ID,
|
|
5301
|
+
PUBLISHERS,
|
|
2456
5302
|
QmdClient,
|
|
5303
|
+
REMNIC_CITATION_FORMAT,
|
|
5304
|
+
REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT,
|
|
5305
|
+
REMNIC_MCP_TOOL_INVENTORY,
|
|
5306
|
+
REMNIC_RECALL_DECISION_RULES,
|
|
5307
|
+
REMNIC_SEMANTIC_OVERVIEW,
|
|
5308
|
+
SENTINEL_FILE,
|
|
2457
5309
|
StorageManager,
|
|
5310
|
+
WebSearchProvider,
|
|
5311
|
+
appendAuditEntry,
|
|
5312
|
+
attachCitation,
|
|
5313
|
+
briefingFilename,
|
|
5314
|
+
buildBriefing,
|
|
5315
|
+
buildCitationGuidance,
|
|
2458
5316
|
buildEntityRecallSection,
|
|
5317
|
+
buildExtensionsBlockForConsolidation,
|
|
5318
|
+
buildExtensionsFooterForSummary,
|
|
5319
|
+
buildProcedureMarkdownBody,
|
|
5320
|
+
buildProcedureRecallSection,
|
|
5321
|
+
checkMarketplaceManifest,
|
|
5322
|
+
clearBulkImportSources,
|
|
5323
|
+
clearTrainingExportAdapters,
|
|
5324
|
+
clearVerdictCache,
|
|
5325
|
+
coerceInstallExtension,
|
|
5326
|
+
convertMemoriesToRecords,
|
|
5327
|
+
createBackend,
|
|
2459
5328
|
createSpace,
|
|
5329
|
+
createVerdictCache,
|
|
5330
|
+
createVersion,
|
|
2460
5331
|
curate,
|
|
5332
|
+
decideSemanticDedup,
|
|
5333
|
+
defaultEnrichmentPipelineConfig,
|
|
2461
5334
|
defaultWorkspaceDir,
|
|
2462
5335
|
deleteSpace,
|
|
5336
|
+
deriveSessionId,
|
|
5337
|
+
describeMemoriesDir,
|
|
5338
|
+
diffVersions,
|
|
5339
|
+
discoverMemoryExtensions,
|
|
2463
5340
|
doctorConnector,
|
|
5341
|
+
emptyManifest,
|
|
5342
|
+
ensureSentinel,
|
|
2464
5343
|
findContradictions,
|
|
2465
5344
|
findDuplicates,
|
|
5345
|
+
focusMatchesEntity,
|
|
5346
|
+
focusMatchesMemory,
|
|
5347
|
+
formatBatchTranscript,
|
|
5348
|
+
formatCitation,
|
|
5349
|
+
formatOaiMemCitation,
|
|
2466
5350
|
formatZodError,
|
|
2467
5351
|
generateContextTree,
|
|
5352
|
+
generateMarketplaceManifest,
|
|
5353
|
+
generateResolverDocument,
|
|
2468
5354
|
generateToken,
|
|
2469
5355
|
getActiveSpace,
|
|
2470
5356
|
getAllValidTokens,
|
|
2471
5357
|
getAllValidTokensCached,
|
|
2472
5358
|
getAuditLog,
|
|
5359
|
+
getBulkImportSource,
|
|
2473
5360
|
getManifestPath,
|
|
5361
|
+
getMemoryForActiveMemory,
|
|
2474
5362
|
getSpacesDir,
|
|
5363
|
+
getTaxonomyDir,
|
|
5364
|
+
getTaxonomyFilePath,
|
|
5365
|
+
getTrainingExportAdapter,
|
|
5366
|
+
getVersion,
|
|
5367
|
+
hasBroadGraphIntent,
|
|
5368
|
+
hasCitation,
|
|
5369
|
+
hostIdForConnector,
|
|
5370
|
+
inferIntentFromText,
|
|
2475
5371
|
initLogger,
|
|
2476
5372
|
installConnector,
|
|
5373
|
+
installFromMarketplace,
|
|
5374
|
+
intentCompatibilityScore,
|
|
5375
|
+
isDirectAnswerEligible,
|
|
5376
|
+
isImportRole,
|
|
5377
|
+
isTaskInitiationIntent,
|
|
2477
5378
|
isTrustZoneName,
|
|
5379
|
+
judgeFactDurability,
|
|
5380
|
+
listBulkImportSources,
|
|
2478
5381
|
listConnectors,
|
|
2479
5382
|
listReviewItems,
|
|
2480
5383
|
listSpaces,
|
|
2481
5384
|
listTokens,
|
|
5385
|
+
listTrainingExportAdapters,
|
|
5386
|
+
listVersions,
|
|
2482
5387
|
loadDaySummaryPrompt,
|
|
2483
5388
|
loadManifest,
|
|
2484
5389
|
loadRegistry,
|
|
5390
|
+
loadTaxonomy,
|
|
2485
5391
|
loadTokenStore,
|
|
2486
5392
|
log,
|
|
5393
|
+
manifestDir,
|
|
5394
|
+
manifestPath,
|
|
5395
|
+
matchesPatterns,
|
|
5396
|
+
materializeForNamespace,
|
|
2487
5397
|
memoryStoreRequestSchema,
|
|
2488
5398
|
mergeSpaces,
|
|
2489
5399
|
migrateFromEngram,
|
|
2490
5400
|
observeRequestSchema,
|
|
2491
5401
|
onboard,
|
|
5402
|
+
parseAllCitations,
|
|
5403
|
+
parseBriefingFocus,
|
|
5404
|
+
parseBriefingWindow,
|
|
5405
|
+
parseCitation,
|
|
2492
5406
|
parseConfig,
|
|
5407
|
+
parseIsoTimestamp,
|
|
5408
|
+
parseOaiMemCitation,
|
|
5409
|
+
parseProcedureStepsFromBody,
|
|
5410
|
+
parseStrictCliDate,
|
|
2493
5411
|
performReview,
|
|
5412
|
+
planRecallMode,
|
|
2494
5413
|
promoteSpace,
|
|
5414
|
+
publisherFor,
|
|
5415
|
+
publisherForConnector,
|
|
2495
5416
|
pullFromSpace,
|
|
2496
5417
|
pushToSpace,
|
|
5418
|
+
readAuditLog,
|
|
5419
|
+
readManifest,
|
|
5420
|
+
recallForActiveMemory,
|
|
2497
5421
|
recallRequestSchema,
|
|
5422
|
+
registerBulkImportSource,
|
|
5423
|
+
registerPublisher,
|
|
5424
|
+
registerTrainingExportAdapter,
|
|
2498
5425
|
removeConnector,
|
|
5426
|
+
renderBriefingMarkdown,
|
|
5427
|
+
renderExtensionsBlock,
|
|
5428
|
+
renderExtensionsFooter,
|
|
5429
|
+
resolveBriefingSaveDir,
|
|
5430
|
+
resolveCategory,
|
|
2499
5431
|
resolveConnectorFromToken,
|
|
5432
|
+
resolveExtensionsRoot,
|
|
5433
|
+
resolvePrincipal,
|
|
5434
|
+
resolveRemnicPluginEntry,
|
|
5435
|
+
revertToVersion,
|
|
2500
5436
|
revokeToken,
|
|
2501
5437
|
rollbackFromEngramMigration,
|
|
5438
|
+
runBinaryLifecyclePipeline,
|
|
5439
|
+
runBulkImportCliCommand,
|
|
5440
|
+
runBulkImportPipeline,
|
|
5441
|
+
runCodexMaterialize,
|
|
5442
|
+
runEnrichmentPipeline,
|
|
5443
|
+
runPostConsolidationMaterialize,
|
|
5444
|
+
sanitizeNoteForCitation,
|
|
2502
5445
|
sanitizeSessionKeyForFilename,
|
|
2503
5446
|
saveManifest,
|
|
2504
5447
|
saveRegistry,
|
|
5448
|
+
saveTaxonomy,
|
|
2505
5449
|
saveTokenStore,
|
|
5450
|
+
scanForBinaries,
|
|
2506
5451
|
shareSpace,
|
|
5452
|
+
stripCitation,
|
|
2507
5453
|
suggestionSubmitRequestSchema,
|
|
2508
5454
|
switchSpace,
|
|
2509
5455
|
syncChanges,
|
|
5456
|
+
validateBriefingFormat,
|
|
5457
|
+
validateImportTurn,
|
|
5458
|
+
validateMarketplaceManifest,
|
|
2510
5459
|
validateRequest,
|
|
2511
|
-
|
|
5460
|
+
validateSlug,
|
|
5461
|
+
validateTaxonomy,
|
|
5462
|
+
verdictCacheSize,
|
|
5463
|
+
watchForChanges,
|
|
5464
|
+
writeManifest,
|
|
5465
|
+
writeMarketplaceManifest
|
|
2512
5466
|
};
|
|
2513
5467
|
//# sourceMappingURL=index.js.map
|