@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
|
@@ -11,19 +11,19 @@ import {
|
|
|
11
11
|
} from "./chunk-D24OXEPB.js";
|
|
12
12
|
import {
|
|
13
13
|
validateRequest
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FVZ2ISIX.js";
|
|
15
15
|
import {
|
|
16
16
|
getOperation
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HHLWSWWF.js";
|
|
18
18
|
import {
|
|
19
19
|
EngramAccessInputError
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BNOAB5HH.js";
|
|
21
21
|
import {
|
|
22
22
|
projectTagProjectId
|
|
23
23
|
} from "./chunk-SHRRWOVY.js";
|
|
24
24
|
import {
|
|
25
25
|
validateBriefingFormat
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-JEIROMLX.js";
|
|
27
27
|
import {
|
|
28
28
|
resolvePrincipal
|
|
29
29
|
} from "./chunk-UZYLX7M6.js";
|
|
@@ -35,8 +35,1012 @@ import {
|
|
|
35
35
|
} from "./chunk-JUC24CTX.js";
|
|
36
36
|
|
|
37
37
|
// src/access-mcp.ts
|
|
38
|
-
import { readFile } from "fs/promises";
|
|
38
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
39
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
40
|
+
|
|
41
|
+
// src/chat/chat-system-prompt.ts
|
|
42
|
+
function buildChatSystemPrompt(opts) {
|
|
43
|
+
const lines = [
|
|
44
|
+
"You are Remnic Chat \u2014 a conversational memory inspector and corrector.",
|
|
45
|
+
"You help the user understand, explore, and fix what Remnic remembers.",
|
|
46
|
+
"",
|
|
47
|
+
"## Core rules",
|
|
48
|
+
"",
|
|
49
|
+
"1. GROUND EVERY ASSERTION. When you state what Remnic remembers, you MUST",
|
|
50
|
+
" cite the memory id or handle, include the source quote, and the date.",
|
|
51
|
+
" If no tool result in this turn supports the claim, say",
|
|
52
|
+
' "I found nothing" instead of guessing.',
|
|
53
|
+
"",
|
|
54
|
+
"2. NO MUTATION WITHOUT CONFIRMATION. You may propose a correction plan",
|
|
55
|
+
" (correction_plan), but you must NEVER apply it (correction_apply) until",
|
|
56
|
+
" the user explicitly confirms in the conversation.",
|
|
57
|
+
"",
|
|
58
|
+
"3. RENDER PLANS AS DIFFS. When you surface a correction plan, render it",
|
|
59
|
+
" as the diff preview the plan returns \u2014 before/after, affected memory",
|
|
60
|
+
" ids, and the supersession reason.",
|
|
61
|
+
"",
|
|
62
|
+
"4. BE HONEST ABOUT LIMITS. If a tool returns no results, say so. If you",
|
|
63
|
+
" are unsure, say so. Never fabricate memory content.",
|
|
64
|
+
"",
|
|
65
|
+
"5. RESPECT SCOPE. You operate within the caller's namespace and principal.",
|
|
66
|
+
" Do not attempt to access memories outside the caller's scope."
|
|
67
|
+
];
|
|
68
|
+
if (opts.correctionAvailable) {
|
|
69
|
+
lines.push(
|
|
70
|
+
"",
|
|
71
|
+
"## Mutation protocol",
|
|
72
|
+
"",
|
|
73
|
+
"When the user asks to change or forget memories:",
|
|
74
|
+
"1. Call correction_plan to generate a diff preview.",
|
|
75
|
+
"2. Present the diff to the user and ask them to reply 'apply' to confirm.",
|
|
76
|
+
"3. Only after explicit confirmation, call correction_apply.",
|
|
77
|
+
"",
|
|
78
|
+
"For bulk/destructive requests ('forget everything about X'), always go",
|
|
79
|
+
"through correction_plan first \u2014 there is no direct-delete tool."
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
lines.push(
|
|
83
|
+
"",
|
|
84
|
+
"## Mutation status",
|
|
85
|
+
"",
|
|
86
|
+
"Correction (mutation) tools are not yet available in this build.",
|
|
87
|
+
"You can inspect and search memories but cannot apply corrections.",
|
|
88
|
+
"When the user asks to change memories, explain that correction tools",
|
|
89
|
+
"require the Correction Contract (#1580) which is pending."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/chat/chat-tools.ts
|
|
96
|
+
var MUTATING_TOOLS = /* @__PURE__ */ new Set([
|
|
97
|
+
"correction_apply",
|
|
98
|
+
"memory_promote"
|
|
99
|
+
]);
|
|
100
|
+
var MEMORY_SEARCH_SCHEMA = {
|
|
101
|
+
type: "function",
|
|
102
|
+
function: {
|
|
103
|
+
name: "memory_search",
|
|
104
|
+
description: "Semantic search across stored memories. Returns matching memory paths, scores, and snippets. Use this to find what Remnic remembers about a topic.",
|
|
105
|
+
parameters: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
query: { type: "string", description: "The search query." },
|
|
109
|
+
maxResults: { type: "number", description: "Maximum results (default 8)." }
|
|
110
|
+
},
|
|
111
|
+
required: ["query"],
|
|
112
|
+
additionalProperties: false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var MEMORY_GET_SCHEMA = {
|
|
117
|
+
type: "function",
|
|
118
|
+
function: {
|
|
119
|
+
name: "memory_get",
|
|
120
|
+
description: "Fetch a single memory by its id. Returns the full memory content, category, tags, and source attribution.",
|
|
121
|
+
parameters: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
memoryId: { type: "string", description: "The memory id to fetch." }
|
|
125
|
+
},
|
|
126
|
+
required: ["memoryId"],
|
|
127
|
+
additionalProperties: false
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var MEMORY_TIMELINE_SCHEMA = {
|
|
132
|
+
type: "function",
|
|
133
|
+
function: {
|
|
134
|
+
name: "memory_timeline",
|
|
135
|
+
description: "Read the lifecycle timeline of a single memory \u2014 creation, updates, consolidations, and tier transitions.",
|
|
136
|
+
parameters: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
memoryId: { type: "string", description: "The memory id." },
|
|
140
|
+
limit: { type: "number", description: "Maximum rows (default 200)." }
|
|
141
|
+
},
|
|
142
|
+
required: ["memoryId"],
|
|
143
|
+
additionalProperties: false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var RECALL_EXPLAIN_SCHEMA = {
|
|
148
|
+
type: "function",
|
|
149
|
+
function: {
|
|
150
|
+
name: "recall_explain",
|
|
151
|
+
description: "Explain why Remnic would recall certain memories for a given query. Returns tier scoring and matching details. Use this to answer 'why did you remember this?'",
|
|
152
|
+
parameters: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
query: { type: "string", description: "Optional query to explain recall for. When omitted, explains the most recent recall." }
|
|
156
|
+
},
|
|
157
|
+
required: [],
|
|
158
|
+
additionalProperties: false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var ENTITY_GET_SCHEMA = {
|
|
163
|
+
type: "function",
|
|
164
|
+
function: {
|
|
165
|
+
name: "entity_get",
|
|
166
|
+
description: "Fetch the compiled entity record for a named entity (person, project, tool, etc.). Returns traits, timeline, and associated memories.",
|
|
167
|
+
parameters: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {
|
|
170
|
+
name: { type: "string", description: "Entity name." }
|
|
171
|
+
},
|
|
172
|
+
required: ["name"],
|
|
173
|
+
additionalProperties: false
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var STATS_SCHEMA = {
|
|
178
|
+
type: "function",
|
|
179
|
+
function: {
|
|
180
|
+
name: "stats",
|
|
181
|
+
description: "Return aggregate memory statistics \u2014 memory count, entity count, profile summary, and open questions. Use this for high-level 'what do you know?' questions.",
|
|
182
|
+
parameters: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {},
|
|
185
|
+
additionalProperties: false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
var CORRECTION_PLAN_SCHEMA = {
|
|
190
|
+
type: "function",
|
|
191
|
+
function: {
|
|
192
|
+
name: "correction_plan",
|
|
193
|
+
description: "Generate a correction plan (diff preview) for a requested memory change. This does NOT apply the change \u2014 it previews what would happen. The user must confirm before correction_apply runs.",
|
|
194
|
+
parameters: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {
|
|
197
|
+
request: {
|
|
198
|
+
type: "string",
|
|
199
|
+
description: "Natural-language description of the correction, e.g. 'we finished the MySQL migration in March' or 'forget the outdated database credentials'."
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
required: ["request"],
|
|
203
|
+
additionalProperties: false
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var CORRECTION_APPLY_SCHEMA = {
|
|
208
|
+
type: "function",
|
|
209
|
+
function: {
|
|
210
|
+
name: "correction_apply",
|
|
211
|
+
description: "Apply a previously generated correction plan. The engine intercepts this call and requires explicit user confirmation before it executes.",
|
|
212
|
+
parameters: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
planId: { type: "string", description: "The plan id from correction_plan." }
|
|
216
|
+
},
|
|
217
|
+
required: ["planId"],
|
|
218
|
+
additionalProperties: false
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var MEMORY_PROMOTE_SCHEMA = {
|
|
223
|
+
type: "function",
|
|
224
|
+
function: {
|
|
225
|
+
name: "memory_promote",
|
|
226
|
+
description: "Promote a memory's tier (e.g. from cold to hot). The engine requires user confirmation before executing.",
|
|
227
|
+
parameters: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
memoryId: { type: "string", description: "The memory id to promote." }
|
|
231
|
+
},
|
|
232
|
+
required: ["memoryId"],
|
|
233
|
+
additionalProperties: false
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
var REVIEW_LIST_SCHEMA = {
|
|
238
|
+
type: "function",
|
|
239
|
+
function: {
|
|
240
|
+
name: "review_list",
|
|
241
|
+
description: "List items in the contradiction/quality review queue. Returns pairs of potentially conflicting memories awaiting resolution.",
|
|
242
|
+
parameters: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
runId: { type: "string", description: "Optional governance run id." }
|
|
246
|
+
},
|
|
247
|
+
additionalProperties: false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
var SCOPE_INSPECT_SCHEMA = {
|
|
252
|
+
type: "function",
|
|
253
|
+
function: {
|
|
254
|
+
name: "scope_inspect",
|
|
255
|
+
description: "Inspect the current scope/namespace resolution \u2014 which namespace the chat session operates in and how it was resolved.",
|
|
256
|
+
parameters: {
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {},
|
|
259
|
+
additionalProperties: false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var READONLY_SCHEMAS = [
|
|
264
|
+
MEMORY_SEARCH_SCHEMA,
|
|
265
|
+
MEMORY_GET_SCHEMA,
|
|
266
|
+
MEMORY_TIMELINE_SCHEMA,
|
|
267
|
+
RECALL_EXPLAIN_SCHEMA,
|
|
268
|
+
ENTITY_GET_SCHEMA,
|
|
269
|
+
STATS_SCHEMA,
|
|
270
|
+
REVIEW_LIST_SCHEMA
|
|
271
|
+
];
|
|
272
|
+
function buildChatToolSchemas(opts) {
|
|
273
|
+
const tools = [...READONLY_SCHEMAS];
|
|
274
|
+
if (opts.correctionAvailable) {
|
|
275
|
+
tools.push(CORRECTION_PLAN_SCHEMA, CORRECTION_APPLY_SCHEMA, MEMORY_PROMOTE_SCHEMA);
|
|
276
|
+
}
|
|
277
|
+
if (opts.scopeInspectAvailable) {
|
|
278
|
+
tools.push(SCOPE_INSPECT_SCHEMA);
|
|
279
|
+
}
|
|
280
|
+
return tools;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/chat/chat-engine.ts
|
|
284
|
+
var CONFIRMATION_KEYWORDS = /* @__PURE__ */ new Set(["yes", "y", "apply", "confirm"]);
|
|
285
|
+
function isConfirmationMessage(text) {
|
|
286
|
+
return CONFIRMATION_KEYWORDS.has(text.trim().toLowerCase());
|
|
287
|
+
}
|
|
288
|
+
var ChatEngine = class {
|
|
289
|
+
llm;
|
|
290
|
+
executor;
|
|
291
|
+
maxToolCallsPerTurn;
|
|
292
|
+
model;
|
|
293
|
+
correctionAvailable;
|
|
294
|
+
scopeInspectAvailable;
|
|
295
|
+
systemPrompt;
|
|
296
|
+
toolSchemas;
|
|
297
|
+
allowedToolNames;
|
|
298
|
+
constructor(opts) {
|
|
299
|
+
this.llm = opts.llm;
|
|
300
|
+
this.executor = opts.executor;
|
|
301
|
+
this.maxToolCallsPerTurn = opts.maxToolCallsPerTurn;
|
|
302
|
+
this.model = opts.model;
|
|
303
|
+
this.correctionAvailable = opts.correctionAvailable;
|
|
304
|
+
this.scopeInspectAvailable = opts.scopeInspectAvailable;
|
|
305
|
+
this.systemPrompt = buildChatSystemPrompt({
|
|
306
|
+
correctionAvailable: opts.correctionAvailable
|
|
307
|
+
});
|
|
308
|
+
this.toolSchemas = buildChatToolSchemas({
|
|
309
|
+
correctionAvailable: opts.correctionAvailable,
|
|
310
|
+
scopeInspectAvailable: opts.scopeInspectAvailable
|
|
311
|
+
});
|
|
312
|
+
this.allowedToolNames = new Set(this.toolSchemas.map((t) => t.function.name));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Process a single user message. The session state is mutated in place
|
|
316
|
+
* (confirmedPlanIds, pendingPlanId) — the caller persists the transcript.
|
|
317
|
+
*/
|
|
318
|
+
async processMessage(userMessage, session) {
|
|
319
|
+
if (isConfirmationMessage(userMessage) && this.correctionAvailable) {
|
|
320
|
+
if (session.pendingPlanId) {
|
|
321
|
+
const planId = session.pendingPlanId;
|
|
322
|
+
session.confirmedPlanIds.add(planId);
|
|
323
|
+
session.pendingPlanId = void 0;
|
|
324
|
+
try {
|
|
325
|
+
const applyResult = await this.executor.correctionApply(planId);
|
|
326
|
+
session.confirmedPlanIds.delete(planId);
|
|
327
|
+
return {
|
|
328
|
+
reply: `Correction applied.
|
|
329
|
+
|
|
330
|
+
${applyResult}`,
|
|
331
|
+
chatSessionId: session.id,
|
|
332
|
+
...session.pendingPromotionId ? {} : {}
|
|
333
|
+
};
|
|
334
|
+
} catch (err) {
|
|
335
|
+
session.confirmedPlanIds.delete(planId);
|
|
336
|
+
session.pendingPlanId = planId;
|
|
337
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
338
|
+
return {
|
|
339
|
+
reply: "[error] Failed to apply the correction. Please try again or re-request the plan.",
|
|
340
|
+
chatSessionId: session.id,
|
|
341
|
+
error: msg
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (session.pendingPromotionId) {
|
|
346
|
+
const memoryId = session.pendingPromotionId;
|
|
347
|
+
session.pendingPromotionId = void 0;
|
|
348
|
+
try {
|
|
349
|
+
const promoteResult = await this.executor.memoryPromote(memoryId);
|
|
350
|
+
return {
|
|
351
|
+
reply: `Memory promoted.
|
|
352
|
+
|
|
353
|
+
${promoteResult}`,
|
|
354
|
+
chatSessionId: session.id
|
|
355
|
+
};
|
|
356
|
+
} catch (err) {
|
|
357
|
+
session.pendingPromotionId = memoryId;
|
|
358
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
359
|
+
return {
|
|
360
|
+
reply: "[error] Failed to promote the memory. Please try again.",
|
|
361
|
+
chatSessionId: session.id,
|
|
362
|
+
error: msg
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const recentEntries = session.transcript.slice(-21).filter((e) => e.role !== "system" || e.seq !== 0).filter((e) => !(e.role === "system" && /^[a-z_]+:/.test(e.content))).filter((e, _i, arr) => {
|
|
368
|
+
if (e.role === "user" && e.content === userMessage) {
|
|
369
|
+
const lastUser = [...arr].reverse().find((x) => x.role === "user");
|
|
370
|
+
return e !== lastUser;
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
});
|
|
374
|
+
const conversation = [
|
|
375
|
+
{ role: "system", content: this.systemPrompt },
|
|
376
|
+
...recentEntries.map((e) => ({
|
|
377
|
+
role: e.role,
|
|
378
|
+
content: e.content,
|
|
379
|
+
...e.toolCallId ? { toolCallId: e.toolCallId } : {}
|
|
380
|
+
})),
|
|
381
|
+
{ role: "user", content: userMessage }
|
|
382
|
+
];
|
|
383
|
+
let toolCallCount = 0;
|
|
384
|
+
const skippedTools = [];
|
|
385
|
+
let pendingPlan;
|
|
386
|
+
for (; ; ) {
|
|
387
|
+
let response;
|
|
388
|
+
try {
|
|
389
|
+
response = await this.llm.complete(conversation, {
|
|
390
|
+
tools: this.toolSchemas,
|
|
391
|
+
...this.model ? { model: this.model } : {}
|
|
392
|
+
});
|
|
393
|
+
} catch (err) {
|
|
394
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
395
|
+
return {
|
|
396
|
+
reply: "[error] The model request failed. Check your model configuration and retry.",
|
|
397
|
+
chatSessionId: session.id,
|
|
398
|
+
error: msg
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (!response) {
|
|
402
|
+
return {
|
|
403
|
+
reply: "[error] No LLM model is available. Configure a local or cloud model to use Remnic Chat.",
|
|
404
|
+
chatSessionId: session.id,
|
|
405
|
+
error: "no_llm_available"
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (response.toolCalls.length === 0) {
|
|
409
|
+
let reply = response.content;
|
|
410
|
+
if (toolCallCount === 0 && looksLikeMemoryAssertion(reply)) {
|
|
411
|
+
reply += "\n\n\u26A0\uFE0F The above assertion was not grounded in a tool result this turn. Please verify with memory_search or memory_get.";
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
reply,
|
|
415
|
+
chatSessionId: session.id,
|
|
416
|
+
...pendingPlan ? { pendingPlan } : {},
|
|
417
|
+
...skippedTools.length > 0 ? { skippedTools } : {}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
conversation.push({ role: "assistant", content: response.content });
|
|
421
|
+
for (const tc of response.toolCalls) {
|
|
422
|
+
if (toolCallCount >= this.maxToolCallsPerTurn) {
|
|
423
|
+
skippedTools.push(tc.name);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
toolCallCount++;
|
|
427
|
+
if (!this.allowedToolNames.has(tc.name)) {
|
|
428
|
+
conversation.push({
|
|
429
|
+
role: "tool",
|
|
430
|
+
content: JSON.stringify({ error: `Tool '${tc.name}' is not available in this configuration.` }),
|
|
431
|
+
toolCallId: tc.id
|
|
432
|
+
});
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (MUTATING_TOOLS.has(tc.name)) {
|
|
436
|
+
if (tc.name === "correction_apply") {
|
|
437
|
+
const planId = typeof tc.arguments.planId === "string" ? tc.arguments.planId : "";
|
|
438
|
+
if (!session.confirmedPlanIds.has(planId)) {
|
|
439
|
+
try {
|
|
440
|
+
const plan = await this.executor.correctionPlan(
|
|
441
|
+
`retrieve plan ${planId}`
|
|
442
|
+
);
|
|
443
|
+
pendingPlan = { planId, preview: plan.preview };
|
|
444
|
+
session.pendingPlanId = planId;
|
|
445
|
+
conversation.push({
|
|
446
|
+
role: "tool",
|
|
447
|
+
content: JSON.stringify({
|
|
448
|
+
intercepted: true,
|
|
449
|
+
message: "This mutation requires confirmation. The plan preview has been shown to the user."
|
|
450
|
+
}),
|
|
451
|
+
toolCallId: tc.id
|
|
452
|
+
});
|
|
453
|
+
return {
|
|
454
|
+
reply: formatPlanPreview(plan.preview),
|
|
455
|
+
chatSessionId: session.id,
|
|
456
|
+
pendingPlan
|
|
457
|
+
};
|
|
458
|
+
} catch {
|
|
459
|
+
conversation.push({
|
|
460
|
+
role: "tool",
|
|
461
|
+
content: JSON.stringify({ error: "Plan not found or expired." }),
|
|
462
|
+
toolCallId: tc.id
|
|
463
|
+
});
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
session.confirmedPlanIds.delete(planId);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (tc.name === "memory_promote" && this.correctionAvailable) {
|
|
471
|
+
const memoryId = typeof tc.arguments.memoryId === "string" ? tc.arguments.memoryId : "";
|
|
472
|
+
session.pendingPromotionId = memoryId;
|
|
473
|
+
conversation.push({
|
|
474
|
+
role: "tool",
|
|
475
|
+
content: JSON.stringify({
|
|
476
|
+
intercepted: true,
|
|
477
|
+
message: `Memory promotion requires confirmation. Reply 'apply' to promote memory '${memoryId}'.`
|
|
478
|
+
}),
|
|
479
|
+
toolCallId: tc.id
|
|
480
|
+
});
|
|
481
|
+
return {
|
|
482
|
+
reply: `I want to promote memory \`${memoryId}\`. Reply **apply** to confirm this promotion, or **cancel** to discard it.`,
|
|
483
|
+
chatSessionId: session.id
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
let result;
|
|
487
|
+
try {
|
|
488
|
+
result = await this.executeTool(tc.name, tc.arguments, session);
|
|
489
|
+
} catch (err) {
|
|
490
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
491
|
+
result = { content: JSON.stringify({ error: msg }) };
|
|
492
|
+
}
|
|
493
|
+
conversation.push({
|
|
494
|
+
role: "tool",
|
|
495
|
+
content: result.content,
|
|
496
|
+
toolCallId: tc.id
|
|
497
|
+
});
|
|
498
|
+
if (tc.name === "correction_plan" && this.correctionAvailable) {
|
|
499
|
+
try {
|
|
500
|
+
const parsed = JSON.parse(result.content);
|
|
501
|
+
if (parsed.planId && parsed.preview) {
|
|
502
|
+
pendingPlan = { planId: parsed.planId, preview: parsed.preview };
|
|
503
|
+
session.pendingPlanId = parsed.planId;
|
|
504
|
+
}
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (skippedTools.length > 0) {
|
|
510
|
+
conversation.push({
|
|
511
|
+
role: "user",
|
|
512
|
+
content: `[system] Tool budget exhausted (${this.maxToolCallsPerTurn} calls). ${skippedTools.length} tool(s) were skipped: ${skippedTools.join(", ")}. Please summarize what you found.`
|
|
513
|
+
});
|
|
514
|
+
let summaryResponse;
|
|
515
|
+
try {
|
|
516
|
+
summaryResponse = await this.llm.complete(conversation, {
|
|
517
|
+
tools: [],
|
|
518
|
+
// No more tools — force a text reply.
|
|
519
|
+
...this.model ? { model: this.model } : {}
|
|
520
|
+
});
|
|
521
|
+
} catch {
|
|
522
|
+
summaryResponse = null;
|
|
523
|
+
}
|
|
524
|
+
const summaryText = summaryResponse?.content ?? "I reached my tool-call budget. Here is what I found so far, but some queries were skipped.";
|
|
525
|
+
return {
|
|
526
|
+
reply: summaryText,
|
|
527
|
+
chatSessionId: session.id,
|
|
528
|
+
...pendingPlan ? { pendingPlan } : {},
|
|
529
|
+
skippedTools
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Execute a single tool by name, delegating to the executor.
|
|
536
|
+
*/
|
|
537
|
+
async executeTool(name, args, _session) {
|
|
538
|
+
switch (name) {
|
|
539
|
+
case "memory_search": {
|
|
540
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
541
|
+
const maxResults = typeof args.maxResults === "number" ? args.maxResults : void 0;
|
|
542
|
+
return { content: await this.executor.memorySearch(query, maxResults) };
|
|
543
|
+
}
|
|
544
|
+
case "memory_get": {
|
|
545
|
+
const memoryId = typeof args.memoryId === "string" ? args.memoryId : "";
|
|
546
|
+
return { content: await this.executor.memoryGet(memoryId) };
|
|
547
|
+
}
|
|
548
|
+
case "memory_timeline": {
|
|
549
|
+
const memoryId = typeof args.memoryId === "string" ? args.memoryId : "";
|
|
550
|
+
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
551
|
+
return { content: await this.executor.memoryTimeline(memoryId, limit) };
|
|
552
|
+
}
|
|
553
|
+
case "recall_explain": {
|
|
554
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
555
|
+
return { content: await this.executor.recallExplain(query) };
|
|
556
|
+
}
|
|
557
|
+
case "entity_get": {
|
|
558
|
+
const entityName = typeof args.name === "string" ? args.name : "";
|
|
559
|
+
return { content: await this.executor.entityGet(entityName) };
|
|
560
|
+
}
|
|
561
|
+
case "stats": {
|
|
562
|
+
return { content: await this.executor.stats() };
|
|
563
|
+
}
|
|
564
|
+
case "correction_plan": {
|
|
565
|
+
const request = typeof args.request === "string" ? args.request : "";
|
|
566
|
+
const plan = await this.executor.correctionPlan(request);
|
|
567
|
+
return { content: JSON.stringify(plan) };
|
|
568
|
+
}
|
|
569
|
+
case "correction_apply": {
|
|
570
|
+
const planId = typeof args.planId === "string" ? args.planId : "";
|
|
571
|
+
return { content: await this.executor.correctionApply(planId) };
|
|
572
|
+
}
|
|
573
|
+
case "memory_promote": {
|
|
574
|
+
const memoryId = typeof args.memoryId === "string" ? args.memoryId : "";
|
|
575
|
+
return { content: await this.executor.memoryPromote(memoryId) };
|
|
576
|
+
}
|
|
577
|
+
case "review_list": {
|
|
578
|
+
const runId = typeof args.runId === "string" ? args.runId : void 0;
|
|
579
|
+
return { content: await this.executor.reviewList(runId) };
|
|
580
|
+
}
|
|
581
|
+
case "scope_inspect": {
|
|
582
|
+
return { content: await this.executor.scopeInspect() };
|
|
583
|
+
}
|
|
584
|
+
default:
|
|
585
|
+
return { content: JSON.stringify({ error: `Unknown tool: ${name}` }) };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
function looksLikeMemoryAssertion(text) {
|
|
590
|
+
const lower = text.toLowerCase();
|
|
591
|
+
const assertionPhrases = [
|
|
592
|
+
"you remember",
|
|
593
|
+
"i remember",
|
|
594
|
+
"according to my memory",
|
|
595
|
+
"your memory",
|
|
596
|
+
"stored memory",
|
|
597
|
+
"remnic remembers",
|
|
598
|
+
"remnic knows"
|
|
599
|
+
];
|
|
600
|
+
const hasAssertion = assertionPhrases.some((p) => lower.includes(p));
|
|
601
|
+
const hasCitation = lower.includes("[id:") || lower.includes("memory id") || lower.includes("source:");
|
|
602
|
+
return hasAssertion && !hasCitation;
|
|
603
|
+
}
|
|
604
|
+
function formatPlanPreview(preview) {
|
|
605
|
+
return [
|
|
606
|
+
"Here is the correction plan I generated:",
|
|
607
|
+
"",
|
|
608
|
+
"```diff",
|
|
609
|
+
preview,
|
|
610
|
+
"```",
|
|
611
|
+
"",
|
|
612
|
+
"Reply **apply** to confirm and apply this correction.",
|
|
613
|
+
"Reply **cancel** to discard it."
|
|
614
|
+
].join("\n");
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/chat/chat-llm.ts
|
|
618
|
+
function createProductionChatLlmAdapter(client) {
|
|
619
|
+
return {
|
|
620
|
+
async complete(messages, options) {
|
|
621
|
+
const toolInstruction = formatToolInstruction(options.tools);
|
|
622
|
+
const mapped = messages.map((m) => {
|
|
623
|
+
if (m.role === "system") {
|
|
624
|
+
return { role: m.role, content: m.content + "\n\n" + toolInstruction };
|
|
625
|
+
}
|
|
626
|
+
if (m.role === "tool") {
|
|
627
|
+
const tag = m.toolCallId ? `[tool:${m.toolCallId}]` : "[tool]";
|
|
628
|
+
return { role: "user", content: `${tag} ${m.content}` };
|
|
629
|
+
}
|
|
630
|
+
return { role: m.role, content: m.content };
|
|
631
|
+
});
|
|
632
|
+
const result = await client.chatCompletion(mapped, {
|
|
633
|
+
...options.model ? { model: options.model } : {},
|
|
634
|
+
...options.signal ? { signal: options.signal } : {}
|
|
635
|
+
});
|
|
636
|
+
if (!result) return null;
|
|
637
|
+
const toolCalls = parseToolCalls(result.content);
|
|
638
|
+
return {
|
|
639
|
+
content: stripToolCallJson(result.content, toolCalls),
|
|
640
|
+
toolCalls
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function formatToolInstruction(tools) {
|
|
646
|
+
if (tools.length === 0) return "";
|
|
647
|
+
const lines = [
|
|
648
|
+
"## Available tools",
|
|
649
|
+
"",
|
|
650
|
+
"You may call the following tools by emitting a JSON block on its own line:",
|
|
651
|
+
"```json",
|
|
652
|
+
'{"tool": "<name>", "arguments": { ... }}',
|
|
653
|
+
"```",
|
|
654
|
+
"",
|
|
655
|
+
"Call one tool at a time. After receiving the tool result, continue your",
|
|
656
|
+
"reasoning or emit another tool call. When you have a final answer, reply",
|
|
657
|
+
"with text only (no tool-call JSON).",
|
|
658
|
+
"",
|
|
659
|
+
"Tools:"
|
|
660
|
+
];
|
|
661
|
+
for (const t of tools) {
|
|
662
|
+
const params = JSON.stringify(t.function.parameters);
|
|
663
|
+
lines.push(`- ${t.function.name}: ${t.function.description}`);
|
|
664
|
+
lines.push(` Parameters: ${params}`);
|
|
665
|
+
}
|
|
666
|
+
return lines.join("\n");
|
|
667
|
+
}
|
|
668
|
+
function parseToolCalls(content) {
|
|
669
|
+
const calls = [];
|
|
670
|
+
const patterns = [
|
|
671
|
+
/```json\s*\n([\s\S]*?)\n```/g,
|
|
672
|
+
/```\s*\n([\s\S]*?)\n```/g
|
|
673
|
+
];
|
|
674
|
+
const candidates = [];
|
|
675
|
+
for (const pattern of patterns) {
|
|
676
|
+
let match;
|
|
677
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
678
|
+
candidates.push(match[1]);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
for (const line of content.split("\n")) {
|
|
682
|
+
const trimmed = line.trim();
|
|
683
|
+
if (trimmed.startsWith("{") && trimmed.includes('"tool"')) {
|
|
684
|
+
candidates.push(trimmed);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
688
|
+
for (const candidate of candidates) {
|
|
689
|
+
try {
|
|
690
|
+
const parsed = JSON.parse(candidate);
|
|
691
|
+
if (typeof parsed.tool === "string" && parsed.tool.length > 0) {
|
|
692
|
+
const args = parsed.arguments ?? {};
|
|
693
|
+
const key = parsed.tool + ":" + JSON.stringify(args);
|
|
694
|
+
if (seen.has(key)) continue;
|
|
695
|
+
seen.add(key);
|
|
696
|
+
calls.push({
|
|
697
|
+
id: `call_${calls.length + 1}`,
|
|
698
|
+
name: parsed.tool,
|
|
699
|
+
arguments: args
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return calls;
|
|
706
|
+
}
|
|
707
|
+
function stripToolCallJson(content, toolCalls) {
|
|
708
|
+
if (toolCalls.length === 0) return content;
|
|
709
|
+
let result = content;
|
|
710
|
+
result = result.replace(/```json\s*\n[\s\S]*?\n```/g, "");
|
|
711
|
+
result = result.replace(/```\s*\n[\s\S]*?\n```/g, "");
|
|
712
|
+
result = result.split("\n").filter((line) => {
|
|
713
|
+
const trimmed = line.trim();
|
|
714
|
+
return !(trimmed.startsWith("{") && trimmed.includes('"tool"'));
|
|
715
|
+
}).join("\n");
|
|
716
|
+
return result.trim();
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/chat/chat-executor.ts
|
|
720
|
+
function createChatExecutor(bindings) {
|
|
721
|
+
const { service, principal, namespace, sessionKey } = bindings;
|
|
722
|
+
return {
|
|
723
|
+
async memorySearch(query, maxResults) {
|
|
724
|
+
const result = await service.memorySearch({
|
|
725
|
+
query,
|
|
726
|
+
...namespace ? { namespace } : {},
|
|
727
|
+
...maxResults !== void 0 ? { maxResults } : {},
|
|
728
|
+
...principal ? { principal } : {}
|
|
729
|
+
});
|
|
730
|
+
return JSON.stringify(result);
|
|
731
|
+
},
|
|
732
|
+
async memoryGet(memoryId) {
|
|
733
|
+
const result = await service.memoryGet(memoryId, namespace, principal, sessionKey);
|
|
734
|
+
return JSON.stringify(result);
|
|
735
|
+
},
|
|
736
|
+
async memoryTimeline(memoryId, limit) {
|
|
737
|
+
const result = await service.memoryTimeline(memoryId, namespace, limit ?? 200, principal);
|
|
738
|
+
return JSON.stringify(result);
|
|
739
|
+
},
|
|
740
|
+
async recallExplain(query) {
|
|
741
|
+
const result = await service.recallExplain({
|
|
742
|
+
...sessionKey ? { sessionKey } : {},
|
|
743
|
+
...namespace ? { namespace } : {},
|
|
744
|
+
...principal ? { authenticatedPrincipal: principal } : {},
|
|
745
|
+
...query ? { query } : {}
|
|
746
|
+
});
|
|
747
|
+
return JSON.stringify(result);
|
|
748
|
+
},
|
|
749
|
+
async entityGet(name) {
|
|
750
|
+
const result = await service.entityGet(name, namespace);
|
|
751
|
+
return JSON.stringify(result);
|
|
752
|
+
},
|
|
753
|
+
async stats() {
|
|
754
|
+
const [profile, entities, questions] = await Promise.all([
|
|
755
|
+
service.memoryProfile(namespace, principal),
|
|
756
|
+
service.memoryEntitiesList(namespace, principal),
|
|
757
|
+
service.memoryQuestions(namespace, principal)
|
|
758
|
+
]);
|
|
759
|
+
return JSON.stringify({ profile, entities, questions });
|
|
760
|
+
},
|
|
761
|
+
async reviewList(runId) {
|
|
762
|
+
const result = await service.reviewQueue(runId, namespace, principal);
|
|
763
|
+
return JSON.stringify(result);
|
|
764
|
+
},
|
|
765
|
+
async scopeInspect() {
|
|
766
|
+
return JSON.stringify({
|
|
767
|
+
namespace: namespace ?? "default",
|
|
768
|
+
principal: principal ?? "default",
|
|
769
|
+
sessionKey: sessionKey ?? void 0
|
|
770
|
+
});
|
|
771
|
+
},
|
|
772
|
+
// ── Correction tools — stubbed until #1580 merges ──────────────────
|
|
773
|
+
// When the Correction Contract lands, replace these stubs with calls to
|
|
774
|
+
// the correction module's plan/apply interface. The engine, CLI, and
|
|
775
|
+
// MCP tool all consume this interface, so the stub-to-real swap is a
|
|
776
|
+
// single-file change here.
|
|
777
|
+
async correctionPlan(request) {
|
|
778
|
+
return {
|
|
779
|
+
planId: `stub-${Date.now()}`,
|
|
780
|
+
preview: `[Correction Contract (#1580) not yet available]
|
|
781
|
+
|
|
782
|
+
Requested correction: "${request}"
|
|
783
|
+
|
|
784
|
+
The plan/apply pipeline is pending. Inspection tools are fully functional.`
|
|
785
|
+
};
|
|
786
|
+
},
|
|
787
|
+
async correctionApply(planId) {
|
|
788
|
+
return JSON.stringify({
|
|
789
|
+
applied: false,
|
|
790
|
+
planId,
|
|
791
|
+
reason: "Correction Contract (#1580) not yet available. No mutation was applied."
|
|
792
|
+
});
|
|
793
|
+
},
|
|
794
|
+
async memoryPromote(memoryId) {
|
|
795
|
+
try {
|
|
796
|
+
const serviceWithPromote = service;
|
|
797
|
+
if (typeof serviceWithPromote.memoryPromote === "function") {
|
|
798
|
+
const result = await serviceWithPromote.memoryPromote(memoryId, namespace, principal);
|
|
799
|
+
return JSON.stringify(result);
|
|
800
|
+
}
|
|
801
|
+
} catch {
|
|
802
|
+
}
|
|
803
|
+
return JSON.stringify({
|
|
804
|
+
promoted: false,
|
|
805
|
+
memoryId,
|
|
806
|
+
reason: "Memory promotion is not available in this build."
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/chat/chat-session.ts
|
|
813
|
+
import { mkdir, readFile, appendFile, readdir, stat, unlink } from "fs/promises";
|
|
814
|
+
import { join } from "path";
|
|
39
815
|
import { randomUUID } from "crypto";
|
|
816
|
+
var CHAT_DIR_NAME = "state/chat";
|
|
817
|
+
function chatSessionDir(memoryDir) {
|
|
818
|
+
return join(memoryDir, CHAT_DIR_NAME);
|
|
819
|
+
}
|
|
820
|
+
function isSafeChatSessionId(chatSessionId) {
|
|
821
|
+
return /^[A-Za-z0-9]{8,128}$/.test(chatSessionId.replace(/-/g, ""));
|
|
822
|
+
}
|
|
823
|
+
function chatSessionFile(memoryDir, chatSessionId) {
|
|
824
|
+
if (!isSafeChatSessionId(chatSessionId)) {
|
|
825
|
+
throw new Error("Invalid chat session id");
|
|
826
|
+
}
|
|
827
|
+
return join(chatSessionDir(memoryDir), `${chatSessionId}.jsonl`);
|
|
828
|
+
}
|
|
829
|
+
async function createChatSession(memoryDir, opts) {
|
|
830
|
+
const id = randomUUID();
|
|
831
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
832
|
+
const state = {
|
|
833
|
+
id,
|
|
834
|
+
principal: opts.principal,
|
|
835
|
+
sessionKey: opts.sessionKey,
|
|
836
|
+
namespace: opts.namespace,
|
|
837
|
+
transcript: [],
|
|
838
|
+
confirmedPlanIds: /* @__PURE__ */ new Set(),
|
|
839
|
+
createdAt: now
|
|
840
|
+
};
|
|
841
|
+
await mkdir(chatSessionDir(memoryDir), { recursive: true });
|
|
842
|
+
await appendFile(
|
|
843
|
+
chatSessionFile(memoryDir, id),
|
|
844
|
+
JSON.stringify({
|
|
845
|
+
seq: 0,
|
|
846
|
+
ts: now,
|
|
847
|
+
role: "system",
|
|
848
|
+
content: `Chat session created. Principal: ${opts.principal ?? "default"}. Namespace: ${opts.namespace ?? "default"}. work_task: Excluded from memory extraction`,
|
|
849
|
+
// Structured binding fields so loadChatSession doesn't need to regex-parse
|
|
850
|
+
// prose (robust against dotted principals like alice@example.com).
|
|
851
|
+
...opts.principal ? { principal: opts.principal } : {},
|
|
852
|
+
...opts.namespace ? { namespace: opts.namespace } : {},
|
|
853
|
+
...opts.sessionKey ? { sessionKey: opts.sessionKey } : {}
|
|
854
|
+
}) + "\n",
|
|
855
|
+
"utf8"
|
|
856
|
+
);
|
|
857
|
+
return state;
|
|
858
|
+
}
|
|
859
|
+
async function loadChatSession(memoryDir, chatSessionId) {
|
|
860
|
+
let raw;
|
|
861
|
+
try {
|
|
862
|
+
raw = await readFile(chatSessionFile(memoryDir, chatSessionId), "utf8");
|
|
863
|
+
} catch {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
867
|
+
const transcript = [];
|
|
868
|
+
let principal;
|
|
869
|
+
let sessionKey;
|
|
870
|
+
let namespace;
|
|
871
|
+
let createdAt = "";
|
|
872
|
+
let pendingPlanId;
|
|
873
|
+
let pendingPromotionId;
|
|
874
|
+
for (const line of lines) {
|
|
875
|
+
try {
|
|
876
|
+
const entry = JSON.parse(line);
|
|
877
|
+
transcript.push(entry);
|
|
878
|
+
if (entry.role === "system" && entry.seq === 0) {
|
|
879
|
+
const metaPrincipal = typeof entry.principal === "string" ? entry.principal : void 0;
|
|
880
|
+
const metaNamespace = typeof entry.namespace === "string" ? entry.namespace : void 0;
|
|
881
|
+
const metaSessionKey = typeof entry.sessionKey === "string" ? entry.sessionKey : void 0;
|
|
882
|
+
if (metaPrincipal) principal = metaPrincipal;
|
|
883
|
+
else {
|
|
884
|
+
const pMatch = entry.content.match(/Principal: (.+?)\.\s/);
|
|
885
|
+
if (pMatch) principal = pMatch[1]?.trim() === "default" ? void 0 : pMatch[1]?.trim();
|
|
886
|
+
}
|
|
887
|
+
if (metaNamespace) namespace = metaNamespace;
|
|
888
|
+
else {
|
|
889
|
+
const nsMatch = entry.content.match(/Namespace: (.+?)\.\s/);
|
|
890
|
+
if (nsMatch) namespace = nsMatch[1]?.trim() === "default" ? void 0 : nsMatch[1]?.trim();
|
|
891
|
+
}
|
|
892
|
+
if (metaSessionKey) sessionKey = metaSessionKey;
|
|
893
|
+
createdAt = entry.ts;
|
|
894
|
+
}
|
|
895
|
+
if (entry.role === "system") {
|
|
896
|
+
const pm = entry.content.match(/^pending_plan:(.+)$/);
|
|
897
|
+
if (pm) pendingPlanId = pm[1];
|
|
898
|
+
const pp = entry.content.match(/^pending_promotion:(.+)$/);
|
|
899
|
+
if (pp) pendingPromotionId = pp[1];
|
|
900
|
+
const resolved = entry.content.match(/^(plan_applied|plan_cancelled|promotion_applied|promotion_cancelled):/);
|
|
901
|
+
if (resolved) {
|
|
902
|
+
if (entry.content.startsWith("plan_")) {
|
|
903
|
+
pendingPlanId = void 0;
|
|
904
|
+
}
|
|
905
|
+
if (entry.content.startsWith("promotion_")) {
|
|
906
|
+
pendingPromotionId = void 0;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return {
|
|
914
|
+
id: chatSessionId,
|
|
915
|
+
principal,
|
|
916
|
+
sessionKey,
|
|
917
|
+
namespace,
|
|
918
|
+
transcript,
|
|
919
|
+
confirmedPlanIds: /* @__PURE__ */ new Set(),
|
|
920
|
+
...pendingPlanId ? { pendingPlanId } : {},
|
|
921
|
+
...pendingPromotionId ? { pendingPromotionId } : {},
|
|
922
|
+
createdAt
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
async function appendTranscriptEntry(memoryDir, chatSessionId, entry) {
|
|
926
|
+
const full = {
|
|
927
|
+
...entry,
|
|
928
|
+
seq: Date.now(),
|
|
929
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
930
|
+
};
|
|
931
|
+
await appendFile(
|
|
932
|
+
chatSessionFile(memoryDir, chatSessionId),
|
|
933
|
+
JSON.stringify(full) + "\n",
|
|
934
|
+
"utf8"
|
|
935
|
+
);
|
|
936
|
+
return full;
|
|
937
|
+
}
|
|
938
|
+
async function markPendingPlan(memoryDir, chatSessionId, planId) {
|
|
939
|
+
await appendTranscriptEntry(memoryDir, chatSessionId, {
|
|
940
|
+
role: "system",
|
|
941
|
+
content: `pending_plan:${planId}`
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
async function markPendingPromotion(memoryDir, chatSessionId, memoryId) {
|
|
945
|
+
await appendTranscriptEntry(memoryDir, chatSessionId, {
|
|
946
|
+
role: "system",
|
|
947
|
+
content: `pending_promotion:${memoryId}`
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
async function markPlanResolved(memoryDir, chatSessionId, planId) {
|
|
951
|
+
await appendTranscriptEntry(memoryDir, chatSessionId, {
|
|
952
|
+
role: "system",
|
|
953
|
+
content: `plan_applied:${planId}`
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
async function markPromotionResolved(memoryDir, chatSessionId, memoryId) {
|
|
957
|
+
await appendTranscriptEntry(memoryDir, chatSessionId, {
|
|
958
|
+
role: "system",
|
|
959
|
+
content: `promotion_applied:${memoryId}`
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
function sessionBelongsToPrincipal(session, principal) {
|
|
963
|
+
if (!session.principal) return true;
|
|
964
|
+
return session.principal === principal;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/chat/chat-factory.ts
|
|
968
|
+
function createChatEngine(opts) {
|
|
969
|
+
const llmClient = opts.fallbackLlm ?? opts.localLlm;
|
|
970
|
+
if (!llmClient) return null;
|
|
971
|
+
const adapter = createProductionChatLlmAdapter(llmClient);
|
|
972
|
+
const executor = createChatExecutor({
|
|
973
|
+
service: opts.service,
|
|
974
|
+
principal: opts.principal,
|
|
975
|
+
namespace: opts.namespace,
|
|
976
|
+
sessionKey: opts.sessionKey
|
|
977
|
+
});
|
|
978
|
+
return new ChatEngine({
|
|
979
|
+
llm: adapter,
|
|
980
|
+
executor,
|
|
981
|
+
maxToolCallsPerTurn: opts.config.maxToolCallsPerTurn,
|
|
982
|
+
...opts.config.model ? { model: opts.config.model } : {},
|
|
983
|
+
correctionAvailable: false,
|
|
984
|
+
scopeInspectAvailable: false
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
async function processChatMessage(opts) {
|
|
988
|
+
if (!opts.config?.enabled) {
|
|
989
|
+
throw new Error("chat_disabled");
|
|
990
|
+
}
|
|
991
|
+
if (!opts.service.fallbackLlmRef && !opts.service.localLlmRef) {
|
|
992
|
+
throw new Error("no_llm_available");
|
|
993
|
+
}
|
|
994
|
+
let session;
|
|
995
|
+
if (opts.chatSessionId) {
|
|
996
|
+
session = await loadChatSession(opts.memoryDir, opts.chatSessionId);
|
|
997
|
+
if (!session) throw new Error("chat_session_not_found");
|
|
998
|
+
if (!sessionBelongsToPrincipal(session, opts.principal)) {
|
|
999
|
+
throw new Error("access_denied");
|
|
1000
|
+
}
|
|
1001
|
+
} else {
|
|
1002
|
+
session = await createChatSession(opts.memoryDir, {
|
|
1003
|
+
principal: opts.principal,
|
|
1004
|
+
...opts.namespace ? { namespace: opts.namespace } : {},
|
|
1005
|
+
...opts.sessionKey ? { sessionKey: opts.sessionKey } : {}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const engine = createChatEngine({
|
|
1009
|
+
service: opts.service,
|
|
1010
|
+
config: opts.config,
|
|
1011
|
+
fallbackLlm: opts.service.fallbackLlmRef,
|
|
1012
|
+
localLlm: opts.service.localLlmRef,
|
|
1013
|
+
principal: opts.principal,
|
|
1014
|
+
...session.namespace ? { namespace: session.namespace } : {},
|
|
1015
|
+
...session.sessionKey ? { sessionKey: session.sessionKey } : {}
|
|
1016
|
+
});
|
|
1017
|
+
if (!engine) {
|
|
1018
|
+
throw new Error("engine_unavailable");
|
|
1019
|
+
}
|
|
1020
|
+
await appendTranscriptEntry(opts.memoryDir, session.id, {
|
|
1021
|
+
role: "user",
|
|
1022
|
+
content: opts.message
|
|
1023
|
+
});
|
|
1024
|
+
const priorPendingPlanId = session.pendingPlanId;
|
|
1025
|
+
const priorPendingPromotionId = session.pendingPromotionId;
|
|
1026
|
+
const result = await engine.processMessage(opts.message, session);
|
|
1027
|
+
await appendTranscriptEntry(opts.memoryDir, session.id, {
|
|
1028
|
+
role: "assistant",
|
|
1029
|
+
content: result.reply
|
|
1030
|
+
});
|
|
1031
|
+
if (result.pendingPlan?.planId) {
|
|
1032
|
+
await markPendingPlan(opts.memoryDir, session.id, result.pendingPlan.planId);
|
|
1033
|
+
} else if (session.pendingPromotionId && !priorPendingPromotionId) {
|
|
1034
|
+
await markPendingPromotion(opts.memoryDir, session.id, session.pendingPromotionId);
|
|
1035
|
+
} else if (priorPendingPlanId && !session.pendingPlanId) {
|
|
1036
|
+
await markPlanResolved(opts.memoryDir, session.id, priorPendingPlanId);
|
|
1037
|
+
} else if (priorPendingPromotionId && !session.pendingPromotionId) {
|
|
1038
|
+
await markPromotionResolved(opts.memoryDir, session.id, priorPendingPromotionId);
|
|
1039
|
+
}
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/access-mcp.ts
|
|
40
1044
|
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
41
1045
|
var LEGACY_MCP_PREFIX = "engram.";
|
|
42
1046
|
var CANONICAL_MCP_PREFIX = "remnic.";
|
|
@@ -81,7 +1085,7 @@ var MCP_MIGRATED_OPERATIONS = {
|
|
|
81
1085
|
function resolveChatGptInspectorRecallSessionKey(explicitSessionKey, authenticatedPrincipal) {
|
|
82
1086
|
if (explicitSessionKey) return explicitSessionKey;
|
|
83
1087
|
if (!authenticatedPrincipal) return void 0;
|
|
84
|
-
return `remnic:chatgpt-memory-inspector:${
|
|
1088
|
+
return `remnic:chatgpt-memory-inspector:${randomUUID2()}`;
|
|
85
1089
|
}
|
|
86
1090
|
var STRICT_MCP_SCHEMA_KEYS = {
|
|
87
1091
|
daySummary: [
|
|
@@ -202,7 +1206,7 @@ async function getMcpServerVersion() {
|
|
|
202
1206
|
if (envVersion) return envVersion;
|
|
203
1207
|
try {
|
|
204
1208
|
const pkgPath = new URL("../package.json", import.meta.url);
|
|
205
|
-
const raw = await
|
|
1209
|
+
const raw = await readFile2(pkgPath, "utf-8");
|
|
206
1210
|
const parsed = JSON.parse(raw);
|
|
207
1211
|
return parsed.version?.trim() || "unknown";
|
|
208
1212
|
} catch {
|
|
@@ -220,6 +1224,7 @@ var EngramMcpServer = class {
|
|
|
220
1224
|
this.codegraphVisible = options.codegraphVisible === true;
|
|
221
1225
|
this.sessionDeltaVisible = options.sessionDeltaVisible === true;
|
|
222
1226
|
this.correctionVisible = options.correctionVisible !== false;
|
|
1227
|
+
this.chatVisible = options.chatVisible === true;
|
|
223
1228
|
this.authenticatedPrincipal = options.principal?.trim() || readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() || void 0;
|
|
224
1229
|
this.resources = [
|
|
225
1230
|
{
|
|
@@ -806,7 +1811,12 @@ var EngramMcpServer = class {
|
|
|
806
1811
|
type: "object",
|
|
807
1812
|
properties: {
|
|
808
1813
|
memoryId: { type: "string" },
|
|
809
|
-
namespace: { type: "string" }
|
|
1814
|
+
namespace: { type: "string" },
|
|
1815
|
+
// Issue #1582 — when memoryId is a `[m:xxxx]` handle, sessionKey
|
|
1816
|
+
// scopes resolution to this session's recent recall history.
|
|
1817
|
+
// Advertising it here lets the transport inject its session key
|
|
1818
|
+
// (toolAcceptsArgument) so MCP callers can cite handles (codex review).
|
|
1819
|
+
sessionKey: { type: "string" }
|
|
810
1820
|
},
|
|
811
1821
|
required: ["memoryId"],
|
|
812
1822
|
additionalProperties: false
|
|
@@ -960,6 +1970,10 @@ var EngramMcpServer = class {
|
|
|
960
1970
|
},
|
|
961
1971
|
description: "Conversation messages to observe"
|
|
962
1972
|
},
|
|
1973
|
+
idempotencyKey: {
|
|
1974
|
+
type: "string",
|
|
1975
|
+
description: "Optional idempotency key (issue #1649). Deduplicates a retried observe POST server-side so the batch is ingested once even when the HTTP response is lost. Reusing the key with a different payload is rejected."
|
|
1976
|
+
},
|
|
963
1977
|
namespace: { type: "string" },
|
|
964
1978
|
skipExtraction: { type: "boolean" },
|
|
965
1979
|
cwd: { type: "string", description: "Working directory for auto git-context resolution." },
|
|
@@ -1988,6 +3002,25 @@ var EngramMcpServer = class {
|
|
|
1988
3002
|
);
|
|
1989
3003
|
this.tools = [...this.tools, ...planTool, ...applyTool];
|
|
1990
3004
|
}
|
|
3005
|
+
if (this.chatVisible) {
|
|
3006
|
+
const chatTools = withToolAliases(
|
|
3007
|
+
{
|
|
3008
|
+
name: "engram.memory_chat",
|
|
3009
|
+
description: "Conversational memory inspection and correction (issue #1583). Send a message to chat about what Remnic remembers, inspect memories, or request corrections. Returns {reply, chatSessionId, pendingPlan?}.",
|
|
3010
|
+
inputSchema: {
|
|
3011
|
+
type: "object",
|
|
3012
|
+
properties: {
|
|
3013
|
+
message: { type: "string", description: "The user's message \u2014 a question about memories, a correction request, or a confirmation (yes/apply) to proceed with a pending plan." },
|
|
3014
|
+
chatSessionId: { type: "string", description: "Optional existing chat session id to resume. Omit to start a new session." }
|
|
3015
|
+
},
|
|
3016
|
+
required: ["message"],
|
|
3017
|
+
additionalProperties: false
|
|
3018
|
+
}
|
|
3019
|
+
},
|
|
3020
|
+
this.emitLegacyTools
|
|
3021
|
+
);
|
|
3022
|
+
this.tools = [...this.tools, ...chatTools];
|
|
3023
|
+
}
|
|
1991
3024
|
}
|
|
1992
3025
|
service;
|
|
1993
3026
|
buffer = Buffer.alloc(0);
|
|
@@ -2049,6 +3082,12 @@ var EngramMcpServer = class {
|
|
|
2049
3082
|
* byte-identical to pre-feature.
|
|
2050
3083
|
*/
|
|
2051
3084
|
correctionVisible;
|
|
3085
|
+
/**
|
|
3086
|
+
* Whether the `memory_chat` tool should appear in `tools/list` (issue
|
|
3087
|
+
* #1583). Gated on `chat.enabled`. When false the tools array is
|
|
3088
|
+
* byte-identical to pre-feature (rule 39).
|
|
3089
|
+
*/
|
|
3090
|
+
chatVisible;
|
|
2052
3091
|
/** Get clientInfo for a specific MCP session. Returns undefined for non-MCP requests. */
|
|
2053
3092
|
getClientInfo(sessionId) {
|
|
2054
3093
|
if (sessionId) {
|
|
@@ -2072,7 +3111,7 @@ var EngramMcpServer = class {
|
|
|
2072
3111
|
if (method === "initialize") {
|
|
2073
3112
|
const params = request.params ?? {};
|
|
2074
3113
|
const rawClientInfo = params.clientInfo;
|
|
2075
|
-
const newSessionId =
|
|
3114
|
+
const newSessionId = randomUUID2();
|
|
2076
3115
|
if (rawClientInfo && typeof rawClientInfo.name === "string") {
|
|
2077
3116
|
const info = { name: rawClientInfo.name, version: rawClientInfo.version };
|
|
2078
3117
|
this.clientInfoBySession.set(newSessionId, info);
|
|
@@ -2186,7 +3225,11 @@ var EngramMcpServer = class {
|
|
|
2186
3225
|
argumentsObject = { ...argumentsObject, sessionKey: options.sessionKeyOverride };
|
|
2187
3226
|
}
|
|
2188
3227
|
const effectivePrincipal = options?.principalOverride ?? this.authenticatedPrincipal;
|
|
2189
|
-
const
|
|
3228
|
+
const mcpScope = {
|
|
3229
|
+
...options?.namespaceOverride ? { namespace: options.namespaceOverride } : {},
|
|
3230
|
+
...options?.sessionKeyOverride ? { sessionKey: options.sessionKeyOverride } : {}
|
|
3231
|
+
};
|
|
3232
|
+
const result = await this.callTool(name, argumentsObject, effectivePrincipal, options?.sessionId, mcpScope, options?.enforceWriteQuota);
|
|
2190
3233
|
return {
|
|
2191
3234
|
jsonrpc: "2.0",
|
|
2192
3235
|
id,
|
|
@@ -2351,7 +3394,7 @@ ${body}`;
|
|
|
2351
3394
|
noteDefault: r.preview?.slice(0, 60) || r.id
|
|
2352
3395
|
}));
|
|
2353
3396
|
}
|
|
2354
|
-
async callTool(name, args, effectivePrincipal, mcpSessionId) {
|
|
3397
|
+
async callTool(name, args, effectivePrincipal, mcpSessionId, scope, enforceWriteQuota) {
|
|
2355
3398
|
const migrated = MCP_MIGRATED_OPERATIONS[toLegacyToolName(name)];
|
|
2356
3399
|
if (migrated) {
|
|
2357
3400
|
const op = getOperation(migrated);
|
|
@@ -2596,7 +3639,7 @@ ${body}`;
|
|
|
2596
3639
|
}
|
|
2597
3640
|
let currentContextScopes;
|
|
2598
3641
|
if (args.currentContextScopes !== void 0 && args.currentContextScopes !== null) {
|
|
2599
|
-
if (!Array.isArray(args.currentContextScopes) || !args.currentContextScopes.every((
|
|
3642
|
+
if (!Array.isArray(args.currentContextScopes) || !args.currentContextScopes.every((scope2) => typeof scope2 === "string")) {
|
|
2600
3643
|
throw new EngramAccessInputError(
|
|
2601
3644
|
"currentContextScopes must be an array of strings"
|
|
2602
3645
|
);
|
|
@@ -2773,21 +3816,27 @@ ${body}`;
|
|
|
2773
3816
|
);
|
|
2774
3817
|
case "engram.observe": {
|
|
2775
3818
|
const body = parseMcpRequest("observe", args);
|
|
2776
|
-
return this.service.observe(
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3819
|
+
return this.service.observe(
|
|
3820
|
+
{
|
|
3821
|
+
sessionKey: body.sessionKey,
|
|
3822
|
+
messages: body.messages.map((message) => ({
|
|
3823
|
+
role: message.role,
|
|
3824
|
+
content: message.content,
|
|
3825
|
+
parts: message.parts ?? void 0,
|
|
3826
|
+
rawContent: message.rawContent ?? void 0,
|
|
3827
|
+
sourceFormat: message.sourceFormat ?? void 0
|
|
3828
|
+
})),
|
|
3829
|
+
skipExtraction: body.skipExtraction === true,
|
|
3830
|
+
idempotencyKey: body.idempotencyKey,
|
|
3831
|
+
namespace: body.namespace,
|
|
3832
|
+
authenticatedPrincipal: effectivePrincipal,
|
|
3833
|
+
cwd: body.cwd,
|
|
3834
|
+
projectTag: body.projectTag
|
|
3835
|
+
},
|
|
3836
|
+
// Forward the transport's write-quota enforcer into observe's
|
|
3837
|
+
// idempotency lock so a replay is never 429'd (issue #1649).
|
|
3838
|
+
enforceWriteQuota ? { enforceWriteQuota } : void 0
|
|
3839
|
+
);
|
|
2791
3840
|
}
|
|
2792
3841
|
case "engram.lcm_search":
|
|
2793
3842
|
return this.service.lcmSearch({
|
|
@@ -3350,6 +4399,28 @@ ${body}`;
|
|
|
3350
4399
|
authenticatedPrincipal: effectivePrincipal
|
|
3351
4400
|
});
|
|
3352
4401
|
}
|
|
4402
|
+
case "engram.memory_chat": {
|
|
4403
|
+
const message = typeof args.message === "string" ? args.message : "";
|
|
4404
|
+
if (!message) {
|
|
4405
|
+
throw new EngramAccessInputError("message is required");
|
|
4406
|
+
}
|
|
4407
|
+
const chatSessionId = typeof args.chatSessionId === "string" ? args.chatSessionId : void 0;
|
|
4408
|
+
const chatResult = await processChatMessage({
|
|
4409
|
+
service: this.service,
|
|
4410
|
+
config: this.service.configRef?.chat,
|
|
4411
|
+
memoryDir: this.service.memoryDir,
|
|
4412
|
+
message,
|
|
4413
|
+
...chatSessionId ? { chatSessionId } : {},
|
|
4414
|
+
...effectivePrincipal ? { principal: effectivePrincipal } : {},
|
|
4415
|
+
...scope?.namespace ? { namespace: scope.namespace } : {},
|
|
4416
|
+
...scope?.sessionKey ? { sessionKey: scope.sessionKey } : {}
|
|
4417
|
+
});
|
|
4418
|
+
if (chatResult && typeof chatResult === "object" && "error" in chatResult) {
|
|
4419
|
+
const { error: _stripped, ...wireResult } = chatResult;
|
|
4420
|
+
return wireResult;
|
|
4421
|
+
}
|
|
4422
|
+
return chatResult;
|
|
4423
|
+
}
|
|
3353
4424
|
default:
|
|
3354
4425
|
throw new Error(`unknown tool: ${name}`);
|
|
3355
4426
|
}
|
|
@@ -3357,6 +4428,17 @@ ${body}`;
|
|
|
3357
4428
|
};
|
|
3358
4429
|
|
|
3359
4430
|
export {
|
|
4431
|
+
ChatEngine,
|
|
4432
|
+
createProductionChatLlmAdapter,
|
|
4433
|
+
createChatExecutor,
|
|
4434
|
+
createChatSession,
|
|
4435
|
+
loadChatSession,
|
|
4436
|
+
appendTranscriptEntry,
|
|
4437
|
+
markPendingPlan,
|
|
4438
|
+
markPendingPromotion,
|
|
4439
|
+
markPlanResolved,
|
|
4440
|
+
markPromotionResolved,
|
|
4441
|
+
sessionBelongsToPrincipal,
|
|
3360
4442
|
EngramMcpServer
|
|
3361
4443
|
};
|
|
3362
|
-
//# sourceMappingURL=chunk-
|
|
4444
|
+
//# sourceMappingURL=chunk-SYDHFFAW.js.map
|