@remnic/core 9.3.713 → 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 +39 -38
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +23 -22
- package/dist/access-mcp.d.ts +12 -5
- package/dist/access-mcp.js +20 -19
- package/dist/access-operations-batch.js +18 -17
- package/dist/access-operations.d.ts +6 -5
- package/dist/access-operations.js +19 -18
- package/dist/{access-service-DwVmvgBg.d.ts → access-service-lddjZoRh.d.ts} +12 -4
- 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-ZDUF7K5V.js → chunk-3VUPWDSE.js} +15 -3
- package/dist/{chunk-ZDUF7K5V.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-UHN2TNSB.js → chunk-BNOAB5HH.js} +38 -11
- package/dist/chunk-BNOAB5HH.js.map +1 -0
- package/dist/{chunk-VW24K2Q4.js → chunk-CTAHKIG6.js} +169 -8
- 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-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-RMT7PAVW.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-VUNI6Y3D.js → chunk-IHMT6XTY.js} +8 -4
- 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-JC3MIKR4.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-DJNKN3XY.js → chunk-SYDHFFAW.js} +1084 -12
- 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-DfDMJqPP.d.ts → cli-iAiVkbYJ.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +42 -41
- 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 +61 -60
- 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/schemas.d.ts +22 -22
- 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/transfer/types.d.ts +12 -12
- 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 +33 -1
- package/src/access-mcp.test.ts +28 -0
- package/src/access-mcp.ts +67 -2
- package/src/access-operations-batch.ts +14 -0
- package/src/access-operations.ts +5 -0
- package/src/access-service.ts +43 -2
- 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-BOGENF7P.js.map +0 -1
- package/dist/chunk-DJNKN3XY.js.map +0 -1
- package/dist/chunk-GKPIY7EQ.js.map +0 -1
- package/dist/chunk-JC3MIKR4.js.map +0 -1
- package/dist/chunk-RMT7PAVW.js.map +0 -1
- package/dist/chunk-SINGJCUR.js.map +0 -1
- package/dist/chunk-UHN2TNSB.js.map +0 -1
- package/dist/chunk-VUNI6Y3D.js.map +0 -1
- package/dist/chunk-VW24K2Q4.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
package/src/access-http.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { getOperation } from "./access-boundary.js";
|
|
|
33
33
|
// (memory_get / memory_store) as a side effect; the HTTP handlers below
|
|
34
34
|
// dispatch the migrated routes through the registry (issue #1525).
|
|
35
35
|
import "./access-operations.js";
|
|
36
|
+
import { handleChatMessage, handleChatEventsSSE } from "./chat/chat-http.js";
|
|
36
37
|
|
|
37
38
|
export interface EngramAccessHttpServerOptions {
|
|
38
39
|
service: EngramAccessService;
|
|
@@ -320,6 +321,7 @@ export class EngramAccessHttpServer {
|
|
|
320
321
|
architectureCardVisible: this.service.architectureCardSurfaceVisible,
|
|
321
322
|
codegraphVisible: this.service.codegraphSurfaceVisible,
|
|
322
323
|
sessionDeltaVisible: this.service.sessionDeltaSurfaceVisible,
|
|
324
|
+
chatVisible: this.service.configRef?.chat?.enabled === true,
|
|
323
325
|
correctionVisible: this.service.correctionSurfaceVisible,
|
|
324
326
|
});
|
|
325
327
|
}
|
|
@@ -1561,6 +1563,14 @@ export class EngramAccessHttpServer {
|
|
|
1561
1563
|
if (req.method === "GET" && memoryMatch) {
|
|
1562
1564
|
const memoryId = decodeURIComponent(memoryMatch[1] ?? "");
|
|
1563
1565
|
const namespace = parsed.searchParams.get("namespace") ?? undefined;
|
|
1566
|
+
// Issue #1582 — thread the transport session key so a `[m:xxxx]` handle in
|
|
1567
|
+
// the path resolves against this session's recall history (codex review).
|
|
1568
|
+
// resolveRequestIdentity reads it from adapter identity / request headers.
|
|
1569
|
+
const sessionKey =
|
|
1570
|
+
this.resolveRequestIdentity(req).sessionKey
|
|
1571
|
+
?? parsed.searchParams.get("session")
|
|
1572
|
+
?? parsed.searchParams.get("sessionKey")
|
|
1573
|
+
?? undefined;
|
|
1564
1574
|
// Migrated through the access boundary (issue #1525): the registry
|
|
1565
1575
|
// entry owns memoryId presence/shape validation (rule 51: reject empty
|
|
1566
1576
|
// ids loudly instead of silently passing "" into the service) and the
|
|
@@ -1570,7 +1580,7 @@ export class EngramAccessHttpServer {
|
|
|
1570
1580
|
throw new EngramAccessInputError("access-boundary: operation not registered: memory_get");
|
|
1571
1581
|
}
|
|
1572
1582
|
const output = (await op.run(
|
|
1573
|
-
{ memoryId, namespace: namespace ?? null },
|
|
1583
|
+
{ memoryId, namespace: namespace ?? null, sessionKey: sessionKey ?? null },
|
|
1574
1584
|
{ service: this.service, authenticatedPrincipal: this.resolveRequestPrincipal(req) },
|
|
1575
1585
|
)) as { result: EngramAccessMemoryResponse };
|
|
1576
1586
|
const response = output.result;
|
|
@@ -2048,6 +2058,28 @@ export class EngramAccessHttpServer {
|
|
|
2048
2058
|
return;
|
|
2049
2059
|
}
|
|
2050
2060
|
|
|
2061
|
+
// ── Chat endpoints (issue #1583) ────────────────────────────────────────
|
|
2062
|
+
if (req.method === "POST" && pathname === "/engram/v1/chat/message") {
|
|
2063
|
+
void getOperation("chat_message"); // boundary dispatch (issue #1525)
|
|
2064
|
+
const body = await this.readJsonBody(req) as Record<string, unknown>;
|
|
2065
|
+
await handleChatMessage(
|
|
2066
|
+
req, res, body,
|
|
2067
|
+
{ service: this.service, config: this.service.configRef?.chat, memoryDir: this.service.memoryDir },
|
|
2068
|
+
this.resolveRequestPrincipal(req),
|
|
2069
|
+
);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
const chatEventsMatch = /^\/engram\/v1\/chat\/events\/([^/]+)$/.exec(pathname);
|
|
2073
|
+
if (req.method === "GET" && chatEventsMatch) {
|
|
2074
|
+
void getOperation("chat_events"); // boundary dispatch (issue #1525)
|
|
2075
|
+
await handleChatEventsSSE(
|
|
2076
|
+
req, res, chatEventsMatch[1] ?? "",
|
|
2077
|
+
{ service: this.service, config: this.service.configRef?.chat, memoryDir: this.service.memoryDir },
|
|
2078
|
+
this.resolveRequestPrincipal(req),
|
|
2079
|
+
);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2051
2083
|
// ── Peer Registry endpoints (issue #679) ─────────────────────────────────
|
|
2052
2084
|
// GET /engram/v1/console/state — operator console engine-state snapshot (issue #688 PR 2/3).
|
|
2053
2085
|
// Read-only; namespace-aware via resolveRequestPrincipal so cross-tenant
|
package/src/access-mcp.test.ts
CHANGED
|
@@ -485,6 +485,34 @@ test("MCP session override is injected only into tools that accept sessionKey",
|
|
|
485
485
|
assert.equal((observeResponse as Record<string, unknown> & { result?: { isError?: boolean } }).result?.isError, false);
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
+
test("MCP memory_get advertises sessionKey so the override reaches the resolver (#1582)", async () => {
|
|
489
|
+
// The engram.memory_get inputSchema must advertise sessionKey, otherwise
|
|
490
|
+
// toolAcceptsArgument skips the override injection and an MCP caller citing a
|
|
491
|
+
// [m:xxxx] handle gets "cannot be resolved without a session key" (codex
|
|
492
|
+
// review). With the field advertised, the transport session key flows through
|
|
493
|
+
// the registry op into service.memoryGet as the 4th positional arg.
|
|
494
|
+
let receivedSessionKey: string | undefined;
|
|
495
|
+
const service = {
|
|
496
|
+
...makeMockService(),
|
|
497
|
+
memoryGet: (_id: string, _ns?: string, _principal?: string, sessionKey?: string) => {
|
|
498
|
+
receivedSessionKey = sessionKey;
|
|
499
|
+
return Promise.resolve({ found: false, namespace: "default" });
|
|
500
|
+
},
|
|
501
|
+
} as unknown as EngramAccessService;
|
|
502
|
+
const server = new EngramMcpServer(service);
|
|
503
|
+
|
|
504
|
+
const response = await server.handleRequest(
|
|
505
|
+
makeToolRequest("engram.memory_get", { memoryId: "[m:4f2a]" }),
|
|
506
|
+
{ sessionKeyOverride: "adapter-session" },
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
assert.equal(receivedSessionKey, "adapter-session");
|
|
510
|
+
assert.equal(
|
|
511
|
+
(response as Record<string, unknown> & { result?: { isError?: boolean } }).result?.isError,
|
|
512
|
+
false,
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
|
|
488
516
|
test("MCP capsule import forwards encrypted archive passphrase", async () => {
|
|
489
517
|
let received: Record<string, unknown> | undefined;
|
|
490
518
|
const service = {
|
package/src/access-mcp.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
buildChatGptMemoryInspectorResult,
|
|
35
35
|
type RemnicChatGptMemoryInspectorInput,
|
|
36
36
|
} from "./mcp-memory-inspector-app.js";
|
|
37
|
+
import { processChatMessage } from "./chat/chat-factory.js";
|
|
37
38
|
|
|
38
39
|
type JsonRpcId = string | number | null;
|
|
39
40
|
|
|
@@ -371,6 +372,13 @@ export class EngramMcpServer {
|
|
|
371
372
|
*/
|
|
372
373
|
private readonly correctionVisible: boolean;
|
|
373
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Whether the `memory_chat` tool should appear in `tools/list` (issue
|
|
377
|
+
* #1583). Gated on `chat.enabled`. When false the tools array is
|
|
378
|
+
* byte-identical to pre-feature (rule 39).
|
|
379
|
+
*/
|
|
380
|
+
private readonly chatVisible: boolean;
|
|
381
|
+
|
|
374
382
|
constructor(
|
|
375
383
|
private readonly service: EngramAccessService,
|
|
376
384
|
options: {
|
|
@@ -383,6 +391,7 @@ export class EngramMcpServer {
|
|
|
383
391
|
codegraphVisible?: boolean;
|
|
384
392
|
sessionDeltaVisible?: boolean;
|
|
385
393
|
correctionVisible?: boolean;
|
|
394
|
+
chatVisible?: boolean;
|
|
386
395
|
} = {},
|
|
387
396
|
) {
|
|
388
397
|
this.citationsEnabled = options.citationsEnabled === true;
|
|
@@ -394,6 +403,7 @@ export class EngramMcpServer {
|
|
|
394
403
|
this.sessionDeltaVisible = options.sessionDeltaVisible === true;
|
|
395
404
|
// correction defaults to visible (enabled by default — plan is read-only).
|
|
396
405
|
this.correctionVisible = options.correctionVisible !== false;
|
|
406
|
+
this.chatVisible = options.chatVisible === true;
|
|
397
407
|
this.authenticatedPrincipal =
|
|
398
408
|
options.principal?.trim() ||
|
|
399
409
|
readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() ||
|
|
@@ -1009,6 +1019,11 @@ export class EngramMcpServer {
|
|
|
1009
1019
|
properties: {
|
|
1010
1020
|
memoryId: { type: "string" },
|
|
1011
1021
|
namespace: { type: "string" },
|
|
1022
|
+
// Issue #1582 — when memoryId is a `[m:xxxx]` handle, sessionKey
|
|
1023
|
+
// scopes resolution to this session's recent recall history.
|
|
1024
|
+
// Advertising it here lets the transport inject its session key
|
|
1025
|
+
// (toolAcceptsArgument) so MCP callers can cite handles (codex review).
|
|
1026
|
+
sessionKey: { type: "string" },
|
|
1012
1027
|
},
|
|
1013
1028
|
required: ["memoryId"],
|
|
1014
1029
|
additionalProperties: false,
|
|
@@ -2225,6 +2240,26 @@ export class EngramMcpServer {
|
|
|
2225
2240
|
);
|
|
2226
2241
|
this.tools = [...this.tools, ...planTool, ...applyTool];
|
|
2227
2242
|
}
|
|
2243
|
+
if (this.chatVisible) {
|
|
2244
|
+
const chatTools = withToolAliases(
|
|
2245
|
+
{
|
|
2246
|
+
name: "engram.memory_chat",
|
|
2247
|
+
description:
|
|
2248
|
+
"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?}.",
|
|
2249
|
+
inputSchema: {
|
|
2250
|
+
type: "object",
|
|
2251
|
+
properties: {
|
|
2252
|
+
message: { type: "string", description: "The user's message — a question about memories, a correction request, or a confirmation (yes/apply) to proceed with a pending plan." },
|
|
2253
|
+
chatSessionId: { type: "string", description: "Optional existing chat session id to resume. Omit to start a new session." },
|
|
2254
|
+
},
|
|
2255
|
+
required: ["message"],
|
|
2256
|
+
additionalProperties: false,
|
|
2257
|
+
},
|
|
2258
|
+
},
|
|
2259
|
+
this.emitLegacyTools,
|
|
2260
|
+
);
|
|
2261
|
+
this.tools = [...this.tools, ...chatTools];
|
|
2262
|
+
}
|
|
2228
2263
|
}
|
|
2229
2264
|
|
|
2230
2265
|
/** Get clientInfo for a specific MCP session. Returns undefined for non-MCP requests. */
|
|
@@ -2385,7 +2420,13 @@ export class EngramMcpServer {
|
|
|
2385
2420
|
argumentsObject = { ...argumentsObject, sessionKey: options.sessionKeyOverride };
|
|
2386
2421
|
}
|
|
2387
2422
|
const effectivePrincipal = options?.principalOverride ?? this.authenticatedPrincipal;
|
|
2388
|
-
|
|
2423
|
+
// Forward the MCP session scope (namespace/sessionKey overrides) so
|
|
2424
|
+
// tools like memory_chat bind the caller active scope (Thread 17).
|
|
2425
|
+
const mcpScope = {
|
|
2426
|
+
...(options?.namespaceOverride ? { namespace: options.namespaceOverride } : {}),
|
|
2427
|
+
...(options?.sessionKeyOverride ? { sessionKey: options.sessionKeyOverride } : {}),
|
|
2428
|
+
};
|
|
2429
|
+
const result = await this.callTool(name, argumentsObject, effectivePrincipal, options?.sessionId, mcpScope, options?.enforceWriteQuota);
|
|
2389
2430
|
return {
|
|
2390
2431
|
jsonrpc: "2.0",
|
|
2391
2432
|
id,
|
|
@@ -2573,7 +2614,7 @@ export class EngramMcpServer {
|
|
|
2573
2614
|
}));
|
|
2574
2615
|
}
|
|
2575
2616
|
|
|
2576
|
-
private async callTool(name: string, args: Record<string, unknown>, effectivePrincipal?: string, mcpSessionId?: string, enforceWriteQuota?: () => void | Promise<void>): Promise<unknown> {
|
|
2617
|
+
private async callTool(name: string, args: Record<string, unknown>, effectivePrincipal?: string, mcpSessionId?: string, scope?: { namespace?: string; sessionKey?: string }, enforceWriteQuota?: () => void | Promise<void>): Promise<unknown> {
|
|
2577
2618
|
// Migrated operations dispatch through the access boundary (issue #1525):
|
|
2578
2619
|
// one registry entry owns schema validation, normalization (rules
|
|
2579
2620
|
// 17/28/36/48/51), and error mapping for every surface. The switch
|
|
@@ -3742,6 +3783,30 @@ export class EngramMcpServer {
|
|
|
3742
3783
|
authenticatedPrincipal: effectivePrincipal,
|
|
3743
3784
|
});
|
|
3744
3785
|
}
|
|
3786
|
+
case "engram.memory_chat": {
|
|
3787
|
+
const message = typeof args.message === "string" ? args.message : "";
|
|
3788
|
+
if (!message) {
|
|
3789
|
+
throw new EngramAccessInputError("message is required");
|
|
3790
|
+
}
|
|
3791
|
+
const chatSessionId = typeof args.chatSessionId === "string" ? args.chatSessionId : undefined;
|
|
3792
|
+
const chatResult = await processChatMessage({
|
|
3793
|
+
service: this.service,
|
|
3794
|
+
config: this.service.configRef?.chat,
|
|
3795
|
+
memoryDir: this.service.memoryDir,
|
|
3796
|
+
message,
|
|
3797
|
+
...(chatSessionId ? { chatSessionId } : {}),
|
|
3798
|
+
...(effectivePrincipal ? { principal: effectivePrincipal } : {}),
|
|
3799
|
+
...(scope?.namespace ? { namespace: scope.namespace } : {}),
|
|
3800
|
+
...(scope?.sessionKey ? { sessionKey: scope.sessionKey } : {}),
|
|
3801
|
+
});
|
|
3802
|
+
// Strip internal error details — MCP clients get the tagged reply only,
|
|
3803
|
+
// matching the HTTP handler (cursor security review, Thread 17).
|
|
3804
|
+
if (chatResult && typeof chatResult === "object" && "error" in chatResult) {
|
|
3805
|
+
const { error: _stripped, ...wireResult } = chatResult as unknown as Record<string, unknown>;
|
|
3806
|
+
return wireResult;
|
|
3807
|
+
}
|
|
3808
|
+
return chatResult;
|
|
3809
|
+
}
|
|
3745
3810
|
default:
|
|
3746
3811
|
throw new Error(`unknown tool: ${name}`);
|
|
3747
3812
|
}
|
|
@@ -1466,3 +1466,17 @@ defineOperation({
|
|
|
1466
1466
|
schema: looseSchema,
|
|
1467
1467
|
handler: async (_input, _ctx) => ({ result: { stream: "sse" } }),
|
|
1468
1468
|
});
|
|
1469
|
+
|
|
1470
|
+
defineOperation({
|
|
1471
|
+
name: "chat_message",
|
|
1472
|
+
description: "Send a message to Remnic Chat (issue #1583). Endpoint registration only — the handler enforces the chat_disabled gate, message validation, and the confirmation protocol.",
|
|
1473
|
+
schema: looseSchema,
|
|
1474
|
+
handler: async (_input, _ctx) => ({ result: { ok: true } }),
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
defineOperation({
|
|
1478
|
+
name: "chat_events",
|
|
1479
|
+
description: "Stream chat session transcript events (SSE, issue #1583). Endpoint registration only.",
|
|
1480
|
+
schema: looseSchema,
|
|
1481
|
+
handler: async (_input, _ctx) => ({ result: { stream: "sse" } }),
|
|
1482
|
+
});
|
package/src/access-operations.ts
CHANGED
|
@@ -58,11 +58,15 @@ import {
|
|
|
58
58
|
*/
|
|
59
59
|
const memoryGetSchema = z.object({
|
|
60
60
|
memoryId: z.string().trim().min(1, "memoryId is required").max(512),
|
|
61
|
+
// Issue #1582 — when memoryId is a `[m:xxxx]` handle, sessionKey scopes the
|
|
62
|
+
// resolution to this session's recent recall history.
|
|
63
|
+
sessionKey: z.string().trim().min(1).max(512).nullable().optional(),
|
|
61
64
|
namespace: z.string().trim().max(256).nullable().optional(),
|
|
62
65
|
});
|
|
63
66
|
|
|
64
67
|
export interface MemoryGetInput {
|
|
65
68
|
readonly memoryId: string;
|
|
69
|
+
readonly sessionKey?: string | null;
|
|
66
70
|
readonly namespace?: string | null;
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -79,6 +83,7 @@ export const memoryGetOperation = defineOperation<MemoryGetInput, MemoryGetOutpu
|
|
|
79
83
|
input.memoryId,
|
|
80
84
|
input.namespace ?? undefined,
|
|
81
85
|
ctx.authenticatedPrincipal,
|
|
86
|
+
input.sessionKey ?? undefined,
|
|
82
87
|
);
|
|
83
88
|
return { result };
|
|
84
89
|
},
|
package/src/access-service.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
type CorrectionPlan,
|
|
58
58
|
type CorrectionRequest,
|
|
59
59
|
} from "./correction/index.js";
|
|
60
|
+
import { isHandleToken } from "./recall-handles.js";
|
|
60
61
|
import { createVersion } from "./page-versioning.js";
|
|
61
62
|
import { WorkStorage } from "./work/storage.js";
|
|
62
63
|
import {
|
|
@@ -4491,10 +4492,19 @@ export class EngramAccessService {
|
|
|
4491
4492
|
}
|
|
4492
4493
|
}
|
|
4493
4494
|
|
|
4494
|
-
async memoryGet(
|
|
4495
|
+
async memoryGet(
|
|
4496
|
+
memoryId: string,
|
|
4497
|
+
namespace?: string,
|
|
4498
|
+
principal?: string,
|
|
4499
|
+
sessionKey?: string,
|
|
4500
|
+
): Promise<EngramAccessMemoryResponse> {
|
|
4501
|
+
// Issue #1582 — accept a `[m:xxxx]` handle in place of a memory id; resolve
|
|
4502
|
+
// it against the caller's session via the shared helper (rule 22). A raw id
|
|
4503
|
+
// passes through unchanged, so callers unaware of handles are unaffected.
|
|
4504
|
+
const resolvedId = this.resolveMemoryIdOrHandleInput(memoryId, sessionKey);
|
|
4495
4505
|
const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
|
|
4496
4506
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
4497
|
-
const memory = await storage.getMemoryById(
|
|
4507
|
+
const memory = await storage.getMemoryById(resolvedId);
|
|
4498
4508
|
if (!memory) {
|
|
4499
4509
|
return { found: false, namespace: resolvedNamespace };
|
|
4500
4510
|
}
|
|
@@ -4785,7 +4795,38 @@ export class EngramAccessService {
|
|
|
4785
4795
|
return service;
|
|
4786
4796
|
}
|
|
4787
4797
|
|
|
4798
|
+
/**
|
|
4799
|
+
* Issue #1582 — resolve a single memoryId-or-handle reference to a concrete
|
|
4800
|
+
* memory id. Handles resolve against the session's recall history via the
|
|
4801
|
+
* orchestrator's shared helper (rule 22); raw ids pass through unchanged. A
|
|
4802
|
+
* handle that misses or is ambiguous becomes an EngramAccessInputError so the
|
|
4803
|
+
* boundary maps it to a 400 (rule 34/51 — list candidates, never guess).
|
|
4804
|
+
*/
|
|
4805
|
+
private resolveMemoryIdOrHandleInput(
|
|
4806
|
+
ref: string,
|
|
4807
|
+
sessionKey: string | undefined,
|
|
4808
|
+
): string {
|
|
4809
|
+
if (!isHandleToken(ref)) return ref;
|
|
4810
|
+
try {
|
|
4811
|
+
return this.orchestrator.resolveMemoryIdOrHandle(ref, sessionKey);
|
|
4812
|
+
} catch (err) {
|
|
4813
|
+
throw new EngramAccessInputError(
|
|
4814
|
+
err instanceof Error ? err.message : String(err),
|
|
4815
|
+
);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4788
4819
|
async correctionPlan(request: CorrectionRequest): Promise<CorrectionPlan> {
|
|
4820
|
+
// Issue #1582 — resolve any `[m:xxxx]` handle in targetIds to its memory
|
|
4821
|
+
// id against the caller's session BEFORE planning, so the planner only ever
|
|
4822
|
+
// sees concrete ids. One shared resolve path (rule 22); a handle that
|
|
4823
|
+
// misses or collides is an explicit input error, never silent (rule 34/51).
|
|
4824
|
+
if (request.targetIds?.some((id) => isHandleToken(id))) {
|
|
4825
|
+
const targetIds = request.targetIds.map((id) =>
|
|
4826
|
+
this.resolveMemoryIdOrHandleInput(id, request.sessionKey),
|
|
4827
|
+
);
|
|
4828
|
+
return this.correctionService().plan({ ...request, targetIds });
|
|
4829
|
+
}
|
|
4789
4830
|
return this.correctionService().plan(request);
|
|
4790
4831
|
}
|
|
4791
4832
|
|
|
@@ -49,7 +49,9 @@ import {
|
|
|
49
49
|
// alongside the other GET list routes (baseline 80 from #1525 + 1 = 81). Also a
|
|
50
50
|
// catalog-completeness correction: POST /engram/v1/memories (operation memory_store)
|
|
51
51
|
// was always live but omitted from HTTP_ROUTES — it is migrated, so no count change.
|
|
52
|
-
|
|
52
|
+
// #1583 remnic chat adds memory_chat — an unmigrated handler dispatched to
|
|
53
|
+
// processChatMessage (carries user input; not a boundary operation) → 81 + 1 = 82.
|
|
54
|
+
const UNMIGRATED_HANDLER_BASELINE = 82;
|
|
53
55
|
|
|
54
56
|
// Keep the import live — `getOperation` is the call surfaces use at dispatch
|
|
55
57
|
// time; referencing it here pins the registry's lookup contract.
|
|
@@ -72,7 +74,7 @@ function shortToolName(advertised: string): string {
|
|
|
72
74
|
/** Spin up a server with emitLegacyTools=true and read the deduped short names. */
|
|
73
75
|
async function liveMcpToolShortNames(): Promise<ReadonlySet<string>> {
|
|
74
76
|
const stub = { briefingEnabled: true } as unknown as EngramAccessService;
|
|
75
|
-
const server = new EngramMcpServer(stub, { emitLegacyTools: true, codingDecisionVisible: true, architectureCardVisible: true, codegraphVisible: true, sessionDeltaVisible: true });
|
|
77
|
+
const server = new EngramMcpServer(stub, { emitLegacyTools: true, codingDecisionVisible: true, architectureCardVisible: true, codegraphVisible: true, sessionDeltaVisible: true, chatVisible: true });
|
|
76
78
|
const response = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list" });
|
|
77
79
|
const result = (response as { result?: { tools?: Array<{ name: string }> } }).result;
|
|
78
80
|
const names = new Set<string>();
|
|
@@ -75,6 +75,9 @@ export const MCP_TOOLS: readonly McpToolEntry[] = [
|
|
|
75
75
|
// Correction Contract (issue #1580) — one plan/apply pipeline.
|
|
76
76
|
{ tool: "memory_correct_plan", operation: "memory_correct_plan" },
|
|
77
77
|
{ tool: "memory_correct_apply", operation: "memory_correct_apply" },
|
|
78
|
+
// Conversational memory inspection + correction (issue #1583) — unmigrated
|
|
79
|
+
// handler dispatched to processChatMessage (not a boundary operation).
|
|
80
|
+
{ tool: "memory_chat", operation: null },
|
|
78
81
|
{ tool: "coding_decision", operation: "coding_decision" },
|
|
79
82
|
{ tool: "coding_architecture", operation: "coding_architecture" },
|
|
80
83
|
// codegraph parity tools (issue #1554)
|
|
@@ -229,6 +232,8 @@ export const HTTP_ROUTES: readonly HttpRouteEntry[] = [
|
|
|
229
232
|
// streaming lifecycle is owned by the HTTP transport (handleGraphEventsSSE).
|
|
230
233
|
{ method: "GET", pathname: "/engram/v1/offline-sync/snapshot-stream", operation: "offline_sync_snapshot_stream" },
|
|
231
234
|
{ method: "GET", pathname: "/engram/v1/graph/events", operation: "graph_events" },
|
|
235
|
+
{ method: "POST", pathname: "/engram/v1/chat/message", operation: "chat_message" },
|
|
236
|
+
{ method: "GET", pathname: "/engram/v1/chat/events/:id", operation: "chat_events" },
|
|
232
237
|
];
|
|
233
238
|
|
|
234
239
|
/**
|
|
@@ -311,3 +311,75 @@ test("getMemoryForActiveMemory ignores explicit namespace overrides when namespa
|
|
|
311
311
|
assert.equal(result.id, "shared-memory");
|
|
312
312
|
assert.equal(result.text, "shared text");
|
|
313
313
|
});
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
test("getMemoryForActiveMemory resolves a [m:xxxx] handle via the orchestrator resolver (#1582)", async () => {
|
|
317
|
+
const resolved: string[] = [];
|
|
318
|
+
const orchestrator = {
|
|
319
|
+
config: { namespacesEnabled: false },
|
|
320
|
+
resolveSelfNamespace: () => "readable-session",
|
|
321
|
+
getStorageForNamespace: async () => ({
|
|
322
|
+
getMemoryById: async (id: string) =>
|
|
323
|
+
id === "fact-1" ? ({ content: "text", frontmatter: {} } as never) : null,
|
|
324
|
+
}),
|
|
325
|
+
resolveMemoryIdOrHandle: (ref: string) => {
|
|
326
|
+
resolved.push(ref);
|
|
327
|
+
return "fact-1";
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = await getMemoryForActiveMemory(
|
|
332
|
+
orchestrator as never,
|
|
333
|
+
"[m:4f2a]",
|
|
334
|
+
{ sessionKey: "session-x" },
|
|
335
|
+
);
|
|
336
|
+
assert.deepEqual(resolved, ["[m:4f2a]"]);
|
|
337
|
+
assert.equal(result.error, undefined);
|
|
338
|
+
assert.equal(result.id, "fact-1");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("getMemoryForActiveMemory passes a raw id through unchanged (no resolver call) (#1582)", async () => {
|
|
342
|
+
const orchestrator = {
|
|
343
|
+
config: { namespacesEnabled: false },
|
|
344
|
+
resolveSelfNamespace: () => "readable-session",
|
|
345
|
+
getStorageForNamespace: async () => ({
|
|
346
|
+
getMemoryById: async (id: string) =>
|
|
347
|
+
id === "fact-1" ? ({ content: "text", frontmatter: {} } as never) : null,
|
|
348
|
+
}),
|
|
349
|
+
resolveMemoryIdOrHandle: () => {
|
|
350
|
+
throw new Error("resolver must not be called for a raw id");
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const result = await getMemoryForActiveMemory(
|
|
355
|
+
orchestrator as never,
|
|
356
|
+
"fact-1",
|
|
357
|
+
{ sessionKey: "session-x" },
|
|
358
|
+
);
|
|
359
|
+
assert.equal(result.id, "fact-1");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("getMemoryForActiveMemory returns not_found when a handle cannot be resolved (#1582, cursor review)", async () => {
|
|
363
|
+
// A missing session key, unknown handle, or ambiguous handle makes the
|
|
364
|
+
// orchestrator resolver THROW. The active-memory get path must yield the SAME
|
|
365
|
+
// not_found contract a bad raw id gets — not propagate the throw and crash an
|
|
366
|
+
// OpenClaw active-memory caller.
|
|
367
|
+
const orchestrator = {
|
|
368
|
+
config: { namespacesEnabled: false },
|
|
369
|
+
resolveSelfNamespace: () => "readable-session",
|
|
370
|
+
getStorageForNamespace: async () => ({
|
|
371
|
+
getMemoryById: async () => null,
|
|
372
|
+
}),
|
|
373
|
+
resolveMemoryIdOrHandle: () => {
|
|
374
|
+
throw new Error("Memory handle [m:dead] cannot be resolved without a session key.");
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const result = await getMemoryForActiveMemory(
|
|
379
|
+
orchestrator as never,
|
|
380
|
+
"[m:dead]",
|
|
381
|
+
// No sessionKey → resolver throws "without a session key".
|
|
382
|
+
{},
|
|
383
|
+
);
|
|
384
|
+
assert.equal(result.error, "not_found");
|
|
385
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { canReadNamespace, defaultNamespaceForPrincipal, resolvePrincipal } from "./namespaces/principal.js";
|
|
2
2
|
import type { MemoryFile, PluginConfig } from "./types.js";
|
|
3
3
|
import { collapseWhitespace, truncateCodePointSafe } from "./whitespace.js";
|
|
4
|
+
import { isHandleToken } from "./recall-handles.js";
|
|
4
5
|
|
|
5
6
|
export interface ActiveMemoryMetadata {
|
|
6
7
|
type?: "fact" | "preference";
|
|
@@ -192,6 +193,8 @@ export async function getMemoryForActiveMemory(
|
|
|
192
193
|
storage?: {
|
|
193
194
|
getMemoryById?: (id: string) => Promise<MemoryFile | null>;
|
|
194
195
|
};
|
|
196
|
+
/** Issue #1582 — resolves a `[m:xxxx]` handle to a memory id (orchestrator). */
|
|
197
|
+
resolveMemoryIdOrHandle?: (ref: string, sessionKey?: string) => string;
|
|
195
198
|
},
|
|
196
199
|
id: string,
|
|
197
200
|
options: {
|
|
@@ -210,10 +213,26 @@ export async function getMemoryForActiveMemory(
|
|
|
210
213
|
? await orchestrator.getStorageForNamespace(namespace)
|
|
211
214
|
: orchestrator.storage;
|
|
212
215
|
|
|
213
|
-
|
|
216
|
+
// Issue #1582 — resolve a `[m:xxxx]` handle to its memory id against the
|
|
217
|
+
// caller's session before the storage read, so OpenClaw active-memory agents
|
|
218
|
+
// can cite injected handles through the SAME shared path as memory_get /
|
|
219
|
+
// correction (cursor review). Raw ids pass through unchanged.
|
|
220
|
+
//
|
|
221
|
+
// A handle that misses, collides, or has no session key becomes not_found —
|
|
222
|
+
// the SAME behavior a bad raw id yields below — instead of throwing, so an
|
|
223
|
+
// active-memory caller gets a uniform not-found contract (cursor review).
|
|
224
|
+
let resolvedId = id;
|
|
225
|
+
if (isHandleToken(id) && typeof orchestrator.resolveMemoryIdOrHandle === "function") {
|
|
226
|
+
try {
|
|
227
|
+
resolvedId = orchestrator.resolveMemoryIdOrHandle(id, options.sessionKey);
|
|
228
|
+
} catch {
|
|
229
|
+
return { error: "not_found" };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const memory = await storage?.getMemoryById?.(resolvedId);
|
|
214
233
|
if (!memory) return { error: "not_found" };
|
|
215
234
|
return {
|
|
216
|
-
id,
|
|
235
|
+
id: resolvedId,
|
|
217
236
|
text: collapseWhitespace(memory.content),
|
|
218
237
|
metadata: buildActiveMemoryMetadataFromMemory(memory),
|
|
219
238
|
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat CLI surface tests (issue #1583 PR 2).
|
|
3
|
+
*
|
|
4
|
+
* Tests the non-TTY `--once` scripting mode and the no-LLM error path.
|
|
5
|
+
* Interactive readline mode is exercised via the golden-transcript pattern
|
|
6
|
+
* in chat-engine.test.ts (the engine loop is identical); here we cover the
|
|
7
|
+
* CLI's own input/output contract.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { strict as assert } from "node:assert";
|
|
11
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { test } from "node:test";
|
|
15
|
+
|
|
16
|
+
import type { EngramAccessService } from "../access-service.js";
|
|
17
|
+
import { parseConfig } from "../config.js";
|
|
18
|
+
import { runChatCli } from "./chat-cli.js";
|
|
19
|
+
import { DEFAULT_CHAT_CONFIG } from "./chat-config.js";
|
|
20
|
+
|
|
21
|
+
/** Capture stdout.write for the duration of `fn`. */
|
|
22
|
+
async function captureStdout(fn: () => Promise<void>): Promise<string> {
|
|
23
|
+
const chunks: string[] = [];
|
|
24
|
+
const original = process.stdout.write.bind(process.stdout);
|
|
25
|
+
process.stdout.write = ((chunk: unknown) => {
|
|
26
|
+
chunks.push(typeof chunk === "string" ? chunk : String(chunk));
|
|
27
|
+
return true;
|
|
28
|
+
}) as typeof process.stdout.write;
|
|
29
|
+
try {
|
|
30
|
+
await fn();
|
|
31
|
+
} finally {
|
|
32
|
+
process.stdout.write = original;
|
|
33
|
+
}
|
|
34
|
+
return chunks.join("");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function makeService(overrides: Partial<EngramAccessService> = {}): EngramAccessService {
|
|
38
|
+
return {
|
|
39
|
+
fallbackLlmRef: {
|
|
40
|
+
chatCompletion: async () => ({ content: "Based on memory_search, your database uses PostgreSQL [id: abc]." }),
|
|
41
|
+
},
|
|
42
|
+
configRef: parseConfig({ memoryDir: "/tmp/chat-cli-test", chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
43
|
+
memoryDir: "/tmp/chat-cli-test",
|
|
44
|
+
memoryGet: () => Promise.resolve(null),
|
|
45
|
+
memoryTimeline: () => Promise.resolve([]),
|
|
46
|
+
memorySearch: () => Promise.resolve({ results: [{ path: "mem/abc.md", score: 0.9, snippet: "PostgreSQL" }], count: 1 }),
|
|
47
|
+
recallExplain: () => Promise.resolve(null),
|
|
48
|
+
entityGet: () => Promise.resolve(null),
|
|
49
|
+
memoryProfile: () => Promise.resolve({}),
|
|
50
|
+
memoryEntitiesList: () => Promise.resolve({ items: [] }),
|
|
51
|
+
memoryQuestions: () => Promise.resolve({ items: [] }),
|
|
52
|
+
reviewQueue: () => Promise.resolve({ items: [] }),
|
|
53
|
+
...overrides,
|
|
54
|
+
} as unknown as EngramAccessService;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function withTempDir<T>(fn: (dir: string) => Promise<T>): Promise<T> {
|
|
58
|
+
const dir = await mkdtemp(join(tmpdir(), "chat-cli-"));
|
|
59
|
+
try { return await fn(dir); } finally { await rm(dir, { recursive: true, force: true }); }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("CLI --once mode: prints the assistant reply and session id", async () => {
|
|
63
|
+
await withTempDir(async (memoryDir) => {
|
|
64
|
+
const service = makeService({
|
|
65
|
+
memoryDir,
|
|
66
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
67
|
+
});
|
|
68
|
+
const out = await captureStdout(() =>
|
|
69
|
+
runChatCli({
|
|
70
|
+
service,
|
|
71
|
+
config: service.configRef!.chat,
|
|
72
|
+
memoryDir,
|
|
73
|
+
once: true,
|
|
74
|
+
input: "what database do I use?",
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
assert.ok(out.includes("PostgreSQL"), `expected reply in stdout, got: ${out}`);
|
|
78
|
+
assert.ok(out.includes("[session:"), "session id should be printed for --once mode");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("CLI --once mode: no input → tagged error", async () => {
|
|
83
|
+
await withTempDir(async (memoryDir) => {
|
|
84
|
+
const service = makeService({
|
|
85
|
+
memoryDir,
|
|
86
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
87
|
+
});
|
|
88
|
+
const out = await captureStdout(() =>
|
|
89
|
+
runChatCli({
|
|
90
|
+
service,
|
|
91
|
+
config: service.configRef!.chat,
|
|
92
|
+
memoryDir,
|
|
93
|
+
once: true,
|
|
94
|
+
input: " ",
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
assert.ok(out.includes("[error]"), `expected error tag, got: ${out}`);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("CLI: no LLM available → tagged error reply", async () => {
|
|
102
|
+
await withTempDir(async (memoryDir) => {
|
|
103
|
+
const service = makeService({
|
|
104
|
+
fallbackLlmRef: null,
|
|
105
|
+
localLlmRef: null,
|
|
106
|
+
memoryDir,
|
|
107
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
108
|
+
});
|
|
109
|
+
const out = await captureStdout(() =>
|
|
110
|
+
runChatCli({
|
|
111
|
+
service,
|
|
112
|
+
config: service.configRef!.chat,
|
|
113
|
+
memoryDir,
|
|
114
|
+
once: true,
|
|
115
|
+
input: "hi",
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
assert.ok(out.includes("[error]"), `expected error tag, got: ${out}`);
|
|
119
|
+
assert.ok(out.toLowerCase().includes("llm") || out.toLowerCase().includes("model"), "should mention model availability");
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("CLI: resumes an existing session by id", async () => {
|
|
124
|
+
await withTempDir(async (memoryDir) => {
|
|
125
|
+
const service = makeService({
|
|
126
|
+
memoryDir,
|
|
127
|
+
configRef: parseConfig({ memoryDir, chat: { ...DEFAULT_CHAT_CONFIG, enabled: true } }),
|
|
128
|
+
});
|
|
129
|
+
// First message creates a session.
|
|
130
|
+
const first = await captureStdout(() =>
|
|
131
|
+
runChatCli({ service, config: service.configRef!.chat, memoryDir, once: true, input: "hello" }),
|
|
132
|
+
);
|
|
133
|
+
const sessionMatch = /\[session: ([^\]]+)\]/.exec(first);
|
|
134
|
+
assert.ok(sessionMatch, "first run should print a session id");
|
|
135
|
+
const sessionId = sessionMatch[1]!;
|
|
136
|
+
// Second message resumes it.
|
|
137
|
+
const second = await captureStdout(() =>
|
|
138
|
+
runChatCli({ service, config: service.configRef!.chat, memoryDir, once: true, input: "again", sessionId }),
|
|
139
|
+
);
|
|
140
|
+
assert.ok(second.includes("[session:"), "resumed run should also print a session id");
|
|
141
|
+
});
|
|
142
|
+
});
|