@remnic/core 9.3.726 → 9.3.728
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +22 -22
- package/dist/access-cli.js +45 -45
- package/dist/access-http.d.ts +20 -5
- package/dist/access-http.js +27 -27
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +26 -26
- package/dist/access-operations-batch.js +23 -23
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +25 -25
- package/dist/access-schema.d.ts +4 -4
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-B2PlPtxh.d.ts → access-service-Bgpjxo4f.d.ts} +36 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +21 -21
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +44 -2
- package/dist/capabilities.js +5 -3
- package/dist/{capsule-crypto-GWVG7LGC.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{catalog-CzUb-GyD.d.ts → catalog-bMczlO2G.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -8
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-OWHERGF2.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-BIA7ELIB.js → chunk-36SEDFAU.js} +3 -3
- package/dist/{chunk-UQSVUEN2.js → chunk-37436GML.js} +2 -2
- package/dist/{chunk-DVRHLN2O.js → chunk-3RCBIOOJ.js} +18 -2
- package/dist/{chunk-DVRHLN2O.js.map → chunk-3RCBIOOJ.js.map} +1 -1
- package/dist/{chunk-RRHHQYJS.js → chunk-3TSTGI6F.js} +2 -2
- package/dist/{chunk-5OOA7SGX.js → chunk-4ESAR77N.js} +2 -2
- package/dist/{chunk-FO3HO52S.js → chunk-4H4F3BFL.js} +2 -2
- package/dist/{chunk-BEUCACTV.js → chunk-4NOI577P.js} +2 -2
- package/dist/{chunk-BBIE7W52.js → chunk-4NR2PDZ5.js} +2 -2
- package/dist/{chunk-WGTWGDR5.js → chunk-76NHR6ZL.js} +5 -5
- package/dist/{chunk-PKMQK2WY.js → chunk-773DPI6T.js} +4 -4
- package/dist/{chunk-MQVKHFIU.js → chunk-7RLCCNHU.js} +8 -8
- package/dist/{chunk-XGTBNVIU.js → chunk-AAD2ZS6P.js} +11 -1
- package/dist/chunk-AAD2ZS6P.js.map +1 -0
- package/dist/{chunk-EZNDNMRH.js → chunk-AQLOP5F4.js} +2 -2
- package/dist/{chunk-UNNDFYUX.js → chunk-ARV3AUOM.js} +5 -5
- package/dist/{chunk-NPL7VDM6.js → chunk-ASKXIAHD.js} +2 -2
- package/dist/{chunk-TQQH2FTX.js → chunk-BQCHEV7O.js} +106 -15
- package/dist/{chunk-TQQH2FTX.js.map → chunk-BQCHEV7O.js.map} +1 -1
- package/dist/{chunk-ZA2S2VLL.js → chunk-C2PG7IEH.js} +5 -2
- package/dist/chunk-C2PG7IEH.js.map +1 -0
- package/dist/{chunk-K4ZNARDO.js → chunk-CBWI22JN.js} +2 -2
- package/dist/{chunk-ZOHMPFOB.js → chunk-CKFLLAIS.js} +3 -3
- package/dist/{chunk-JPUT5NGH.js → chunk-DMQWV6SQ.js} +2 -2
- package/dist/{chunk-F3XZN24Z.js → chunk-DRUDC2UX.js} +2 -2
- package/dist/{chunk-KJR66XOM.js → chunk-EY6MDMHK.js} +2 -2
- package/dist/{chunk-3TUT4PWB.js → chunk-FVZ2ISIX.js} +2 -2
- package/dist/{chunk-C4X4QGIO.js → chunk-G6NKALRN.js} +5 -2
- package/dist/chunk-G6NKALRN.js.map +1 -0
- package/dist/{chunk-6SQQOJ4U.js → chunk-GKRLPHPU.js} +2 -2
- package/dist/{chunk-PLG6JHQS.js → chunk-H5BWGCRZ.js} +43 -42
- package/dist/chunk-H5BWGCRZ.js.map +1 -0
- package/dist/{chunk-2NBP25BG.js → chunk-HGNOCPZX.js} +171 -79
- package/dist/chunk-HGNOCPZX.js.map +1 -0
- package/dist/{chunk-5NWYQUQC.js → chunk-HOHVIVDR.js} +2 -2
- package/dist/{chunk-DCKNMLEQ.js → chunk-I5REUWLH.js} +11 -5
- package/dist/chunk-I5REUWLH.js.map +1 -0
- package/dist/{chunk-6WAJQKBS.js → chunk-JYLFZWUX.js} +2 -2
- package/dist/{chunk-5W7CTZ2E.js → chunk-KQAFEZQX.js} +4 -4
- package/dist/{chunk-UKTDXTWX.js → chunk-M4AYVEWS.js} +7 -7
- package/dist/{chunk-YTZ3VGC7.js → chunk-M4V2JVSZ.js} +2 -2
- package/dist/{chunk-TA3Y6GEX.js → chunk-MLKWJOAJ.js} +2 -2
- package/dist/{chunk-DQXPJ6HD.js → chunk-O6YH5F47.js} +4 -4
- package/dist/{chunk-7AG54AF3.js → chunk-PUR2CKX2.js} +1 -1
- package/dist/chunk-PUR2CKX2.js.map +1 -0
- package/dist/{chunk-OAHOL3ZC.js → chunk-QNEUZIAH.js} +152 -62
- package/dist/chunk-QNEUZIAH.js.map +1 -0
- package/dist/{chunk-Q6D75LVH.js → chunk-UM5AWJNQ.js} +79 -17
- package/dist/chunk-UM5AWJNQ.js.map +1 -0
- package/dist/{chunk-53EEQYE5.js → chunk-UTHQPSRC.js} +8 -8
- package/dist/{chunk-BB57OZWC.js → chunk-UVPL565C.js} +2 -2
- package/dist/{chunk-6AFVIOVZ.js → chunk-V4GNSDFH.js} +2 -2
- package/dist/{chunk-EHISUJFN.js → chunk-WAS3NV46.js} +6 -3
- package/dist/chunk-WAS3NV46.js.map +1 -0
- package/dist/{chunk-UVYI6VIX.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-V7CMKMHM.js → chunk-Y4YD5TYW.js} +3 -3
- package/dist/{chunk-7NS7AWMZ.js → chunk-YGIQORJ3.js} +3 -3
- package/dist/{chunk-MDBZTH7N.js → chunk-YKBHOBJK.js} +44 -10
- package/dist/chunk-YKBHOBJK.js.map +1 -0
- package/dist/{chunk-3MS3QVX6.js → chunk-YOLZS7NY.js} +2 -2
- package/dist/{chunk-6OIDDKAA.js → chunk-YTU7WKYN.js} +35 -9
- package/dist/chunk-YTU7WKYN.js.map +1 -0
- package/dist/{cli-IOlDHSBT.d.ts → cli-BLME420h.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +49 -49
- package/dist/compat/codex-cli-fallback.d.ts +1 -0
- package/dist/compat/codex-cli-fallback.js +22 -0
- package/dist/compat/codex-cli-fallback.js.map +1 -0
- package/dist/compat/codex-thread-key.d.ts +1 -0
- package/dist/compat/codex-thread-key.js +18 -0
- package/dist/compat/codex-thread-key.js.map +1 -0
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +3 -2
- package/dist/faithfulness-config.d.ts +1 -0
- package/dist/faithfulness-config.js +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-PG5HBC3K.js → first-start-migration-22LPFHD5.js} +6 -2
- package/dist/first-start-migration-22LPFHD5.js.map +1 -0
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +64 -64
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +7 -7
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +9 -4
- package/dist/local-model-endpoint.js +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +14 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -5
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +7 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +20 -20
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-C_OMSIMZ.d.ts → orchestrator-DqPAz8ot.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +33 -33
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/provenance.js +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +4 -4
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -4
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -8
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-B8LiYtvx.d.ts → semantic-consolidation-Cl15ksZy.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +6 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +2 -2
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DmTaqob2.d.ts → types-ChQb9D6s.d.ts} +8 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +7 -7
- package/package.json +26 -2
- package/src/access-http.ts +68 -1
- package/src/access-service.ts +2 -1
- package/src/admin/admin-surfaces.test.ts +289 -0
- package/src/admin/admin-surfaces.ts +138 -7
- package/src/bitemporal-dedup-backfill.test.ts +294 -0
- package/src/capabilities.test.ts +155 -0
- package/src/capabilities.ts +91 -0
- package/src/chat/chat-engine.test.ts +104 -1
- package/src/chat/chat-http.test.ts +119 -1
- package/src/chat/chat-http.ts +252 -97
- package/src/chat/chat-session.ts +166 -7
- package/src/cli.ts +3 -2
- package/src/compat/codex-cli-fallback.ts +23 -0
- package/src/compat/codex-subpath-aliases.test.ts +93 -0
- package/src/compat/codex-thread-key.ts +23 -0
- package/src/embedding-fallback.ts +2 -1
- package/src/extraction-faithfulness.test.ts +240 -0
- package/src/extraction-faithfulness.ts +58 -6
- package/src/extraction.ts +7 -3
- package/src/faithfulness-config.ts +16 -1
- package/src/local-model-endpoint.ts +24 -3
- package/src/maintenance/first-start-migration.ts +3 -1
- package/src/orchestrator.ts +238 -34
- package/src/provenance-extraction.test.ts +266 -0
- package/src/provenance.ts +66 -16
- package/src/search/embed-helper.ts +3 -2
- package/src/types.ts +8 -0
- package/dist/chunk-2NBP25BG.js.map +0 -1
- package/dist/chunk-6OIDDKAA.js.map +0 -1
- package/dist/chunk-7AG54AF3.js.map +0 -1
- package/dist/chunk-C4X4QGIO.js.map +0 -1
- package/dist/chunk-DCKNMLEQ.js.map +0 -1
- package/dist/chunk-EHISUJFN.js.map +0 -1
- package/dist/chunk-MDBZTH7N.js.map +0 -1
- package/dist/chunk-OAHOL3ZC.js.map +0 -1
- package/dist/chunk-PLG6JHQS.js.map +0 -1
- package/dist/chunk-Q6D75LVH.js.map +0 -1
- package/dist/chunk-XGTBNVIU.js.map +0 -1
- package/dist/chunk-ZA2S2VLL.js.map +0 -1
- package/dist/first-start-migration-PG5HBC3K.js.map +0 -1
- /package/dist/{capsule-crypto-GWVG7LGC.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-OWHERGF2.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-BIA7ELIB.js.map → chunk-36SEDFAU.js.map} +0 -0
- /package/dist/{chunk-UQSVUEN2.js.map → chunk-37436GML.js.map} +0 -0
- /package/dist/{chunk-RRHHQYJS.js.map → chunk-3TSTGI6F.js.map} +0 -0
- /package/dist/{chunk-5OOA7SGX.js.map → chunk-4ESAR77N.js.map} +0 -0
- /package/dist/{chunk-FO3HO52S.js.map → chunk-4H4F3BFL.js.map} +0 -0
- /package/dist/{chunk-BEUCACTV.js.map → chunk-4NOI577P.js.map} +0 -0
- /package/dist/{chunk-BBIE7W52.js.map → chunk-4NR2PDZ5.js.map} +0 -0
- /package/dist/{chunk-WGTWGDR5.js.map → chunk-76NHR6ZL.js.map} +0 -0
- /package/dist/{chunk-PKMQK2WY.js.map → chunk-773DPI6T.js.map} +0 -0
- /package/dist/{chunk-MQVKHFIU.js.map → chunk-7RLCCNHU.js.map} +0 -0
- /package/dist/{chunk-EZNDNMRH.js.map → chunk-AQLOP5F4.js.map} +0 -0
- /package/dist/{chunk-UNNDFYUX.js.map → chunk-ARV3AUOM.js.map} +0 -0
- /package/dist/{chunk-NPL7VDM6.js.map → chunk-ASKXIAHD.js.map} +0 -0
- /package/dist/{chunk-K4ZNARDO.js.map → chunk-CBWI22JN.js.map} +0 -0
- /package/dist/{chunk-ZOHMPFOB.js.map → chunk-CKFLLAIS.js.map} +0 -0
- /package/dist/{chunk-JPUT5NGH.js.map → chunk-DMQWV6SQ.js.map} +0 -0
- /package/dist/{chunk-F3XZN24Z.js.map → chunk-DRUDC2UX.js.map} +0 -0
- /package/dist/{chunk-KJR66XOM.js.map → chunk-EY6MDMHK.js.map} +0 -0
- /package/dist/{chunk-3TUT4PWB.js.map → chunk-FVZ2ISIX.js.map} +0 -0
- /package/dist/{chunk-6SQQOJ4U.js.map → chunk-GKRLPHPU.js.map} +0 -0
- /package/dist/{chunk-5NWYQUQC.js.map → chunk-HOHVIVDR.js.map} +0 -0
- /package/dist/{chunk-6WAJQKBS.js.map → chunk-JYLFZWUX.js.map} +0 -0
- /package/dist/{chunk-5W7CTZ2E.js.map → chunk-KQAFEZQX.js.map} +0 -0
- /package/dist/{chunk-UKTDXTWX.js.map → chunk-M4AYVEWS.js.map} +0 -0
- /package/dist/{chunk-YTZ3VGC7.js.map → chunk-M4V2JVSZ.js.map} +0 -0
- /package/dist/{chunk-TA3Y6GEX.js.map → chunk-MLKWJOAJ.js.map} +0 -0
- /package/dist/{chunk-DQXPJ6HD.js.map → chunk-O6YH5F47.js.map} +0 -0
- /package/dist/{chunk-53EEQYE5.js.map → chunk-UTHQPSRC.js.map} +0 -0
- /package/dist/{chunk-BB57OZWC.js.map → chunk-UVPL565C.js.map} +0 -0
- /package/dist/{chunk-6AFVIOVZ.js.map → chunk-V4GNSDFH.js.map} +0 -0
- /package/dist/{chunk-UVYI6VIX.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-V7CMKMHM.js.map → chunk-Y4YD5TYW.js.map} +0 -0
- /package/dist/{chunk-7NS7AWMZ.js.map → chunk-YGIQORJ3.js.map} +0 -0
- /package/dist/{chunk-3MS3QVX6.js.map → chunk-YOLZS7NY.js.map} +0 -0
package/src/chat/chat-session.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* *about* memories does not create memories of the conversation itself.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { mkdir, readFile, appendFile, readdir, stat, unlink } from "node:fs/promises";
|
|
10
|
+
import { mkdir, readFile, appendFile, readdir, stat, unlink, open } from "node:fs/promises";
|
|
11
|
+
import { constants as fsConstants } from "node:fs";
|
|
11
12
|
import { join } from "node:path";
|
|
12
13
|
import { randomUUID } from "node:crypto";
|
|
13
14
|
|
|
@@ -40,6 +41,36 @@ export function chatSessionFile(memoryDir: string, chatSessionId: string): strin
|
|
|
40
41
|
return join(chatSessionDir(memoryDir), `${chatSessionId}.jsonl`);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Monotonic transcript seq (issue #1687 review — strictly increasing)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Last emitted transcript `seq`. Seeded from `Date.now()` on first use and
|
|
50
|
+
* incremented when two appends land in the same millisecond, so `seq` is
|
|
51
|
+
* strictly increasing within a process (review thread: `Date.now()` alone is
|
|
52
|
+
* not monotonic — same-ms entries would share a seq and break the SSE
|
|
53
|
+
* reconnect dedup-by-seq contract). Across process restarts the counter is
|
|
54
|
+
* raised above the on-disk max by `loadChatSession` (issue #1718), so a
|
|
55
|
+
* backward clock step (NTP, VM migration, manual change) cannot re-seed it
|
|
56
|
+
* below lines already persisted for a resumed session.
|
|
57
|
+
*/
|
|
58
|
+
let lastTranscriptSeq = 0;
|
|
59
|
+
|
|
60
|
+
function nextTranscriptSeq(): number {
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
lastTranscriptSeq = now > lastTranscriptSeq ? now : lastTranscriptSeq + 1;
|
|
63
|
+
return lastTranscriptSeq;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Test-only: reset the module-level transcript seq counter so a regression
|
|
68
|
+
* test can deterministically simulate a fresh process restart (issue #1718).
|
|
69
|
+
*/
|
|
70
|
+
export function __resetTranscriptSeqForTest(): void {
|
|
71
|
+
lastTranscriptSeq = 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
/**
|
|
44
75
|
* Create a new chat session. The session binds the caller's principal and
|
|
45
76
|
* sessionKey at creation (rule 42); every tool call flows through
|
|
@@ -154,6 +185,19 @@ export async function loadChatSession(
|
|
|
154
185
|
// tolerated, never crash the engine).
|
|
155
186
|
}
|
|
156
187
|
}
|
|
188
|
+
// Issue #1718 — backward-clock-skew robustness: raise the module-level seq
|
|
189
|
+
// counter so the next append is strictly greater than any line already on
|
|
190
|
+
// disk for this session, regardless of clock direction across restarts
|
|
191
|
+
// (NTP step, VM migration, manual change). Without this, a backward clock
|
|
192
|
+
// skew would re-seed the counter below the on-disk max and the SSE
|
|
193
|
+
// reconnect dedup-by-seq contract could drop freshly-appended entries as
|
|
194
|
+
// duplicates. The transcript is already fully parsed above, so this is a
|
|
195
|
+
// single pass over the in-memory array.
|
|
196
|
+
for (const e of transcript) {
|
|
197
|
+
if (typeof e.seq === "number" && e.seq + 1 > lastTranscriptSeq) {
|
|
198
|
+
lastTranscriptSeq = e.seq + 1;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
157
201
|
return {
|
|
158
202
|
id: chatSessionId,
|
|
159
203
|
principal,
|
|
@@ -169,25 +213,130 @@ export async function loadChatSession(
|
|
|
169
213
|
|
|
170
214
|
/**
|
|
171
215
|
* Append a transcript entry to the session file (atomic append, rule 54).
|
|
216
|
+
*
|
|
217
|
+
* After the append resolves, in-process subscribers (open SSE connections —
|
|
218
|
+
* issue #1687) are notified synchronously so live clients receive the new
|
|
219
|
+
* entry without polling. Listener errors are swallowed so a misbehaving
|
|
220
|
+
* subscriber can never break a transcript append (rule 54 — atomic + safe).
|
|
172
221
|
*/
|
|
173
222
|
export async function appendTranscriptEntry(
|
|
174
223
|
memoryDir: string,
|
|
175
224
|
chatSessionId: string,
|
|
176
225
|
entry: Omit<ChatTranscriptEntry, "seq" | "ts">,
|
|
177
226
|
): Promise<ChatTranscriptEntry> {
|
|
227
|
+
const filePath = chatSessionFile(memoryDir, chatSessionId);
|
|
178
228
|
const full: ChatTranscriptEntry = {
|
|
179
229
|
...entry,
|
|
180
|
-
seq:
|
|
230
|
+
seq: nextTranscriptSeq(),
|
|
181
231
|
ts: new Date().toISOString(),
|
|
182
232
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
233
|
+
// Atomic resurrection guard (codex P2): open with O_APPEND but WITHOUT
|
|
234
|
+
// O_CREAT so a session already unlinked by a concurrent TTL sweep fails at
|
|
235
|
+
// open-time (ENOENT) — there is no stat->appendFile TOCTOU. A headerless
|
|
236
|
+
// recreation would load with no principal binding and be treated as public
|
|
237
|
+
// by sessionBelongsToPrincipal. A concurrent unlink during the append is
|
|
238
|
+
// caught by the post-append re-stat below so the turn fails visibly rather
|
|
239
|
+
// than writing to an orphaned inode and returning 200.
|
|
240
|
+
let fh;
|
|
241
|
+
try {
|
|
242
|
+
fh = await open(filePath, fsConstants.O_APPEND | fsConstants.O_WRONLY);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
// Only ENOENT (session unlinked by a concurrent TTL sweep) maps to
|
|
245
|
+
// chat_session_expired; real fs errors (EACCES, EMFILE, EISDIR, ...)
|
|
246
|
+
// are rethrown so the caller surfaces the actual failure (codex P2).
|
|
247
|
+
if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
248
|
+
throw new Error("chat_session_expired");
|
|
249
|
+
}
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
await fh.appendFile(JSON.stringify(full) + "\n", "utf8");
|
|
254
|
+
} finally {
|
|
255
|
+
await fh.close();
|
|
256
|
+
}
|
|
257
|
+
// Post-append re-check (codex P2): if a concurrent TTL sweep unlinked the
|
|
258
|
+
// session between open() and here, the append landed on the orphaned inode
|
|
259
|
+
// and the directory no longer references it. Re-stat the PATH (not the fd)
|
|
260
|
+
// so the turn fails with chat_session_expired instead of returning 200 with
|
|
261
|
+
// a reply the client cannot resume. A later sweep unlinking an over-TTL
|
|
262
|
+
// session is a legitimate TTL action, not a silent loss.
|
|
263
|
+
try {
|
|
264
|
+
await stat(filePath);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
// Only ENOENT (concurrent TTL unlink) is session expiry; other fs
|
|
267
|
+
// errors (EACCES, EMFILE, ENOTDIR, ...) are rethrown (codex/kilo P2).
|
|
268
|
+
if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
269
|
+
throw new Error("chat_session_expired");
|
|
270
|
+
}
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
notifyTranscriptListeners(memoryDir, chatSessionId, full);
|
|
188
274
|
return full;
|
|
189
275
|
}
|
|
190
276
|
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// In-process transcript pub/sub (issue #1687 SSE push)
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Per-session listener sets, keyed by `memoryDir + chatSessionId` so two
|
|
283
|
+
* memory stores sharing a legacy/imported session id cannot leak transcript
|
|
284
|
+
* entries to each other (AGENTS.md State Scoping — module-level singletons
|
|
285
|
+
* are scoped per store). Kept module-level so appendTranscriptEntry can fan
|
|
286
|
+
* out new entries to open SSE connections regardless of which caller
|
|
287
|
+
* appended (HTTP, MCP, or CLI). Empty sets are pruned so the map never
|
|
288
|
+
* grows unbounded (rule 47 — no leak).
|
|
289
|
+
*/
|
|
290
|
+
const transcriptListeners = new Map<string, Set<(entry: ChatTranscriptEntry) => void>>();
|
|
291
|
+
|
|
292
|
+
function transcriptListenerKey(memoryDir: string, chatSessionId: string): string {
|
|
293
|
+
return `${memoryDir}\x00${chatSessionId}`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function notifyTranscriptListeners(memoryDir: string, chatSessionId: string, entry: ChatTranscriptEntry): void {
|
|
297
|
+
const set = transcriptListeners.get(transcriptListenerKey(memoryDir, chatSessionId));
|
|
298
|
+
if (!set) return;
|
|
299
|
+
for (const fn of set) {
|
|
300
|
+
try {
|
|
301
|
+
fn(entry);
|
|
302
|
+
} catch {
|
|
303
|
+
// A listener must never break the append path (rule 54).
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Subscribe to live transcript entries for a chat session in a given memory
|
|
310
|
+
* store. Returns an unsubscribe function that removes the listener and
|
|
311
|
+
* prunes the session's set when it becomes empty. Used by the SSE handler
|
|
312
|
+
* to push new user/assistant entries to connected clients (issue #1687).
|
|
313
|
+
*
|
|
314
|
+
* The `memoryDir` scopes the subscription to its store so a process hosting
|
|
315
|
+
* two Remnic services cannot cross-deliver transcript entries (AGENTS.md
|
|
316
|
+
* State Scoping).
|
|
317
|
+
*/
|
|
318
|
+
export function subscribeChatTranscript(
|
|
319
|
+
memoryDir: string,
|
|
320
|
+
chatSessionId: string,
|
|
321
|
+
listener: (entry: ChatTranscriptEntry) => void,
|
|
322
|
+
): () => void {
|
|
323
|
+
const key = transcriptListenerKey(memoryDir, chatSessionId);
|
|
324
|
+
let set = transcriptListeners.get(key);
|
|
325
|
+
if (!set) {
|
|
326
|
+
set = new Set();
|
|
327
|
+
transcriptListeners.set(key, set);
|
|
328
|
+
}
|
|
329
|
+
set.add(listener);
|
|
330
|
+
return () => {
|
|
331
|
+
const current = transcriptListeners.get(key);
|
|
332
|
+
if (!current) return;
|
|
333
|
+
current.delete(listener);
|
|
334
|
+
if (current.size === 0) {
|
|
335
|
+
transcriptListeners.delete(key);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
191
340
|
/**
|
|
192
341
|
* Append a pending-plan marker so a later turn can confirm the plan
|
|
193
342
|
* (append-only state — loadChatSession scans for the latest unresolved one).
|
|
@@ -271,6 +420,16 @@ export async function cleanupExpiredChatSessions(
|
|
|
271
420
|
// boundary is inclusive; sub-millisecond APFS mtime skew is negligible
|
|
272
421
|
// against an hour-scale threshold (rule 54 — deterministic cleanup).
|
|
273
422
|
if (ttlHours <= 0 || ageHours >= ttlHours) {
|
|
423
|
+
// Re-check mtime immediately before unlinking so a turn appended
|
|
424
|
+
// mid-sweep (which refreshes mtime) is not deleted by the stale age
|
|
425
|
+
// decision captured above (codex P2). ttlHours <= 0 (expire-all)
|
|
426
|
+
// skips the re-check. The residual microsecond window is a
|
|
427
|
+
// legitimate sweep of an over-TTL file.
|
|
428
|
+
if (ttlHours > 0) {
|
|
429
|
+
const fresh = await stat(filePath);
|
|
430
|
+
const freshAge = (Date.now() - fresh.mtimeMs) / (1000 * 60 * 60);
|
|
431
|
+
if (freshAge < ttlHours) continue;
|
|
432
|
+
}
|
|
274
433
|
await unlink(filePath);
|
|
275
434
|
removed++;
|
|
276
435
|
}
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { access, lstat, readFile, readdir, realpath, unlink } from "node:fs/prom
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import type { Readable, Writable } from "node:stream";
|
|
6
6
|
import type { Orchestrator } from "./orchestrator.js";
|
|
7
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
7
8
|
import { ThreadingManager } from "./threading.js";
|
|
8
9
|
import { utcDayRange } from "./transcript.js";
|
|
9
10
|
import { runWearablesCliCommand } from "./wearables/cli.js";
|
|
@@ -3728,7 +3729,7 @@ export function registerCli(
|
|
|
3728
3729
|
console.log("=== Extraction Judge Verdict Stats ===\n");
|
|
3729
3730
|
console.log(`Total verdicts: ${stats.total}`);
|
|
3730
3731
|
if (stats.total === 0) {
|
|
3731
|
-
if (!orchestrator.config.
|
|
3732
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).extractionJudgeTelemetry) {
|
|
3732
3733
|
console.log(
|
|
3733
3734
|
"\nNote: extractionJudgeTelemetryEnabled is OFF. Enable it in plugin config to collect verdict telemetry.",
|
|
3734
3735
|
);
|
|
@@ -8541,7 +8542,7 @@ export function registerCli(
|
|
|
8541
8542
|
config: orchestrator.config,
|
|
8542
8543
|
memoryDir: orchestrator.config.memoryDir,
|
|
8543
8544
|
embeddingLookupFactory: (storage) => {
|
|
8544
|
-
if (!orchestrator.config.
|
|
8545
|
+
if (!resolveMemoryLifecycleCapabilities(orchestrator.config).embeddingFallback) return undefined;
|
|
8545
8546
|
return async (content: string, limit: number) => {
|
|
8546
8547
|
try {
|
|
8547
8548
|
return await orchestrator.semanticDedupLookup(content, limit, storage);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated alias subpath `@remnic/core/codex-cli-fallback`.
|
|
3
|
+
*
|
|
4
|
+
* PR #1638 renamed `src/codex-cli-fallback.ts` -> `src/cli-fallback.ts` to
|
|
5
|
+
* graduate the host-prefixed name per CLAUDE.md rule 31 (generic @remnic/core
|
|
6
|
+
* modules must not carry a host prefix). This thin re-export keeps the old
|
|
7
|
+
* subpath resolving during the compat window so external consumers importing
|
|
8
|
+
* `@remnic/core/codex-cli-fallback` do not hit ERR_PACKAGE_PATH_NOT_EXPORTED.
|
|
9
|
+
* It forwards every export to the canonical module -- no logic is duplicated.
|
|
10
|
+
*
|
|
11
|
+
* Removal timeline: v10.0.0 (see issue #1643).
|
|
12
|
+
* Import from `@remnic/core/cli-fallback` instead.
|
|
13
|
+
*/
|
|
14
|
+
export * from "../cli-fallback.js";
|
|
15
|
+
|
|
16
|
+
process.emitWarning(
|
|
17
|
+
"@remnic/core/codex-cli-fallback is a deprecated alias; import from @remnic/core/cli-fallback instead. Removed in v10.0.0 (issue #1643).",
|
|
18
|
+
{
|
|
19
|
+
type: "DeprecationWarning",
|
|
20
|
+
code: "REMNIC_DEP_CORE_SUBPATH_CODEX_CLI_FALLBACK",
|
|
21
|
+
detail: "PR #1638 renamed codex-cli-fallback.ts to cli-fallback.ts.",
|
|
22
|
+
},
|
|
23
|
+
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import * as canonicalCliFallback from "../cli-fallback.js";
|
|
7
|
+
import * as canonicalThreadKey from "../thread-key.js";
|
|
8
|
+
import * as aliasCliFallback from "./codex-cli-fallback.js";
|
|
9
|
+
import * as aliasThreadKey from "./codex-thread-key.js";
|
|
10
|
+
|
|
11
|
+
const pkgJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
|
|
12
|
+
|
|
13
|
+
test("codex-cli-fallback alias re-exports the same value symbols as cli-fallback", () => {
|
|
14
|
+
// export * forwards live bindings by reference, so the function objects and
|
|
15
|
+
// the test-hooks namespace are identical (===) across both subpaths.
|
|
16
|
+
assert.equal(
|
|
17
|
+
aliasCliFallback.callCodexCliFallback,
|
|
18
|
+
canonicalCliFallback.callCodexCliFallback,
|
|
19
|
+
);
|
|
20
|
+
assert.equal(
|
|
21
|
+
aliasCliFallback.setCodexCliFallbackRunnerForProcess,
|
|
22
|
+
canonicalCliFallback.setCodexCliFallbackRunnerForProcess,
|
|
23
|
+
);
|
|
24
|
+
assert.equal(
|
|
25
|
+
aliasCliFallback.__codexCliFallbackTestHooks,
|
|
26
|
+
canonicalCliFallback.__codexCliFallbackTestHooks,
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("codex-thread-key alias re-exports the same symbol as thread-key", () => {
|
|
31
|
+
assert.equal(
|
|
32
|
+
aliasThreadKey.CODEX_THREAD_KEY_PREFIX,
|
|
33
|
+
canonicalThreadKey.CODEX_THREAD_KEY_PREFIX,
|
|
34
|
+
);
|
|
35
|
+
assert.equal(aliasThreadKey.CODEX_THREAD_KEY_PREFIX, "codex-thread:");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("every named runtime export of cli-fallback is forwarded by the alias", () => {
|
|
39
|
+
for (const key of Object.keys(canonicalCliFallback)) {
|
|
40
|
+
assert.equal(
|
|
41
|
+
key in aliasCliFallback,
|
|
42
|
+
true,
|
|
43
|
+
`alias missing forwarded export: ${key}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("importing the old subpath resolves to the same symbol as the new subpath", async () => {
|
|
49
|
+
// @remnic/core self-references its own exports map. Under --conditions=
|
|
50
|
+
// remnic-source (set by preflight/test NODE_OPTIONS) the subpaths resolve to
|
|
51
|
+
// src/. Both specifiers land on the same module record, so the exported
|
|
52
|
+
// function is the same object reference.
|
|
53
|
+
const [byNew, byOld] = await Promise.all([
|
|
54
|
+
import("@remnic/core/cli-fallback"),
|
|
55
|
+
import("@remnic/core/codex-cli-fallback"),
|
|
56
|
+
]);
|
|
57
|
+
assert.equal(byOld.callCodexCliFallback, byNew.callCodexCliFallback);
|
|
58
|
+
|
|
59
|
+
const [newKey, oldKey] = await Promise.all([
|
|
60
|
+
import("@remnic/core/thread-key"),
|
|
61
|
+
import("@remnic/core/codex-thread-key"),
|
|
62
|
+
]);
|
|
63
|
+
assert.equal(oldKey.CODEX_THREAD_KEY_PREFIX, newKey.CODEX_THREAD_KEY_PREFIX);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("the deprecated subpaths are registered with a removal timeline in the exports map", () => {
|
|
67
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as {
|
|
68
|
+
exports: Record<string, { deprecated?: string; import?: string }>;
|
|
69
|
+
};
|
|
70
|
+
const aliasEntries = [
|
|
71
|
+
"./codex-cli-fallback",
|
|
72
|
+
"./codex-cli-fallback.js",
|
|
73
|
+
"./codex-thread-key",
|
|
74
|
+
"./codex-thread-key.js",
|
|
75
|
+
];
|
|
76
|
+
for (const subpath of aliasEntries) {
|
|
77
|
+
const entry = pkg.exports[subpath];
|
|
78
|
+
assert.ok(entry, `missing exports entry for ${subpath}`);
|
|
79
|
+
assert.ok(
|
|
80
|
+
entry.deprecated,
|
|
81
|
+
`exports entry for ${subpath} missing a deprecated field`,
|
|
82
|
+
);
|
|
83
|
+
assert.match(
|
|
84
|
+
entry.deprecated,
|
|
85
|
+
/v?10\.0\.0|remov/i,
|
|
86
|
+
`deprecated field for ${subpath} should state a removal timeline`,
|
|
87
|
+
);
|
|
88
|
+
assert.ok(
|
|
89
|
+
entry.import?.includes("/compat/"),
|
|
90
|
+
`exports entry for ${subpath} should target the compat re-export module`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deprecated alias subpath `@remnic/core/codex-thread-key`.
|
|
3
|
+
*
|
|
4
|
+
* PR #1638 renamed `src/codex-thread-key.ts` -> `src/thread-key.ts` to
|
|
5
|
+
* graduate the host-prefixed name per CLAUDE.md rule 31. This thin re-export
|
|
6
|
+
* keeps the old subpath resolving during the compat window so external
|
|
7
|
+
* consumers importing `@remnic/core/codex-thread-key` do not hit
|
|
8
|
+
* ERR_PACKAGE_PATH_NOT_EXPORTED. It forwards every export to the canonical
|
|
9
|
+
* module -- no logic is duplicated.
|
|
10
|
+
*
|
|
11
|
+
* Removal timeline: v10.0.0 (see issue #1643).
|
|
12
|
+
* Import from `@remnic/core/thread-key` instead.
|
|
13
|
+
*/
|
|
14
|
+
export * from "../thread-key.js";
|
|
15
|
+
|
|
16
|
+
process.emitWarning(
|
|
17
|
+
"@remnic/core/codex-thread-key is a deprecated alias; import from @remnic/core/thread-key instead. Removed in v10.0.0 (issue #1643).",
|
|
18
|
+
{
|
|
19
|
+
type: "DeprecationWarning",
|
|
20
|
+
code: "REMNIC_DEP_CORE_SUBPATH_CODEX_THREAD_KEY",
|
|
21
|
+
detail: "PR #1638 renamed codex-thread-key.ts to thread-key.ts.",
|
|
22
|
+
},
|
|
23
|
+
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { log } from "./logger.js";
|
|
4
|
+
import { resolveMemoryLifecycleCapabilities } from "./capabilities.js";
|
|
4
5
|
import { readEnvVar } from "./runtime/env.js";
|
|
5
6
|
import type { PluginConfig } from "./types.js";
|
|
6
7
|
import {
|
|
@@ -397,7 +398,7 @@ export class EmbeddingFallback {
|
|
|
397
398
|
private async resolveProvider(
|
|
398
399
|
options: { includeHost?: boolean } = {},
|
|
399
400
|
): Promise<ProviderConfig | null> {
|
|
400
|
-
if (!this.config.
|
|
401
|
+
if (!resolveMemoryLifecycleCapabilities(this.config).embeddingFallback) return null;
|
|
401
402
|
|
|
402
403
|
if (
|
|
403
404
|
options.includeHost !== false &&
|
|
@@ -26,6 +26,7 @@ import type { FaithfulnessResult } from "./extraction-faithfulness.js";
|
|
|
26
26
|
import type { FallbackLlmClient } from "./fallback-llm.js";
|
|
27
27
|
import type { LocalLlmClient } from "./local-llm.js";
|
|
28
28
|
import type { MemoryFrontmatter } from "./types.js";
|
|
29
|
+
import { callOpenAiCompatibleChat } from "./local-model-endpoint.js";
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
// Helpers — stub LLM clients
|
|
@@ -1429,3 +1430,242 @@ test("checkFaithfulnessBatch: local-endpoint failure does NOT leak the local mod
|
|
|
1429
1430
|
);
|
|
1430
1431
|
assert.equal(result.results[0]?.ok, true);
|
|
1431
1432
|
});
|
|
1433
|
+
|
|
1434
|
+
// ---------------------------------------------------------------------------
|
|
1435
|
+
// Issue #1700 nits #5 + #6: probe signal forwarding + local parse-fallback
|
|
1436
|
+
// ---------------------------------------------------------------------------
|
|
1437
|
+
|
|
1438
|
+
test("callOpenAiCompatibleChat: a pre-aborted batch signal aborts the probe immediately, not after timeoutMs (#1700 nit #5)", async () => {
|
|
1439
|
+
// The batch signal is ALREADY aborted when the probe starts. With nit #5 the
|
|
1440
|
+
// probe forwards it and aborts instantly; without it the probe would wait for
|
|
1441
|
+
// its own timeoutMs (5000ms here). Assert the probe returns null fast.
|
|
1442
|
+
const endpoint = { baseUrl: "http://localhost:11434/v1", model: "remnic-faithfulness-gate-v1" };
|
|
1443
|
+
const batchController = new AbortController();
|
|
1444
|
+
batchController.abort();
|
|
1445
|
+
const spy = ((_url: string, init: RequestInit) =>
|
|
1446
|
+
new Promise<Response>((_resolve, reject) => {
|
|
1447
|
+
const sig = init.signal;
|
|
1448
|
+
if (!sig) return reject(new Error("no signal"));
|
|
1449
|
+
if (sig.aborted) return reject(new Error("aborted"));
|
|
1450
|
+
sig.addEventListener("abort", () => reject(new Error("aborted")), { once: true });
|
|
1451
|
+
})) as unknown as typeof fetch;
|
|
1452
|
+
const start = Date.now();
|
|
1453
|
+
const result = await callOpenAiCompatibleChat(
|
|
1454
|
+
endpoint,
|
|
1455
|
+
[{ role: "user", content: "hi" }],
|
|
1456
|
+
{ timeoutMs: 5000, signal: batchController.signal },
|
|
1457
|
+
spy,
|
|
1458
|
+
);
|
|
1459
|
+
const elapsed = Date.now() - start;
|
|
1460
|
+
assert.equal(result, null, "a pre-aborted signal must fail the probe closed (null)");
|
|
1461
|
+
assert.ok(
|
|
1462
|
+
elapsed < 1000,
|
|
1463
|
+
`probe must abort immediately on a pre-aborted signal (${elapsed}ms), not wait timeoutMs`,
|
|
1464
|
+
);
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
test("checkFaithfulnessBatch: default surfaces malformed_output for a 200-with-garbage local response (#1700 nit #6)", async () => {
|
|
1468
|
+
// extractionFaithfulnessLocalParseFallback unset (default false): a local
|
|
1469
|
+
// endpoint returning 200 with non-verdict JSON surfaces malformed_output so a
|
|
1470
|
+
// misconfigured endpoint is visible. The configured chain must NOT run.
|
|
1471
|
+
const inputs = [{ factText: "Fact", quote: "Quote" }];
|
|
1472
|
+
const config = parseConfig({
|
|
1473
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
1474
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
1475
|
+
extractionFaithfulnessTimeoutMs: 5000,
|
|
1476
|
+
});
|
|
1477
|
+
const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
|
|
1478
|
+
const { fetch: garbageFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
|
|
1479
|
+
model: "remnic-faithfulness-gate-v1",
|
|
1480
|
+
choices: [{ message: { content: "this is not a verdict array" } }],
|
|
1481
|
+
}));
|
|
1482
|
+
const result = await checkFaithfulnessBatch(
|
|
1483
|
+
inputs,
|
|
1484
|
+
config,
|
|
1485
|
+
null,
|
|
1486
|
+
stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
|
|
1487
|
+
garbageFetch,
|
|
1488
|
+
);
|
|
1489
|
+
assert.equal(fallbackCalls.length, 0, "chain must NOT run when flag is off — garbage surfaces loudly");
|
|
1490
|
+
assert.equal(result.results[0]?.ok, false);
|
|
1491
|
+
if (!result.results[0]?.ok) {
|
|
1492
|
+
assert.equal(result.results[0].error.code, "malformed_output");
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back to the chain on local parse failure (#1700 nit #6)", async () => {
|
|
1497
|
+
// Flag ON: the same 200-with-garbage local response now falls through to the
|
|
1498
|
+
// configured chain instead of surfacing malformed_output (resilient fallback).
|
|
1499
|
+
const inputs = [{ factText: "Fact", quote: "Quote" }];
|
|
1500
|
+
const config = parseConfig({
|
|
1501
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
1502
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
1503
|
+
extractionFaithfulnessTimeoutMs: 5000,
|
|
1504
|
+
extractionFaithfulnessLocalParseFallback: true,
|
|
1505
|
+
});
|
|
1506
|
+
const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
|
|
1507
|
+
const { fetch: garbageFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
|
|
1508
|
+
model: "remnic-faithfulness-gate-v1",
|
|
1509
|
+
choices: [{ message: { content: "this is not a verdict array" } }],
|
|
1510
|
+
}));
|
|
1511
|
+
const result = await checkFaithfulnessBatch(
|
|
1512
|
+
inputs,
|
|
1513
|
+
config,
|
|
1514
|
+
null,
|
|
1515
|
+
stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
|
|
1516
|
+
garbageFetch,
|
|
1517
|
+
);
|
|
1518
|
+
assert.equal(
|
|
1519
|
+
fallbackCalls.length,
|
|
1520
|
+
1,
|
|
1521
|
+
"chain MUST run when flag is on and the local response is unparseable",
|
|
1522
|
+
);
|
|
1523
|
+
assert.equal(result.results[0]?.ok, true);
|
|
1524
|
+
if (result.results[0]?.ok) {
|
|
1525
|
+
assert.equal(result.results[0].verdict, "entailed", "fallback chain verdict is used");
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
|
|
1529
|
+
test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback does NOT fall back when the local response is valid (#1700 nit #6 regression)", async () => {
|
|
1530
|
+
// Flag ON but the local response IS a valid verdict array → use it, do not
|
|
1531
|
+
// call the chain. Guards against the flag over-triggering on good responses.
|
|
1532
|
+
const inputs = [{ factText: "Fact", quote: "Quote" }];
|
|
1533
|
+
const config = parseConfig({
|
|
1534
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
1535
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
1536
|
+
extractionFaithfulnessTimeoutMs: 5000,
|
|
1537
|
+
extractionFaithfulnessLocalParseFallback: true,
|
|
1538
|
+
});
|
|
1539
|
+
const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
|
|
1540
|
+
const { fetch: goodFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
|
|
1541
|
+
model: "remnic-faithfulness-gate-v1",
|
|
1542
|
+
choices: [{ message: { content: JSON.stringify([{ index: 0, verdict: "contradicted" }]) } }],
|
|
1543
|
+
}));
|
|
1544
|
+
const result = await checkFaithfulnessBatch(
|
|
1545
|
+
inputs,
|
|
1546
|
+
config,
|
|
1547
|
+
null,
|
|
1548
|
+
stubFallbackLlm(JSON.stringify([{ index: 0, verdict: "entailed" }]), fallbackCalls),
|
|
1549
|
+
goodFetch,
|
|
1550
|
+
);
|
|
1551
|
+
assert.equal(fallbackCalls.length, 0, "chain must NOT run when the local response is valid, even with the flag on");
|
|
1552
|
+
assert.equal(result.results[0]?.ok, true);
|
|
1553
|
+
if (result.results[0]?.ok) {
|
|
1554
|
+
assert.equal(result.results[0].verdict, "contradicted", "valid local verdict is used");
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back when the local response is a PARTIAL verdict array (codex P2 PRRT_kwDORJXyws6O6zwZ)", async () => {
|
|
1559
|
+
// Flag ON: a local endpoint that returns a valid-but-INCOMPLETE verdict
|
|
1560
|
+
// array (1 of N entries) must fall through to the configured chain, NOT be
|
|
1561
|
+
// accepted as-is. parseFaithfulnessResponse is truthy as soon as ONE entry
|
|
1562
|
+
// is valid, so the previous guard accepted the partial response and the
|
|
1563
|
+
// missing indexes surfaced as malformed_output downstream -- defeating the
|
|
1564
|
+
// resilient fallback. Accept the local response only when it covers every
|
|
1565
|
+
// expected index.
|
|
1566
|
+
const inputs = [
|
|
1567
|
+
{ factText: "Fact A", quote: "Quote A" },
|
|
1568
|
+
{ factText: "Fact B", quote: "Quote B" },
|
|
1569
|
+
];
|
|
1570
|
+
const config = parseConfig({
|
|
1571
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
1572
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
1573
|
+
extractionFaithfulnessTimeoutMs: 5000,
|
|
1574
|
+
extractionFaithfulnessLocalParseFallback: true,
|
|
1575
|
+
});
|
|
1576
|
+
const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
|
|
1577
|
+
// Local endpoint returns only index 0 -- index 1 is missing (partial).
|
|
1578
|
+
const { fetch: partialFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
|
|
1579
|
+
model: "remnic-faithfulness-gate-v1",
|
|
1580
|
+
choices: [{ message: { content: JSON.stringify([{ index: 0, verdict: "contradicted" }]) } }],
|
|
1581
|
+
}));
|
|
1582
|
+
const result = await checkFaithfulnessBatch(
|
|
1583
|
+
inputs,
|
|
1584
|
+
config,
|
|
1585
|
+
null,
|
|
1586
|
+
stubFallbackLlm(
|
|
1587
|
+
JSON.stringify([
|
|
1588
|
+
{ index: 0, verdict: "entailed" },
|
|
1589
|
+
{ index: 1, verdict: "unsupported" },
|
|
1590
|
+
]),
|
|
1591
|
+
fallbackCalls,
|
|
1592
|
+
),
|
|
1593
|
+
partialFetch,
|
|
1594
|
+
);
|
|
1595
|
+
assert.equal(
|
|
1596
|
+
fallbackCalls.length,
|
|
1597
|
+
1,
|
|
1598
|
+
"a partial local verdict array must fall through to the configured chain",
|
|
1599
|
+
);
|
|
1600
|
+
// The chain's complete verdict array is used for both indexes.
|
|
1601
|
+
assert.equal(result.results[0]?.ok && result.results[0].verdict, "entailed");
|
|
1602
|
+
assert.equal(result.results[1]?.ok && result.results[1].verdict, "unsupported");
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
test("checkFaithfulnessBatch: extractionFaithfulnessLocalParseFallback falls back when local indexes are fractional/non-contiguous (codex P2 PRRT_kwDORJXyws6O7PfY)", async () => {
|
|
1606
|
+
// Flag ON: a malformed local response with expectedCount distinct but
|
|
1607
|
+
// NON-INTEGER indexes (e.g. 0 and 0.5 for a two-item batch) must NOT be
|
|
1608
|
+
// accepted. parseFaithfulnessResponse used to admit any numeric index, so a
|
|
1609
|
+
// size-only check passed while index 1 stayed missing -> malformed_output.
|
|
1610
|
+
// parseEntries now rejects non-integer indexes, and the gate requires every
|
|
1611
|
+
// integer key 0..N-1 before accepting the local response.
|
|
1612
|
+
const inputs = [
|
|
1613
|
+
{ factText: "Fact A", quote: "Quote A" },
|
|
1614
|
+
{ factText: "Fact B", quote: "Quote B" },
|
|
1615
|
+
];
|
|
1616
|
+
const config = parseConfig({
|
|
1617
|
+
extractionFaithfulnessModel: "remnic-faithfulness-gate-v1",
|
|
1618
|
+
extractionFaithfulnessBaseUrl: "http://localhost:11434/v1",
|
|
1619
|
+
extractionFaithfulnessTimeoutMs: 5000,
|
|
1620
|
+
extractionFaithfulnessLocalParseFallback: true,
|
|
1621
|
+
});
|
|
1622
|
+
const fallbackCalls: Array<{ messages: unknown; options: unknown }> = [];
|
|
1623
|
+
// Local endpoint returns indexes 0 and 0.5 -- two distinct keys, but index 1
|
|
1624
|
+
// is missing (fractional index is invalid).
|
|
1625
|
+
const { fetch: fracFetch } = fakeFetchFor("http://localhost:11434/v1", () => ({
|
|
1626
|
+
model: "remnic-faithfulness-gate-v1",
|
|
1627
|
+
choices: [{ message: { content: JSON.stringify([
|
|
1628
|
+
{ index: 0, verdict: "contradicted" },
|
|
1629
|
+
{ index: 0.5, verdict: "entailed" },
|
|
1630
|
+
]) } }],
|
|
1631
|
+
}));
|
|
1632
|
+
const result = await checkFaithfulnessBatch(
|
|
1633
|
+
inputs,
|
|
1634
|
+
config,
|
|
1635
|
+
null,
|
|
1636
|
+
stubFallbackLlm(
|
|
1637
|
+
JSON.stringify([
|
|
1638
|
+
{ index: 0, verdict: "entailed" },
|
|
1639
|
+
{ index: 1, verdict: "unsupported" },
|
|
1640
|
+
]),
|
|
1641
|
+
fallbackCalls,
|
|
1642
|
+
),
|
|
1643
|
+
fracFetch,
|
|
1644
|
+
);
|
|
1645
|
+
assert.equal(
|
|
1646
|
+
fallbackCalls.length,
|
|
1647
|
+
1,
|
|
1648
|
+
"a fractional/non-contiguous local response must fall through to the chain",
|
|
1649
|
+
);
|
|
1650
|
+
assert.equal(result.results[0]?.ok && result.results[0].verdict, "entailed");
|
|
1651
|
+
assert.equal(result.results[1]?.ok && result.results[1].verdict, "unsupported");
|
|
1652
|
+
});
|
|
1653
|
+
|
|
1654
|
+
test('parseConfig: extractionFaithfulnessLocalParseFallback coerces CLI string "true" (#1700 review — coerceBool parity)', () => {
|
|
1655
|
+
// A CLI override reaches the parser as the string "true"; === true would
|
|
1656
|
+
// silently leave the flag disabled. coerceBool must turn it on (gotcha 36).
|
|
1657
|
+
const onByString = parseConfig({
|
|
1658
|
+
extractionFaithfulnessLocalParseFallback: "true",
|
|
1659
|
+
});
|
|
1660
|
+
assert.equal(onByString.extractionFaithfulnessLocalParseFallback, true);
|
|
1661
|
+
const onByBool = parseConfig({
|
|
1662
|
+
extractionFaithfulnessLocalParseFallback: true,
|
|
1663
|
+
});
|
|
1664
|
+
assert.equal(onByBool.extractionFaithfulnessLocalParseFallback, true);
|
|
1665
|
+
const offDefault = parseConfig({});
|
|
1666
|
+
assert.equal(offDefault.extractionFaithfulnessLocalParseFallback, false);
|
|
1667
|
+
const offByString = parseConfig({
|
|
1668
|
+
extractionFaithfulnessLocalParseFallback: "false",
|
|
1669
|
+
});
|
|
1670
|
+
assert.equal(offByString.extractionFaithfulnessLocalParseFallback, false);
|
|
1671
|
+
});
|