@remnic/core 9.3.712 → 9.3.714
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 +6 -6
- package/dist/access-boundary.js +17 -16
- package/dist/access-cli.js +40 -39
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +24 -23
- package/dist/access-mcp.d.ts +18 -5
- package/dist/access-mcp.js +21 -20
- package/dist/access-operations-batch.js +18 -17
- package/dist/access-operations.d.ts +6 -5
- package/dist/access-operations.js +20 -19
- package/dist/access-schema.d.ts +10 -0
- package/dist/access-schema.js +1 -1
- package/dist/{access-service-Dx2rSJjH.d.ts → access-service-lddjZoRh.d.ts} +20 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +16 -15
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-surface-catalog.js +6 -1
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +3 -1
- package/dist/active-memory-bridge.js +2 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- 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 +8 -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 +1 -1
- package/dist/{catalog-CadPvH29.d.ts → catalog-CbR0CsUw.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 +9 -8
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-OMSTU233.js → chunk-2KAYTPPT.js} +4 -4
- package/dist/{chunk-VYZNWH4J.js → chunk-2SXMCQEQ.js} +3 -3
- package/dist/{chunk-NXL5CVE7.js → chunk-2Y6LMOQQ.js} +2 -2
- package/dist/{chunk-AMXOCZHE.js → chunk-3VUPWDSE.js} +15 -3
- package/dist/{chunk-AMXOCZHE.js.map → chunk-3VUPWDSE.js.map} +1 -1
- package/dist/{chunk-FVQJYWH7.js → chunk-5GKPFL3S.js} +1 -1
- package/dist/{chunk-FVQJYWH7.js.map → chunk-5GKPFL3S.js.map} +1 -1
- package/dist/{chunk-LQ4J7ELC.js → chunk-67HG7KFE.js} +2 -2
- package/dist/{chunk-ZBG4K36I.js → chunk-6FUJ2VYY.js} +3 -3
- package/dist/{chunk-GMRNKPWO.js → chunk-6QUPUTT7.js} +2 -2
- package/dist/{chunk-Z56IHRVV.js → chunk-6UU43ZGD.js} +2 -2
- package/dist/{chunk-APJQ6UEA.js → chunk-AGNBY3VG.js} +4 -4
- package/dist/{chunk-OWOKOFXK.js → chunk-AJOIM6UR.js} +2 -2
- package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
- package/dist/{chunk-YVWZW37D.js → chunk-BNOAB5HH.js} +62 -12
- package/dist/{chunk-YVWZW37D.js.map → chunk-BNOAB5HH.js.map} +1 -1
- package/dist/{chunk-A2R5NV6O.js → chunk-CTAHKIG6.js} +205 -29
- package/dist/chunk-CTAHKIG6.js.map +1 -0
- package/dist/{chunk-K22IJO4R.js → chunk-CTOQEZSN.js} +2 -2
- package/dist/{chunk-SINGJCUR.js → chunk-CX77GNMB.js} +14 -3
- package/dist/chunk-CX77GNMB.js.map +1 -0
- package/dist/{chunk-HSDJCT3V.js → chunk-DXGS5YOQ.js} +3 -3
- package/dist/{chunk-HV57RHMD.js → chunk-EH5ED3C6.js} +2 -2
- package/dist/{chunk-NUWZFSKC.js → chunk-FVZ2ISIX.js} +9 -1
- package/dist/chunk-FVZ2ISIX.js.map +1 -0
- package/dist/{chunk-GKPIY7EQ.js → chunk-FXZKXW7C.js} +117 -46
- package/dist/chunk-FXZKXW7C.js.map +1 -0
- package/dist/{chunk-7NDYFAJS.js → chunk-GM3BMWKR.js} +2 -2
- package/dist/{chunk-ZVDRL6SK.js → chunk-HHLWSWWF.js} +2 -2
- package/dist/chunk-HHLWSWWF.js.map +1 -0
- package/dist/{chunk-5QUS24F7.js → chunk-I56LKMF7.js} +2 -2
- package/dist/{chunk-Z3B2YW56.js → chunk-IHMT6XTY.js} +9 -5
- package/dist/chunk-IHMT6XTY.js.map +1 -0
- package/dist/chunk-JAKIDQB2.js +173 -0
- package/dist/chunk-JAKIDQB2.js.map +1 -0
- package/dist/{chunk-35YJ6KCV.js → chunk-JEIROMLX.js} +2 -2
- package/dist/{chunk-SKQCFAYU.js → chunk-JVEMUX6L.js} +2 -2
- package/dist/{chunk-CXMXAC5R.js → chunk-K44MIN6D.js} +3 -3
- package/dist/{chunk-5KQCOIPW.js → chunk-MECU6WSH.js} +188 -31
- package/dist/chunk-MECU6WSH.js.map +1 -0
- package/dist/{chunk-BQCFXAMN.js → chunk-MPHDVHIB.js} +2 -2
- package/dist/{chunk-BOGENF7P.js → chunk-MSVSP5VO.js} +1 -1
- package/dist/chunk-MSVSP5VO.js.map +1 -0
- package/dist/{chunk-QXNFQKWU.js → chunk-MTHNJCOJ.js} +2 -2
- package/dist/{chunk-3TCRU4JA.js → chunk-O62P2LFL.js} +2 -2
- package/dist/{chunk-JSDZMOT7.js → chunk-OC56UWV5.js} +11 -11
- package/dist/{chunk-7IBEWQLG.js → chunk-QOIONSOI.js} +2 -2
- package/dist/{chunk-4P5UAL3C.js → chunk-RBHANGVX.js} +6 -6
- package/dist/{chunk-SK2CR6MW.js → chunk-RVLB4E6N.js} +2 -2
- package/dist/{chunk-I3BT2IDW.js → chunk-SYDHFFAW.js} +1110 -28
- package/dist/chunk-SYDHFFAW.js.map +1 -0
- package/dist/{chunk-VILEUJXC.js → chunk-UC4YOAC2.js} +74 -2
- package/dist/{chunk-VILEUJXC.js.map → chunk-UC4YOAC2.js.map} +1 -1
- package/dist/{chunk-CNVIWMQI.js → chunk-UY4JMEAK.js} +2 -2
- package/dist/{chunk-S6FQLQGH.js → chunk-V7GDTQ6I.js} +2 -2
- package/dist/{chunk-JNOYYWCA.js → chunk-VO4UICII.js} +3 -3
- package/dist/{chunk-54TI5GLV.js → chunk-WI4QX7GH.js} +3 -3
- package/dist/{chunk-3XHD3XGK.js → chunk-XCTDHF7U.js} +30 -2
- package/dist/chunk-XCTDHF7U.js.map +1 -0
- package/dist/{chunk-XKU4YE6Z.js → chunk-XZTUMWKQ.js} +2 -2
- package/dist/{cli-DTWRoJEI.d.ts → cli-iAiVkbYJ.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +43 -42
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +8 -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 +4 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +8 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +9 -8
- 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/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +8 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +3 -2
- package/dist/extraction-faithfulness.d.ts +1 -1
- 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 +4 -3
- package/dist/fallback-llm.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 +62 -61
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +3 -3
- 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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +8 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +8 -7
- package/dist/maintenance/rebuild-memory-projection.js +9 -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 +12 -11
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +3 -3
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +8 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +17 -16
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +10 -9
- package/dist/{orchestrator-B1iVjEyc.d.ts → orchestrator-iVZWi8aW.d.ts} +21 -4
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +32 -31
- 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 +3 -2
- 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-handles.d.ts +228 -0
- package/dist/recall-handles.js +42 -0
- package/dist/recall-handles.js.map +1 -0
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +40 -2
- package/dist/recall-state.js +3 -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 +5 -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/sanitize.d.ts +2 -0
- package/dist/sanitize.js +6 -2
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +2 -2
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +4 -4
- 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-CY8uVRNJ.d.ts → semantic-consolidation-DSjMDZ1u.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +9 -8
- package/dist/semantic-rule-promotion.js +8 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +8 -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 +3 -3
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +7 -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/import-sqlite.js +2 -2
- package/dist/{types-Dr0Cw3L9.d.ts → types-D9dwzU_x.d.ts} +25 -1
- 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 +8 -7
- package/package.json +2 -2
- package/src/access-boundary.ts +4 -1
- package/src/access-http.ts +78 -20
- package/src/access-mcp.test.ts +29 -0
- package/src/access-mcp.ts +99 -17
- package/src/access-operations-batch.ts +14 -0
- package/src/access-operations.ts +5 -0
- package/src/access-schema.ts +8 -0
- package/src/access-service-observe-idempotency.test.ts +308 -0
- package/src/access-service.ts +106 -5
- package/src/access-surface-catalog.test.ts +4 -2
- package/src/access-surface-catalog.ts +5 -0
- package/src/active-memory-bridge.test.ts +72 -0
- package/src/active-memory-bridge.ts +21 -2
- package/src/chat/chat-cli.test.ts +142 -0
- package/src/chat/chat-cli.ts +206 -0
- package/src/chat/chat-config.ts +57 -0
- package/src/chat/chat-engine.test.ts +272 -0
- package/src/chat/chat-engine.ts +518 -0
- package/src/chat/chat-executor.ts +133 -0
- package/src/chat/chat-factory.ts +161 -0
- package/src/chat/chat-http.test.ts +197 -0
- package/src/chat/chat-http.ts +222 -0
- package/src/chat/chat-llm.ts +229 -0
- package/src/chat/chat-mcp.test.ts +135 -0
- package/src/chat/chat-security.test.ts +491 -0
- package/src/chat/chat-session.ts +296 -0
- package/src/chat/chat-system-prompt.ts +76 -0
- package/src/chat/chat-tools.ts +255 -0
- package/src/chat/chat-types.ts +167 -0
- package/src/cli.ts +20 -0
- package/src/config.ts +9 -0
- package/src/orchestrator.ts +106 -5
- package/src/recall-handles-wiring.test.ts +477 -0
- package/src/recall-handles.test.ts +283 -0
- package/src/recall-handles.ts +414 -0
- package/src/recall-state.ts +99 -0
- package/src/sanitize.ts +6 -0
- package/src/types.ts +29 -0
- package/dist/chunk-3XHD3XGK.js.map +0 -1
- package/dist/chunk-5KQCOIPW.js.map +0 -1
- package/dist/chunk-A2R5NV6O.js.map +0 -1
- package/dist/chunk-BOGENF7P.js.map +0 -1
- package/dist/chunk-GKPIY7EQ.js.map +0 -1
- package/dist/chunk-I3BT2IDW.js.map +0 -1
- package/dist/chunk-NUWZFSKC.js.map +0 -1
- package/dist/chunk-SINGJCUR.js.map +0 -1
- package/dist/chunk-Z3B2YW56.js.map +0 -1
- package/dist/chunk-ZVDRL6SK.js.map +0 -1
- /package/dist/{chunk-OMSTU233.js.map → chunk-2KAYTPPT.js.map} +0 -0
- /package/dist/{chunk-VYZNWH4J.js.map → chunk-2SXMCQEQ.js.map} +0 -0
- /package/dist/{chunk-NXL5CVE7.js.map → chunk-2Y6LMOQQ.js.map} +0 -0
- /package/dist/{chunk-LQ4J7ELC.js.map → chunk-67HG7KFE.js.map} +0 -0
- /package/dist/{chunk-ZBG4K36I.js.map → chunk-6FUJ2VYY.js.map} +0 -0
- /package/dist/{chunk-GMRNKPWO.js.map → chunk-6QUPUTT7.js.map} +0 -0
- /package/dist/{chunk-Z56IHRVV.js.map → chunk-6UU43ZGD.js.map} +0 -0
- /package/dist/{chunk-APJQ6UEA.js.map → chunk-AGNBY3VG.js.map} +0 -0
- /package/dist/{chunk-OWOKOFXK.js.map → chunk-AJOIM6UR.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-K22IJO4R.js.map → chunk-CTOQEZSN.js.map} +0 -0
- /package/dist/{chunk-HSDJCT3V.js.map → chunk-DXGS5YOQ.js.map} +0 -0
- /package/dist/{chunk-HV57RHMD.js.map → chunk-EH5ED3C6.js.map} +0 -0
- /package/dist/{chunk-7NDYFAJS.js.map → chunk-GM3BMWKR.js.map} +0 -0
- /package/dist/{chunk-5QUS24F7.js.map → chunk-I56LKMF7.js.map} +0 -0
- /package/dist/{chunk-35YJ6KCV.js.map → chunk-JEIROMLX.js.map} +0 -0
- /package/dist/{chunk-SKQCFAYU.js.map → chunk-JVEMUX6L.js.map} +0 -0
- /package/dist/{chunk-CXMXAC5R.js.map → chunk-K44MIN6D.js.map} +0 -0
- /package/dist/{chunk-BQCFXAMN.js.map → chunk-MPHDVHIB.js.map} +0 -0
- /package/dist/{chunk-QXNFQKWU.js.map → chunk-MTHNJCOJ.js.map} +0 -0
- /package/dist/{chunk-3TCRU4JA.js.map → chunk-O62P2LFL.js.map} +0 -0
- /package/dist/{chunk-JSDZMOT7.js.map → chunk-OC56UWV5.js.map} +0 -0
- /package/dist/{chunk-7IBEWQLG.js.map → chunk-QOIONSOI.js.map} +0 -0
- /package/dist/{chunk-4P5UAL3C.js.map → chunk-RBHANGVX.js.map} +0 -0
- /package/dist/{chunk-SK2CR6MW.js.map → chunk-RVLB4E6N.js.map} +0 -0
- /package/dist/{chunk-CNVIWMQI.js.map → chunk-UY4JMEAK.js.map} +0 -0
- /package/dist/{chunk-S6FQLQGH.js.map → chunk-V7GDTQ6I.js.map} +0 -0
- /package/dist/{chunk-JNOYYWCA.js.map → chunk-VO4UICII.js.map} +0 -0
- /package/dist/{chunk-54TI5GLV.js.map → chunk-WI4QX7GH.js.map} +0 -0
- /package/dist/{chunk-XKU4YE6Z.js.map → chunk-XZTUMWKQ.js.map} +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM adapter for the chat engine (issue #1583).
|
|
3
|
+
*
|
|
4
|
+
* The adapter exposes the production {@link ChatLlmAdapter} interface so the
|
|
5
|
+
* engine is decoupled from the routing chain. Two implementations:
|
|
6
|
+
*
|
|
7
|
+
* - {@link createProductionChatLlmAdapter}: wraps the existing
|
|
8
|
+
* FallbackLlmClient / LocalLlmClient routing chain so local Ollama/vLLM
|
|
9
|
+
* models are fully supported (gotcha 1). Memory chat must not require
|
|
10
|
+
* cloud.
|
|
11
|
+
*
|
|
12
|
+
* - {@link StubChatLlmAdapter}: test-only adapter that drives scripted
|
|
13
|
+
* tool-call sequences. PR 1 tests use this to verify the engine loop,
|
|
14
|
+
* confirmation protocol, budget exhaustion, and citation guard without
|
|
15
|
+
* real LLM calls.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ChatLlmAdapter, ChatLlmMessage, ChatLlmResponse, ChatLlmToolCall, ChatToolSchema } from "./chat-types.js";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Minimal LLM-client interface (duck-typed from FallbackLlmClient and
|
|
22
|
+
// LocalLlmClient — both expose chatCompletion returning {content}|null).
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
interface LlmLikeClient {
|
|
26
|
+
chatCompletion(
|
|
27
|
+
messages: Array<{ role: string; content: string }>,
|
|
28
|
+
options?: { model?: string; signal?: AbortSignal; [k: string]: unknown },
|
|
29
|
+
): Promise<{ content: string; modelUsed?: string } | null>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a production LLM adapter from an existing routing-chain client
|
|
34
|
+
* (FallbackLlmClient or LocalLlmClient).
|
|
35
|
+
*
|
|
36
|
+
* The adapter maps the chat engine's message format to the client's
|
|
37
|
+
* chatCompletion signature, injects the tool schema as a system-level
|
|
38
|
+
* instruction (OpenAI function-calling compat), and parses tool-call requests
|
|
39
|
+
* from the model's response.
|
|
40
|
+
*/
|
|
41
|
+
export function createProductionChatLlmAdapter(
|
|
42
|
+
client: LlmLikeClient,
|
|
43
|
+
): ChatLlmAdapter {
|
|
44
|
+
return {
|
|
45
|
+
async complete(
|
|
46
|
+
messages: ChatLlmMessage[],
|
|
47
|
+
options: { tools: ChatToolSchema[]; model?: string; signal?: AbortSignal },
|
|
48
|
+
): Promise<ChatLlmResponse | null> {
|
|
49
|
+
// Build the tool-calling instruction appended to the system message.
|
|
50
|
+
const toolInstruction = formatToolInstruction(options.tools);
|
|
51
|
+
const mapped = messages.map((m) => {
|
|
52
|
+
if (m.role === "system") {
|
|
53
|
+
return { role: m.role, content: m.content + "\n\n" + toolInstruction };
|
|
54
|
+
}
|
|
55
|
+
// Normalize tool results to a provider-safe role. The underlying
|
|
56
|
+
// chat-completion clients do not implement native tool-call envelopes,
|
|
57
|
+
// so a bare role:"tool" (without tool_call_id) is rejected by
|
|
58
|
+
// OpenAI/Anthropic/Ollama endpoints. Surface the result as a user
|
|
59
|
+
// message so the follow-up LLM turn always succeeds (P1).
|
|
60
|
+
if (m.role === "tool") {
|
|
61
|
+
const tag = m.toolCallId ? `[tool:${m.toolCallId}]` : "[tool]";
|
|
62
|
+
return { role: "user", content: `${tag} ${m.content}` };
|
|
63
|
+
}
|
|
64
|
+
return { role: m.role, content: m.content };
|
|
65
|
+
});
|
|
66
|
+
const result = await client.chatCompletion(mapped, {
|
|
67
|
+
...(options.model ? { model: options.model } : {}),
|
|
68
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
69
|
+
});
|
|
70
|
+
if (!result) return null;
|
|
71
|
+
const toolCalls = parseToolCalls(result.content);
|
|
72
|
+
return {
|
|
73
|
+
content: stripToolCallJson(result.content, toolCalls),
|
|
74
|
+
toolCalls,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Format the available tools as a function-calling instruction for the LLM.
|
|
82
|
+
* Models that support native function-calling can ignore this; models that
|
|
83
|
+
* don't (most local models) use it to emit structured tool calls.
|
|
84
|
+
*/
|
|
85
|
+
function formatToolInstruction(tools: ChatToolSchema[]): string {
|
|
86
|
+
if (tools.length === 0) return "";
|
|
87
|
+
const lines = [
|
|
88
|
+
"## Available tools",
|
|
89
|
+
"",
|
|
90
|
+
"You may call the following tools by emitting a JSON block on its own line:",
|
|
91
|
+
"```json",
|
|
92
|
+
'{"tool": "<name>", "arguments": { ... }}',
|
|
93
|
+
"```",
|
|
94
|
+
"",
|
|
95
|
+
"Call one tool at a time. After receiving the tool result, continue your",
|
|
96
|
+
"reasoning or emit another tool call. When you have a final answer, reply",
|
|
97
|
+
"with text only (no tool-call JSON).",
|
|
98
|
+
"",
|
|
99
|
+
"Tools:",
|
|
100
|
+
];
|
|
101
|
+
for (const t of tools) {
|
|
102
|
+
const params = JSON.stringify(t.function.parameters);
|
|
103
|
+
lines.push(`- ${t.function.name}: ${t.function.description}`);
|
|
104
|
+
lines.push(` Parameters: ${params}`);
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse a tool-call JSON block from the model's response content.
|
|
111
|
+
* Tolerates leading/trailing text and code fences.
|
|
112
|
+
*/
|
|
113
|
+
export function parseToolCalls(content: string): ChatLlmToolCall[] {
|
|
114
|
+
const calls: ChatLlmToolCall[] = [];
|
|
115
|
+
// Match ```json ... ``` blocks and bare JSON objects with "tool" key.
|
|
116
|
+
const patterns = [
|
|
117
|
+
/```json\s*\n([\s\S]*?)\n```/g,
|
|
118
|
+
/```\s*\n([\s\S]*?)\n```/g,
|
|
119
|
+
];
|
|
120
|
+
const candidates: string[] = [];
|
|
121
|
+
for (const pattern of patterns) {
|
|
122
|
+
let match: RegExpExecArray | null;
|
|
123
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
124
|
+
candidates.push(match[1]);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Also try bare JSON lines.
|
|
128
|
+
for (const line of content.split("\n")) {
|
|
129
|
+
const trimmed = line.trim();
|
|
130
|
+
if (trimmed.startsWith("{") && trimmed.includes('"tool"')) {
|
|
131
|
+
candidates.push(trimmed);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const seen = new Set<string>();
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(candidate) as { tool?: string; arguments?: Record<string, unknown> };
|
|
138
|
+
if (typeof parsed.tool === "string" && parsed.tool.length > 0) {
|
|
139
|
+
const args = parsed.arguments ?? {};
|
|
140
|
+
// De-duplicate: a fenced block and its inner bare line can both match.
|
|
141
|
+
const key = parsed.tool + ":" + JSON.stringify(args);
|
|
142
|
+
if (seen.has(key)) continue;
|
|
143
|
+
seen.add(key);
|
|
144
|
+
calls.push({
|
|
145
|
+
id: `call_${calls.length + 1}`,
|
|
146
|
+
name: parsed.tool,
|
|
147
|
+
arguments: args,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Not valid JSON — skip.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return calls;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Remove tool-call JSON blocks from the content, leaving only prose.
|
|
159
|
+
*/
|
|
160
|
+
function stripToolCallJson(content: string, toolCalls: ChatLlmToolCall[]): string {
|
|
161
|
+
if (toolCalls.length === 0) return content;
|
|
162
|
+
let result = content;
|
|
163
|
+
// Remove fenced blocks.
|
|
164
|
+
result = result.replace(/```json\s*\n[\s\S]*?\n```/g, "");
|
|
165
|
+
result = result.replace(/```\s*\n[\s\S]*?\n```/g, "");
|
|
166
|
+
// Remove bare JSON tool-call lines.
|
|
167
|
+
result = result
|
|
168
|
+
.split("\n")
|
|
169
|
+
.filter((line) => {
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
return !(trimmed.startsWith("{") && trimmed.includes('"tool"'));
|
|
172
|
+
})
|
|
173
|
+
.join("\n");
|
|
174
|
+
return result.trim();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Stub adapter — for tests (rule 33: production signature, scripted behaviour)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* A scripted step in the stub adapter. Each step is consumed in order for
|
|
183
|
+
* each LLM call; the last step repeats if the engine loops beyond the
|
|
184
|
+
* script length.
|
|
185
|
+
*/
|
|
186
|
+
export interface StubLlmStep {
|
|
187
|
+
content?: string;
|
|
188
|
+
toolCalls?: Array<{ name: string; arguments: Record<string, unknown> }>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export class StubChatLlmAdapter implements ChatLlmAdapter {
|
|
192
|
+
private steps: StubLlmStep[];
|
|
193
|
+
private callIndex = 0;
|
|
194
|
+
|
|
195
|
+
constructor(steps: StubLlmStep[]) {
|
|
196
|
+
this.steps = steps;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async complete(
|
|
200
|
+
_messages: ChatLlmMessage[],
|
|
201
|
+
_options: { tools: ChatToolSchema[]; model?: string; signal?: AbortSignal },
|
|
202
|
+
): Promise<ChatLlmResponse | null> {
|
|
203
|
+
const step = this.steps[Math.min(this.callIndex, this.steps.length - 1)];
|
|
204
|
+
this.callIndex++;
|
|
205
|
+
const toolCalls: ChatLlmToolCall[] = (step.toolCalls ?? []).map((tc, i) => ({
|
|
206
|
+
id: `call_${i + 1}`,
|
|
207
|
+
name: tc.name,
|
|
208
|
+
arguments: tc.arguments,
|
|
209
|
+
}));
|
|
210
|
+
return {
|
|
211
|
+
content: step.content ?? "",
|
|
212
|
+
toolCalls,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Reset the call index (for test reuse). */
|
|
217
|
+
reset(): void {
|
|
218
|
+
this.callIndex = 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A stub adapter that always returns null (simulates LLM outage, rule 13).
|
|
224
|
+
*/
|
|
225
|
+
export class NullChatLlmAdapter implements ChatLlmAdapter {
|
|
226
|
+
async complete(): Promise<null> {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat MCP surface tests (issue #1583 PR 4).
|
|
3
|
+
*
|
|
4
|
+
* Verifies the tools/list parity gate (chat.enabled → memory_chat visible,
|
|
5
|
+
* byte-identical when off — rule 39) and the memory_chat tool dispatch.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { strict as assert } from "node:assert";
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { test } from "node:test";
|
|
13
|
+
|
|
14
|
+
import { EngramMcpServer } from "../access-mcp.js";
|
|
15
|
+
import type { EngramAccessService } from "../access-service.js";
|
|
16
|
+
import { parseConfig } from "../config.js";
|
|
17
|
+
import { DEFAULT_CHAT_CONFIG } from "./chat-config.js";
|
|
18
|
+
|
|
19
|
+
function makeBaseService(overrides: Record<string, unknown> = {}): EngramAccessService {
|
|
20
|
+
return {
|
|
21
|
+
briefingEnabled: false,
|
|
22
|
+
briefing: () => Promise.resolve({ markdown: "", json: {}, sections: {}, window: {} }),
|
|
23
|
+
recall: () => Promise.resolve({ context: "" }),
|
|
24
|
+
recallExplain: () => Promise.resolve(null),
|
|
25
|
+
store: () => Promise.resolve({ id: "x", stored: true }),
|
|
26
|
+
memoryStore: () => Promise.resolve({ schemaVersion: 1, operation: "memory_store", namespace: "default", dryRun: true, accepted: true, queued: false, status: "validated" }),
|
|
27
|
+
memoryGet: () => Promise.resolve(null),
|
|
28
|
+
memoryTimeline: () => Promise.resolve([]),
|
|
29
|
+
entityGet: () => Promise.resolve(null),
|
|
30
|
+
reviewQueueList: () => Promise.resolve({ items: [] }),
|
|
31
|
+
observe: () => Promise.resolve({ ok: true }),
|
|
32
|
+
...overrides,
|
|
33
|
+
} as unknown as EngramAccessService;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makeListRequest(): Record<string, unknown> {
|
|
37
|
+
return { jsonrpc: "2.0", id: 1, method: "tools/list", params: {} };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function makeCallRequest(name: string, args: Record<string, unknown>): Record<string, unknown> {
|
|
41
|
+
return { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name, arguments: args } };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
test("MCP tools/list: memory_chat absent when chatVisible is false (byte-identical, rule 39)", async () => {
|
|
45
|
+
const service = makeBaseService();
|
|
46
|
+
const server = new EngramMcpServer(service, { chatVisible: false });
|
|
47
|
+
const response = await server.handleRequest(makeListRequest());
|
|
48
|
+
const tools = (response as { result?: { tools?: { name: string }[] } }).result?.tools ?? [];
|
|
49
|
+
const names = tools.map((t) => t.name);
|
|
50
|
+
assert.ok(!names.includes("engram.memory_chat"), "memory_chat must NOT appear when chat is disabled");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("MCP tools/list: memory_chat present when chatVisible is true", async () => {
|
|
54
|
+
const service = makeBaseService();
|
|
55
|
+
const server = new EngramMcpServer(service, { chatVisible: true });
|
|
56
|
+
const response = await server.handleRequest(makeListRequest());
|
|
57
|
+
const tools = (response as { result?: { tools?: { name: string }[] } }).result?.tools ?? [];
|
|
58
|
+
const names = tools.map((t) => t.name);
|
|
59
|
+
assert.ok(names.includes("engram.memory_chat"), "memory_chat must appear when chat is enabled");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("MCP memory_chat: dispatches to processChatMessage and returns a reply", async () => {
|
|
63
|
+
const memoryDir = await mkdtemp(join(tmpdir(), "chat-mcp-"));
|
|
64
|
+
const llm = {
|
|
65
|
+
chatCompletion: async () => ({ content: "Your database uses PostgreSQL [id: abc]." }),
|
|
66
|
+
};
|
|
67
|
+
const service = makeBaseService({
|
|
68
|
+
fallbackLlmRef: llm,
|
|
69
|
+
memoryDir,
|
|
70
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
71
|
+
});
|
|
72
|
+
const server = new EngramMcpServer(service, { chatVisible: true, principal: "alice" });
|
|
73
|
+
|
|
74
|
+
const response = await server.handleRequest(
|
|
75
|
+
makeCallRequest("engram.memory_chat", { message: "what database?" }),
|
|
76
|
+
);
|
|
77
|
+
const result = (response as { result?: { content?: { text?: string }[]; isError?: boolean } }).result;
|
|
78
|
+
assert.equal(result?.isError, false);
|
|
79
|
+
const text = result?.content?.[0]?.text ?? "";
|
|
80
|
+
const parsed = JSON.parse(text) as { reply?: string; chatSessionId?: string };
|
|
81
|
+
assert.ok(parsed.reply && parsed.reply.length > 0, "reply text must be present");
|
|
82
|
+
assert.ok(parsed.chatSessionId, "chatSessionId must be returned");
|
|
83
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("MCP memory_chat: rejects empty message with input error", async () => {
|
|
87
|
+
const memoryDir = await mkdtemp(join(tmpdir(), "chat-mcp-err-"));
|
|
88
|
+
const service = makeBaseService({
|
|
89
|
+
fallbackLlmRef: { chatCompletion: async () => ({ content: "x" }) },
|
|
90
|
+
memoryDir,
|
|
91
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
92
|
+
});
|
|
93
|
+
const server = new EngramMcpServer(service, { chatVisible: true });
|
|
94
|
+
|
|
95
|
+
const response = await server.handleRequest(
|
|
96
|
+
makeCallRequest("engram.memory_chat", { message: "" }),
|
|
97
|
+
);
|
|
98
|
+
const result = (response as { result?: { isError?: boolean; content?: { text?: string }[] } }).result;
|
|
99
|
+
assert.equal(result?.isError, true, "empty message must surface an input error");
|
|
100
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("MCP memory_chat: strips internal error from structuredContent (Thread 17)", async () => {
|
|
104
|
+
const memoryDir = await mkdtemp(join(tmpdir(), "chat-mcp-leak-"));
|
|
105
|
+
// LLM returns null → engine tags the result with error: "no_llm_available".
|
|
106
|
+
const llm = {
|
|
107
|
+
chatCompletion: async () => null,
|
|
108
|
+
};
|
|
109
|
+
const service = makeBaseService({
|
|
110
|
+
fallbackLlmRef: llm,
|
|
111
|
+
memoryDir,
|
|
112
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
113
|
+
});
|
|
114
|
+
const server = new EngramMcpServer(service, { chatVisible: true, principal: "alice" });
|
|
115
|
+
|
|
116
|
+
const response = await server.handleRequest(
|
|
117
|
+
makeCallRequest("engram.memory_chat", { message: "what database?" }),
|
|
118
|
+
);
|
|
119
|
+
const result = (response as {
|
|
120
|
+
result?: {
|
|
121
|
+
content?: { text?: string }[];
|
|
122
|
+
structuredContent?: Record<string, unknown>;
|
|
123
|
+
isError?: boolean;
|
|
124
|
+
};
|
|
125
|
+
}).result;
|
|
126
|
+
assert.equal(result?.isError, false, "should not be a JSON-RPC error");
|
|
127
|
+
// The text content and structuredContent must NOT expose the raw error field.
|
|
128
|
+
const sc = result?.structuredContent ?? {};
|
|
129
|
+
assert.ok(!("error" in sc), "structuredContent must not leak the error field");
|
|
130
|
+
const text = result?.content?.[0]?.text ?? "";
|
|
131
|
+
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
132
|
+
assert.ok(!("error" in parsed), "text content must not leak the error field");
|
|
133
|
+
assert.ok(typeof parsed.reply === "string", "reply must be present");
|
|
134
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
135
|
+
});
|