@remnic/core 9.3.751 → 9.3.753
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-admin-ops-surface.d.ts +194 -0
- package/dist/access-admin-ops-surface.js +128 -0
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +30 -30
- package/dist/access-cli.js +92 -92
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +38 -38
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +35 -35
- package/dist/access-operations-batch.js +32 -32
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +34 -34
- package/dist/access-recall-surface.d.ts +363 -0
- package/dist/access-recall-surface.js +128 -0
- package/dist/access-schema.d.ts +116 -116
- package/dist/access-schema.js +8 -8
- package/dist/{access-service-DmAEOJdJ.d.ts → access-service-MitduesD.d.ts} +75 -118
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +33 -29
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +6 -6
- 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 +3 -3
- package/dist/briefing.js +10 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-CZJSLEFG.js} +3 -3
- package/dist/capsule-crypto-CZJSLEFG.js.map +1 -0
- package/dist/{catalog-BpOcwHdE.d.ts → catalog-BEmNVRN0.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +15 -15
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-6SXNQFHB.js → chunk-243P7YYV.js} +7 -7
- package/dist/{chunk-2NLLXCJG.js → chunk-2BFLDH5F.js} +2 -2
- package/dist/{chunk-ZBVQ4ZM4.js → chunk-34THARMH.js} +17 -17
- package/dist/{chunk-SNEXVX3U.js → chunk-3HGZX26P.js} +21 -21
- package/dist/{chunk-5PZCDUJ6.js → chunk-3ZIKDH6C.js} +4 -4
- package/dist/{chunk-X7Y7WX73.js → chunk-437S3G37.js} +2 -2
- package/dist/{chunk-RS25QOKZ.js → chunk-64SE4X5G.js} +2 -2
- package/dist/{chunk-JBPKEARU.js → chunk-AU7Q3LSC.js} +4 -4
- package/dist/{chunk-6FNZ3NMS.js → chunk-AXBRVBT6.js} +2 -2
- package/dist/{chunk-W3BFT5OJ.js → chunk-BWOEYBRB.js} +2 -2
- package/dist/{chunk-UU32EAIY.js → chunk-C3IHRWIC.js} +7 -7
- package/dist/{chunk-DBRVU5PR.js → chunk-DKIZ4BUQ.js} +14 -14
- package/dist/{chunk-EEKDRAWX.js → chunk-DS4QM6I7.js} +4 -4
- package/dist/{chunk-WOX2UW4K.js → chunk-E3MRYDUQ.js} +2 -2
- package/dist/{chunk-K2R3DEEH.js → chunk-E6LENGYV.js} +2 -2
- package/dist/{chunk-CGZWGVQF.js → chunk-EEO4YWQ7.js} +9 -9
- package/dist/{chunk-ORAK3LNZ.js → chunk-ER3CCWK2.js} +2 -2
- package/dist/{chunk-TPLE6SNY.js → chunk-GWGWBK5Y.js} +7 -7
- package/dist/{chunk-BJMBJZ2Y.js → chunk-GWTFLF2P.js} +59 -59
- package/dist/chunk-GWTFLF2P.js.map +1 -0
- package/dist/{chunk-IKHKHADN.js → chunk-ITLJATQI.js} +5 -5
- package/dist/{chunk-E4PFDD3N.js → chunk-JES2O5Q6.js} +4 -4
- package/dist/{chunk-QOGXUNWL.js → chunk-JGPURIEH.js} +7 -7
- package/dist/{chunk-YGGXUNS4.js → chunk-KY4WOZ3P.js} +55 -55
- package/dist/{chunk-WA7FECSG.js → chunk-KYHGLDFU.js} +7 -7
- package/dist/{chunk-V5RVMULT.js → chunk-LZ73Y56P.js} +10 -10
- package/dist/{chunk-ARV3AUOM.js → chunk-M5QKGHCR.js} +2 -2
- package/dist/{chunk-SD34EK4Z.js → chunk-N3VE7OCY.js} +4 -4
- package/dist/{chunk-KQAFEZQX.js → chunk-OUXPVWTZ.js} +6 -6
- package/dist/{chunk-NNL4LJAD.js → chunk-OZMBT7N3.js} +5 -5
- package/dist/{chunk-RWZB6MAY.js → chunk-PN5AMCK2.js} +1065 -1176
- package/dist/chunk-PN5AMCK2.js.map +1 -0
- package/dist/{chunk-WWSXVOGY.js → chunk-QIRZI3C3.js} +2 -2
- package/dist/{chunk-B35L45HZ.js → chunk-TVISHYL5.js} +2 -2
- package/dist/{chunk-SQNED75R.js → chunk-V5UV7BOA.js} +1 -1
- package/dist/{chunk-7TWA7DKP.js → chunk-VVSV2QVY.js} +2 -2
- package/dist/{chunk-4NWIGAIC.js → chunk-XOPLMSVV.js} +2 -2
- package/dist/{chunk-K2JM4DZZ.js → chunk-YNBEHUZH.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-UETBEHBA.js → chunk-ZECLLIMX.js} +3442 -3085
- package/dist/chunk-ZECLLIMX.js.map +1 -0
- package/dist/{chunk-QRRBXD24.js → chunk-ZIFPFKB2.js} +7 -7
- package/dist/{chunk-HYI2GP3F.js → chunk-ZMHDSIEX.js} +7 -7
- package/dist/{cli-lmf5RGyQ.d.ts → cli-DqonYBZ0.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +64 -64
- package/dist/compounding/engine.d.ts +3 -3
- package/dist/compounding/engine.js +11 -11
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +11 -11
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -12
- 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/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-3FJYWS3G.js → contradiction-scan-2SODRNUF.js} +3 -3
- package/dist/contradiction-scan-2SODRNUF.js.map +1 -0
- 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/dashboard-runtime.js +2 -2
- 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 +11 -11
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- 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 +3 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-4I3VDSYS.js → first-start-migration-DY6YCRC2.js} +2 -2
- package/dist/{forget-WYLR4ZFV.js → forget-OEE5OELK.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +1284 -1284
- package/dist/index.js +148 -148
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +4 -4
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +8 -8
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- 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 +13 -13
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +12 -12
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +19 -19
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +15 -15
- package/dist/{orchestrator-D7bb8ZGp.d.ts → orchestrator-D7ZGOQLX.d.ts} +101 -180
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +92 -92
- 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/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-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -3
- 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/schemas.d.ts +100 -100
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +1 -1
- 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/secure-store/index.js +2 -2
- package/dist/{semantic-consolidation-CTfbVwCA.d.ts → semantic-consolidation-7Sndms2M.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -13
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -10
- 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 +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +9 -9
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +2 -2
- 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/{tier-stats-5XF4UBRQ.js → tier-stats-TG6GUZ6N.js} +4 -4
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +3 -3
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/transfer/types.d.ts +110 -110
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-D4NYDtXI.d.ts → types-DTEFxFuL.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +10 -10
- package/package.json +2 -2
- package/src/access-admin-ops-surface.ts +828 -0
- package/src/access-recall-surface.ts +1552 -0
- package/src/access-service.ts +70 -1917
- package/src/orchestration/recall-entry.ts +290 -0
- package/src/orchestration/session-context.ts +407 -0
- package/src/orchestrator.ts +125 -462
- package/dist/chunk-BJMBJZ2Y.js.map +0 -1
- package/dist/chunk-RWZB6MAY.js.map +0 -1
- package/dist/chunk-UETBEHBA.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-admin-ops-surface.js.map} +0 -0
- /package/dist/{contradiction-scan-3FJYWS3G.js.map → access-recall-surface.js.map} +0 -0
- /package/dist/{chunk-6SXNQFHB.js.map → chunk-243P7YYV.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-2BFLDH5F.js.map} +0 -0
- /package/dist/{chunk-ZBVQ4ZM4.js.map → chunk-34THARMH.js.map} +0 -0
- /package/dist/{chunk-SNEXVX3U.js.map → chunk-3HGZX26P.js.map} +0 -0
- /package/dist/{chunk-5PZCDUJ6.js.map → chunk-3ZIKDH6C.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-437S3G37.js.map} +0 -0
- /package/dist/{chunk-RS25QOKZ.js.map → chunk-64SE4X5G.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-AU7Q3LSC.js.map} +0 -0
- /package/dist/{chunk-6FNZ3NMS.js.map → chunk-AXBRVBT6.js.map} +0 -0
- /package/dist/{chunk-W3BFT5OJ.js.map → chunk-BWOEYBRB.js.map} +0 -0
- /package/dist/{chunk-UU32EAIY.js.map → chunk-C3IHRWIC.js.map} +0 -0
- /package/dist/{chunk-DBRVU5PR.js.map → chunk-DKIZ4BUQ.js.map} +0 -0
- /package/dist/{chunk-EEKDRAWX.js.map → chunk-DS4QM6I7.js.map} +0 -0
- /package/dist/{chunk-WOX2UW4K.js.map → chunk-E3MRYDUQ.js.map} +0 -0
- /package/dist/{chunk-K2R3DEEH.js.map → chunk-E6LENGYV.js.map} +0 -0
- /package/dist/{chunk-CGZWGVQF.js.map → chunk-EEO4YWQ7.js.map} +0 -0
- /package/dist/{chunk-ORAK3LNZ.js.map → chunk-ER3CCWK2.js.map} +0 -0
- /package/dist/{chunk-TPLE6SNY.js.map → chunk-GWGWBK5Y.js.map} +0 -0
- /package/dist/{chunk-IKHKHADN.js.map → chunk-ITLJATQI.js.map} +0 -0
- /package/dist/{chunk-E4PFDD3N.js.map → chunk-JES2O5Q6.js.map} +0 -0
- /package/dist/{chunk-QOGXUNWL.js.map → chunk-JGPURIEH.js.map} +0 -0
- /package/dist/{chunk-YGGXUNS4.js.map → chunk-KY4WOZ3P.js.map} +0 -0
- /package/dist/{chunk-WA7FECSG.js.map → chunk-KYHGLDFU.js.map} +0 -0
- /package/dist/{chunk-V5RVMULT.js.map → chunk-LZ73Y56P.js.map} +0 -0
- /package/dist/{chunk-ARV3AUOM.js.map → chunk-M5QKGHCR.js.map} +0 -0
- /package/dist/{chunk-SD34EK4Z.js.map → chunk-N3VE7OCY.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-OUXPVWTZ.js.map} +0 -0
- /package/dist/{chunk-NNL4LJAD.js.map → chunk-OZMBT7N3.js.map} +0 -0
- /package/dist/{chunk-WWSXVOGY.js.map → chunk-QIRZI3C3.js.map} +0 -0
- /package/dist/{chunk-B35L45HZ.js.map → chunk-TVISHYL5.js.map} +0 -0
- /package/dist/{chunk-SQNED75R.js.map → chunk-V5UV7BOA.js.map} +0 -0
- /package/dist/{chunk-7TWA7DKP.js.map → chunk-VVSV2QVY.js.map} +0 -0
- /package/dist/{chunk-4NWIGAIC.js.map → chunk-XOPLMSVV.js.map} +0 -0
- /package/dist/{chunk-K2JM4DZZ.js.map → chunk-YNBEHUZH.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-QRRBXD24.js.map → chunk-ZIFPFKB2.js.map} +0 -0
- /package/dist/{chunk-HYI2GP3F.js.map → chunk-ZMHDSIEX.js.map} +0 -0
- /package/dist/{first-start-migration-4I3VDSYS.js.map → first-start-migration-DY6YCRC2.js.map} +0 -0
- /package/dist/{forget-WYLR4ZFV.js.map → forget-OEE5OELK.js.map} +0 -0
- /package/dist/{tier-stats-5XF4UBRQ.js.map → tier-stats-TG6GUZ6N.js.map} +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall-entry coordinator — extracted from the orchestrator
|
|
3
|
+
* (issue #1526, seam 28).
|
|
4
|
+
*
|
|
5
|
+
* Owns the public recall() entry surface: the init gate + timeout
|
|
6
|
+
* fallback around recallInternal, recall failure logging/suppression,
|
|
7
|
+
* recall result publication, and eval shadow-recall queueing.
|
|
8
|
+
*
|
|
9
|
+
* Behavior-preserving move (late-binding selfDeps wiring, seams 18–27).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { type CapabilitySet, type GraphConstructionCapabilitySet, type MemoryLifecycleCapabilitySet, resolveCapabilities, resolveEvalCapabilities, resolveGraphConstructionCapabilities, resolveNamespaceCapabilities, resolveRecallAuxiliaryCapabilities } from "../capabilities.js";
|
|
13
|
+
import { type EvalShadowRecallRecord, recordEvalShadowRecall } from "../evals.js";
|
|
14
|
+
import { StorageManager } from "../index.js";
|
|
15
|
+
import { log } from "../logger.js";
|
|
16
|
+
import { resolvePrincipal } from "../namespaces/principal.js";
|
|
17
|
+
import { type RecallInvocationOptions, abortRecallError } from "./orchestrator-helpers.js";
|
|
18
|
+
import { ProfilingCollector } from "../profiling.js";
|
|
19
|
+
import type { TrustStageResultItem } from "../trust-score-stage.js";
|
|
20
|
+
import type { IdentityInjectionMode, PluginConfig, QmdSearchResult } from "../types.js";
|
|
21
|
+
|
|
22
|
+
export interface RecallEntryDeps {
|
|
23
|
+
appendRecallSection(
|
|
24
|
+
sectionBuckets: Map<string, string[]>,
|
|
25
|
+
sectionId: string,
|
|
26
|
+
content: string,
|
|
27
|
+
): boolean;
|
|
28
|
+
readonly config: PluginConfig;
|
|
29
|
+
enqueueDirectAnswerObservation(
|
|
30
|
+
prompt: string,
|
|
31
|
+
sessionKey: string,
|
|
32
|
+
namespaceOverride: string | undefined,
|
|
33
|
+
principalOverride: string | undefined,
|
|
34
|
+
caps: CapabilitySet,
|
|
35
|
+
namespacesEnabled: boolean,
|
|
36
|
+
): void;
|
|
37
|
+
evalShadowWriteChain: Promise<void>;
|
|
38
|
+
extractMemoryIdsFromResults(results: QmdSearchResult[]): string[];
|
|
39
|
+
formatQmdResults(
|
|
40
|
+
title: string,
|
|
41
|
+
results: QmdSearchResult[],
|
|
42
|
+
sessionKey?: string,
|
|
43
|
+
trustByPath?: Map<string, TrustStageResultItem> | null,
|
|
44
|
+
): string;
|
|
45
|
+
readonly initPromise: Promise<void> | null;
|
|
46
|
+
lastRecallFailureAtMs: number;
|
|
47
|
+
lastRecallFailureLogAtMs: number;
|
|
48
|
+
logRecallFailure(err: unknown): void;
|
|
49
|
+
readonly profiler: ProfilingCollector;
|
|
50
|
+
recallInternal(
|
|
51
|
+
prompt: string,
|
|
52
|
+
sessionKey?: string,
|
|
53
|
+
options?: RecallInvocationOptions,
|
|
54
|
+
caps?: CapabilitySet,
|
|
55
|
+
graphCaps?: GraphConstructionCapabilitySet,
|
|
56
|
+
lifecycleCaps?: MemoryLifecycleCapabilitySet,
|
|
57
|
+
): Promise<string>;
|
|
58
|
+
readonly storage: StorageManager;
|
|
59
|
+
suppressedRecallFailures: number;
|
|
60
|
+
trackMemoryAccess(memoryIds: string[]): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class RecallEntryCoordinator {
|
|
64
|
+
constructor(
|
|
65
|
+
private readonly deps: RecallEntryDeps,
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
async recall(
|
|
69
|
+
prompt: string,
|
|
70
|
+
sessionKey?: string,
|
|
71
|
+
options: RecallInvocationOptions = {},
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
// Resolve the recall-operation capability gates ONCE, at the operation
|
|
74
|
+
// entry, and thread the frozen set down (issue #1523). Never re-read the
|
|
75
|
+
// migrated flags off `this.deps.config` mid-operation.
|
|
76
|
+
const caps = resolveCapabilities(this.deps.config);
|
|
77
|
+
const graphCaps = resolveGraphConstructionCapabilities(this.deps.config); // #1566 Cluster A
|
|
78
|
+
const abortController = new AbortController();
|
|
79
|
+
const onAbort = () => {
|
|
80
|
+
abortController.abort();
|
|
81
|
+
};
|
|
82
|
+
if (options.abortSignal?.aborted) {
|
|
83
|
+
abortController.abort();
|
|
84
|
+
} else {
|
|
85
|
+
options.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const principal =
|
|
89
|
+
typeof options.principalOverride === "string" &&
|
|
90
|
+
options.principalOverride.length > 0
|
|
91
|
+
? options.principalOverride
|
|
92
|
+
: resolvePrincipal(sessionKey, this.deps.config);
|
|
93
|
+
const namespacesEnabled = resolveNamespaceCapabilities(this.deps.config).namespaces;
|
|
94
|
+
if (namespacesEnabled && !principal) {
|
|
95
|
+
throw new Error("authentication required: namespaces are enabled and no principal was supplied");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Wait for initialization to complete before attempting recall. The timeout
|
|
99
|
+
// is configurable so OpenClaw's per-hook budget and Remnic's internal init
|
|
100
|
+
// gate can stay aligned during cold starts.
|
|
101
|
+
let initGateTimeoutHandle: NodeJS.Timeout | null = null;
|
|
102
|
+
let onInitGateAbort: (() => void) | null = null;
|
|
103
|
+
if (this.deps.initPromise) {
|
|
104
|
+
const gateResult = await Promise.race([
|
|
105
|
+
this.deps.initPromise.then(() => "ok" as const),
|
|
106
|
+
new Promise<"timeout">((resolve) => {
|
|
107
|
+
initGateTimeoutHandle = setTimeout(
|
|
108
|
+
() => resolve("timeout"),
|
|
109
|
+
this.deps.config.initGateTimeoutMs,
|
|
110
|
+
);
|
|
111
|
+
}),
|
|
112
|
+
abortController.signal.aborted
|
|
113
|
+
? Promise.resolve("aborted" as const)
|
|
114
|
+
: new Promise<"aborted">((resolve) => {
|
|
115
|
+
onInitGateAbort = () => resolve("aborted");
|
|
116
|
+
abortController.signal.addEventListener(
|
|
117
|
+
"abort",
|
|
118
|
+
onInitGateAbort,
|
|
119
|
+
{ once: true },
|
|
120
|
+
);
|
|
121
|
+
}),
|
|
122
|
+
]);
|
|
123
|
+
if (initGateTimeoutHandle) clearTimeout(initGateTimeoutHandle);
|
|
124
|
+
if (onInitGateAbort)
|
|
125
|
+
abortController.signal.removeEventListener("abort", onInitGateAbort);
|
|
126
|
+
if (gateResult === "aborted") {
|
|
127
|
+
this.deps.logRecallFailure(abortRecallError("recall aborted before init"));
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
if (gateResult === "timeout") {
|
|
131
|
+
log.warn("recall: init gate timed out — proceeding without full init");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Secure-store lock gate (issue #690 PR 3/4).
|
|
136
|
+
// If secure-store is enabled but the keyring holds no key for this
|
|
137
|
+
// memory directory, reject recall with a clear human-readable error
|
|
138
|
+
// rather than surfacing a cryptic SecureStoreLockedError from deep
|
|
139
|
+
// inside the storage layer.
|
|
140
|
+
if (resolveRecallAuxiliaryCapabilities(this.deps.config).secureStore && !this.deps.storage.isSecureStoreUnlocked()) {
|
|
141
|
+
const lockedMsg =
|
|
142
|
+
"[secure-store locked] Memory store is encrypted and locked. " +
|
|
143
|
+
"Unlock the secure-store inside this daemon process, or restart the daemon through a secure-store aware launcher that installs the key.";
|
|
144
|
+
log.warn("recall blocked: secure-store is locked");
|
|
145
|
+
return lockedMsg;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Keep outer recall timeout above worst-case serialized hybrid search:
|
|
149
|
+
// QMD subprocess BM25 (30s) + vector (30s) can consume ~60s under contention.
|
|
150
|
+
try {
|
|
151
|
+
const recallPromise = this.deps.recallInternal(prompt, sessionKey, {
|
|
152
|
+
...options,
|
|
153
|
+
abortSignal: abortController.signal,
|
|
154
|
+
}, caps, graphCaps);
|
|
155
|
+
const RECALL_TIMEOUT_MS = this.deps.config.recallOuterTimeoutMs ?? 75_000;
|
|
156
|
+
if (RECALL_TIMEOUT_MS <= 0) {
|
|
157
|
+
return await recallPromise;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let timeoutHandle: NodeJS.Timeout | null = null;
|
|
161
|
+
const timeoutPromise = new Promise<string>((_, reject) => {
|
|
162
|
+
timeoutHandle = setTimeout(() => {
|
|
163
|
+
abortController.abort();
|
|
164
|
+
reject(new Error("recall timeout"));
|
|
165
|
+
}, RECALL_TIMEOUT_MS);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
let recallResult: string;
|
|
169
|
+
try {
|
|
170
|
+
recallResult = await Promise.race([recallPromise, timeoutPromise]);
|
|
171
|
+
} finally {
|
|
172
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Observation-mode direct-answer tier (issue #518 slice 3c).
|
|
176
|
+
// Runs after the user's recall already succeeded, fire-and-forget,
|
|
177
|
+
// so annotation latency can never delay the caller's response.
|
|
178
|
+
if (caps.recallDirectAnswer && sessionKey) {
|
|
179
|
+
try {
|
|
180
|
+
this.deps.enqueueDirectAnswerObservation(
|
|
181
|
+
prompt,
|
|
182
|
+
sessionKey,
|
|
183
|
+
options.namespace?.trim() || undefined,
|
|
184
|
+
options.principalOverride,
|
|
185
|
+
caps,
|
|
186
|
+
namespacesEnabled,
|
|
187
|
+
);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
log.debug(`direct-answer observation setup failed: ${err}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return recallResult;
|
|
194
|
+
} catch (err) {
|
|
195
|
+
this.deps.logRecallFailure(err);
|
|
196
|
+
// endTrace() is safe here: if no trace is active (disabled or already
|
|
197
|
+
// closed by recallInternal's try/finally), it returns null immediately.
|
|
198
|
+
this.deps.profiler.endTrace();
|
|
199
|
+
return ""; // Return empty context on timeout/error
|
|
200
|
+
} finally {
|
|
201
|
+
options.abortSignal?.removeEventListener("abort", onAbort);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
logRecallFailure(err: unknown): void {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
const LOG_WINDOW_MS = 60_000;
|
|
209
|
+
const idleSinceLastFailureMs = now - this.deps.lastRecallFailureAtMs;
|
|
210
|
+
this.deps.lastRecallFailureAtMs = now;
|
|
211
|
+
if (idleSinceLastFailureMs >= LOG_WINDOW_MS) {
|
|
212
|
+
this.deps.suppressedRecallFailures = 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (now - this.deps.lastRecallFailureLogAtMs >= LOG_WINDOW_MS) {
|
|
216
|
+
const suffix =
|
|
217
|
+
this.deps.suppressedRecallFailures > 0
|
|
218
|
+
? ` (suppressed ${this.deps.suppressedRecallFailures} similar failures in last minute)`
|
|
219
|
+
: "";
|
|
220
|
+
log.warn(`recall timed out or failed: ${errorMsg}${suffix}`);
|
|
221
|
+
this.deps.lastRecallFailureLogAtMs = now;
|
|
222
|
+
this.deps.suppressedRecallFailures = 0;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.deps.suppressedRecallFailures += 1;
|
|
227
|
+
log.debug(`recall timed out or failed (suppressed): ${errorMsg}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
queueEvalShadowRecall(
|
|
231
|
+
record: Omit<EvalShadowRecallRecord, "schemaVersion">,
|
|
232
|
+
): void {
|
|
233
|
+
if (!resolveEvalCapabilities(this.deps.config).evalHarness || !resolveEvalCapabilities(this.deps.config).evalShadowMode)
|
|
234
|
+
return;
|
|
235
|
+
this.deps.evalShadowWriteChain = this.deps.evalShadowWriteChain
|
|
236
|
+
.catch(() => undefined)
|
|
237
|
+
.then(async () => {
|
|
238
|
+
try {
|
|
239
|
+
await recordEvalShadowRecall({
|
|
240
|
+
memoryDir: this.deps.config.memoryDir,
|
|
241
|
+
evalStoreDir: this.deps.config.evalStoreDir,
|
|
242
|
+
record: {
|
|
243
|
+
schemaVersion: 1,
|
|
244
|
+
...record,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
} catch (err) {
|
|
248
|
+
log.debug(`eval shadow recall write failed: ${err}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
publishRecallResults(options: {
|
|
254
|
+
title: string;
|
|
255
|
+
results: QmdSearchResult[];
|
|
256
|
+
sectionBuckets: Map<string, string[]>;
|
|
257
|
+
retrievalQuery: string;
|
|
258
|
+
sessionKey: string | undefined;
|
|
259
|
+
identityInjection?: {
|
|
260
|
+
mode: IdentityInjectionMode | "none";
|
|
261
|
+
injectedChars: number;
|
|
262
|
+
truncated: boolean;
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Issue #1577 — per-recall trust map. When present, quarantined items
|
|
266
|
+
* are filtered from injection on EVERY recall path (hot QMD, embedding
|
|
267
|
+
* fallback, cold archive, recent) so a faithfulness-contradicted memory
|
|
268
|
+
* cannot sneak in via a branch that bypasses trust scoring (review:
|
|
269
|
+
* fallback paths bypass trust). The map is also threaded to
|
|
270
|
+
* formatQmdResults for the epistemic hedge.
|
|
271
|
+
*/
|
|
272
|
+
trustByPath?: Map<string, TrustStageResultItem> | null;
|
|
273
|
+
}): void {
|
|
274
|
+
const sectionId = "memories";
|
|
275
|
+
// Filter quarantined items from ALL recall paths so no branch can inject
|
|
276
|
+
// a hard-negative memory that trust scoring excluded on another path.
|
|
277
|
+
const trustByPath = options.trustByPath ?? null;
|
|
278
|
+
const injectable = trustByPath
|
|
279
|
+
? options.results.filter((r) => !trustByPath.get(r.path)?.quarantined)
|
|
280
|
+
: options.results;
|
|
281
|
+
const memoryIds = this.deps.extractMemoryIdsFromResults(injectable);
|
|
282
|
+
this.deps.trackMemoryAccess(memoryIds);
|
|
283
|
+
|
|
284
|
+
this.deps.appendRecallSection(
|
|
285
|
+
options.sectionBuckets,
|
|
286
|
+
sectionId,
|
|
287
|
+
this.deps.formatQmdResults(options.title, injectable, options.sessionKey, trustByPath),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-context coordinator — extracted from the orchestrator
|
|
3
|
+
* (issue #1526, seam 28).
|
|
4
|
+
*
|
|
5
|
+
* Owns per-session binding surfaces and session-facing service glue:
|
|
6
|
+
* - peer-id and coding-context bindings (incl. namespace overlay)
|
|
7
|
+
* - LCM read-namespace resolution for a session
|
|
8
|
+
* - session flush, passive-correction service construction
|
|
9
|
+
* - day-summary generation and deep-sleep governance triggers
|
|
10
|
+
*
|
|
11
|
+
* Behavior-preserving move (late-binding selfDeps wiring, seams 18–27).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { SmartBuffer } from "../buffer.js";
|
|
15
|
+
import { resolveNamespaceCapabilities } from "../capabilities.js";
|
|
16
|
+
import { combineNamespaces, resolveCodingNamespaceOverlay } from "../coding/coding-namespace.js";
|
|
17
|
+
import { createCorrectionService } from "../correction/correction-access-wiring.js";
|
|
18
|
+
import type { CorrectionService } from "../correction/correction-service.js";
|
|
19
|
+
import { ExtractionEngine } from "../extraction.js";
|
|
20
|
+
import { StorageManager } from "../index.js";
|
|
21
|
+
import { LocalLlmClient } from "../local-llm.js";
|
|
22
|
+
import { log } from "../logger.js";
|
|
23
|
+
import { canWriteNamespace, defaultNamespaceForPrincipal, recallNamespacesForPrincipal, resolvePrincipal } from "../namespaces/principal.js";
|
|
24
|
+
import type { ExtractionRunResult } from "./extraction-run.js";
|
|
25
|
+
import type { BufferTurn, CodingContext, DaySummaryResult, MemoryFile, PluginConfig } from "../types.js";
|
|
26
|
+
import {
|
|
27
|
+
Orchestrator,
|
|
28
|
+
} from "../orchestrator.js";
|
|
29
|
+
|
|
30
|
+
export interface SessionContextDeps {
|
|
31
|
+
readonly _codingContextBySession: Map<string, CodingContext>;
|
|
32
|
+
/** Defensive init for Object.create(prototype) fakes — creates the session-binding maps on the orchestrator. */
|
|
33
|
+
ensureSessionBindingMaps(): void;
|
|
34
|
+
_passiveCorrectionService: CorrectionService | null;
|
|
35
|
+
readonly _peerIdBySession: Map<string, string>;
|
|
36
|
+
applyCodingNamespaceOverlay(sessionKey: string | undefined, baseNamespace: string): string;
|
|
37
|
+
readonly buffer: SmartBuffer;
|
|
38
|
+
readonly config: PluginConfig;
|
|
39
|
+
readonly extraction: ExtractionEngine;
|
|
40
|
+
getCodingContextForSession(sessionKey: string | undefined): CodingContext | null;
|
|
41
|
+
readonly initPromise: Promise<void> | null;
|
|
42
|
+
readonly localLlm: LocalLlmClient;
|
|
43
|
+
processEntitySynthesisQueue(
|
|
44
|
+
namespace?: string,
|
|
45
|
+
maxEntities?: number,
|
|
46
|
+
): Promise<number>;
|
|
47
|
+
queueBufferedExtraction(
|
|
48
|
+
turnsToExtract: BufferTurn[],
|
|
49
|
+
reason: "trigger_mode" | "heartbeat_observer",
|
|
50
|
+
options?: {
|
|
51
|
+
skipDedupeCheck?: boolean;
|
|
52
|
+
clearBufferAfterExtraction?: boolean;
|
|
53
|
+
skipCharThreshold?: boolean;
|
|
54
|
+
skipUserTurnThreshold?: boolean;
|
|
55
|
+
extractionDeadlineMs?: number;
|
|
56
|
+
failOnExtractionFailure?: boolean;
|
|
57
|
+
onTaskSettled?: (
|
|
58
|
+
error?: unknown,
|
|
59
|
+
result?: ExtractionRunResult,
|
|
60
|
+
) => void;
|
|
61
|
+
bufferKey?: string;
|
|
62
|
+
abortSignal?: AbortSignal;
|
|
63
|
+
/**
|
|
64
|
+
* Explicit namespace override for the write path (#460). When set,
|
|
65
|
+
* `runExtraction` writes to this namespace instead of deriving one
|
|
66
|
+
* from `defaultNamespaceForPrincipal(resolvePrincipal(sessionKey))`.
|
|
67
|
+
* Used by bulk-import to pin writes to a deterministic namespace
|
|
68
|
+
* regardless of user-configured principal routing rules.
|
|
69
|
+
*/
|
|
70
|
+
writeNamespaceOverride?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Pin the provenance principal (#1495 thread 1). Forwarded to
|
|
73
|
+
* `runExtraction` so access `observe` can record provenance under the
|
|
74
|
+
* authenticated principal instead of `resolvePrincipal(sessionKey)`.
|
|
75
|
+
*/
|
|
76
|
+
principalOverride?: string;
|
|
77
|
+
},
|
|
78
|
+
): Promise<void>;
|
|
79
|
+
resolvePrincipal(sessionKey?: string): string | undefined;
|
|
80
|
+
readonly storage: StorageManager;
|
|
81
|
+
storageDirNamespace(storageDir: string): string;
|
|
82
|
+
/** The orchestrator itself — passiveCorrectionService constructs the CorrectionService against it. */
|
|
83
|
+
readonly orchestratorSelf: Orchestrator;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class SessionContextCoordinator {
|
|
87
|
+
constructor(
|
|
88
|
+
private readonly deps: SessionContextDeps,
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Effective namespace a same-session LCM/structured-history READER must use
|
|
93
|
+
* to find what the access `observe` surface WROTE (#1495 thread 2).
|
|
94
|
+
*
|
|
95
|
+
* This MUST mirror the `observe` scope plan's write-namespace resolution, NOT
|
|
96
|
+
* `resolveSelfNamespace`: when no coding overlay applies, `observe` archives
|
|
97
|
+
* under `config.defaultNamespace` (an unqualified observed turn is NOT moved
|
|
98
|
+
* to the principal self namespace — identical to
|
|
99
|
+
* `resolveCodingScopedWriteNamespace`/`memory_store`, rule 39). Only when a
|
|
100
|
+
* coding overlay actually changes the namespace does the writer (and so the
|
|
101
|
+
* reader) use the overlaid `project-*` namespace. Returning the self base for
|
|
102
|
+
* the no-overlay case would prefix the read key with a namespace the writer
|
|
103
|
+
* never used, so the reader would miss its own evidence.
|
|
104
|
+
*
|
|
105
|
+
* Honours the access-surface `principalOverride` (#1505 thread 2, codex): when
|
|
106
|
+
* a recall supplies an authenticated principal NOT encoded in the raw
|
|
107
|
+
* `sessionKey`, `observe` archived LCM under THAT principal's base namespace.
|
|
108
|
+
* Deriving the base from `resolvePrincipal(sessionKey)` alone could fall back
|
|
109
|
+
* to `default`, so principal `alice` observing `sess-1` would write under
|
|
110
|
+
* `alice` but READ under `default`. Threading the override here keeps the read
|
|
111
|
+
* base identical to the write base.
|
|
112
|
+
*
|
|
113
|
+
* READ-AUTHORIZATION gate (#1505 round 3, codex P2 "Gate LCM recall keys by
|
|
114
|
+
* readable namespaces"): the overlay LCM read key is a `<principal>-project-*`
|
|
115
|
+
* sub-namespace of the principal SELF base. The normal recall namespace set
|
|
116
|
+
* below only substitutes the coding overlay when the principal SELF base is
|
|
117
|
+
* actually in the readable recall set (`recallNamespacesForPrincipal` — gated
|
|
118
|
+
* by `defaultRecallNamespaces.includes("self")` AND `canReadNamespace`). If a
|
|
119
|
+
* principal can WRITE but not READ its self namespace (or `defaultRecall-
|
|
120
|
+
* Namespaces` omits `self`), QMD/file recall never touches those overlay rows,
|
|
121
|
+
* so neither may the LCM read key. When the self base is NOT readable, fall
|
|
122
|
+
* back to the default store — exactly what an unqualified, unauthorized recall
|
|
123
|
+
* resolves to — rather than injecting overlay rows the rest of recall excludes
|
|
124
|
+
* (rule 42 read/write parity; rule 48 least-privilege).
|
|
125
|
+
*/
|
|
126
|
+
lcmReadNamespaceForSession(
|
|
127
|
+
sessionKey?: string,
|
|
128
|
+
principalOverride?: string,
|
|
129
|
+
): string {
|
|
130
|
+
const principal =
|
|
131
|
+
typeof principalOverride === "string" && principalOverride.length > 0
|
|
132
|
+
? principalOverride
|
|
133
|
+
: this.deps.resolvePrincipal(sessionKey);
|
|
134
|
+
const base = defaultNamespaceForPrincipal(principal, this.deps.config);
|
|
135
|
+
const overlaid = this.deps.applyCodingNamespaceOverlay(sessionKey, base);
|
|
136
|
+
// No overlay → collapse to the default store so the LCM key is the raw
|
|
137
|
+
// sessionKey, exactly what an unqualified observe archived under.
|
|
138
|
+
if (overlaid === base) return this.deps.config.defaultNamespace;
|
|
139
|
+
// Overlay applied. Only honour it when the principal SELF base is in the
|
|
140
|
+
// readable recall set (same gate the recall namespace set uses to
|
|
141
|
+
// substitute the overlay). Otherwise the overlay rows are unauthorized for
|
|
142
|
+
// this reader — fall back to the default store so the LCM read matches
|
|
143
|
+
// what QMD/file recall would surface.
|
|
144
|
+
const selfReadableInRecall = recallNamespacesForPrincipal(
|
|
145
|
+
principal,
|
|
146
|
+
this.deps.config,
|
|
147
|
+
).includes(base);
|
|
148
|
+
return selfReadableInRecall ? overlaid : this.deps.config.defaultNamespace;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Attach a coding-agent context to a session (issue #569). Called by the
|
|
153
|
+
* Claude Code / Codex / Cursor connectors at session start after
|
|
154
|
+
* `resolveGitContext(cwd)`. The context is consulted by the recall path
|
|
155
|
+
* and the write path so that memories route to a project- (and optionally
|
|
156
|
+
* branch-) scoped namespace.
|
|
157
|
+
*
|
|
158
|
+
* Pass `null` to clear.
|
|
159
|
+
*/
|
|
160
|
+
setCodingContextForSession(sessionKey: string, codingContext: CodingContext | null): void {
|
|
161
|
+
if (typeof sessionKey !== "string" || sessionKey.length === 0) return;
|
|
162
|
+
// Defensive init — `Object.create(Orchestrator.prototype)` stubs in
|
|
163
|
+
// legacy tests skip class-field initializers. The init runs on the
|
|
164
|
+
// ORCHESTRATOR via deps (PR #1802 review: writing here would create
|
|
165
|
+
// the map on the coordinator while reads keep going through deps).
|
|
166
|
+
this.deps.ensureSessionBindingMaps();
|
|
167
|
+
if (codingContext === null) {
|
|
168
|
+
this.deps._codingContextBySession.delete(sessionKey);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.deps._codingContextBySession.set(sessionKey, codingContext);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Read-only accessor for the coding context attached to a session. Returns
|
|
176
|
+
* `null` when none is set. Used by `remnic doctor` and by tests.
|
|
177
|
+
*
|
|
178
|
+
* Defensive `_codingContextBySession` lookup — legacy orchestrator-flush
|
|
179
|
+
* tests use `Object.create(Orchestrator.prototype)` which does not run
|
|
180
|
+
* class-field initializers, so the Map may be undefined on stubs.
|
|
181
|
+
*/
|
|
182
|
+
getCodingContextForSession(sessionKey: string | undefined): CodingContext | null {
|
|
183
|
+
if (typeof sessionKey !== "string" || sessionKey.length === 0) return null;
|
|
184
|
+
return this.deps._codingContextBySession?.get(sessionKey) ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Shared helper used by both the recall path and the write path (rule 42).
|
|
189
|
+
*
|
|
190
|
+
* Given a base namespace computed from the principal, returns the overlaid
|
|
191
|
+
* coding namespace when the session has a coding context AND
|
|
192
|
+
* `codingMode.projectScope` is true AND `namespacesEnabled` is true.
|
|
193
|
+
* Otherwise returns `baseNamespace` unchanged — CLAUDE.md #30 escape hatch.
|
|
194
|
+
*
|
|
195
|
+
* Principal isolation (CLAUDE.md rule 42): the overlay is COMBINED with
|
|
196
|
+
* the principal-derived `baseNamespace` rather than replacing it, so two
|
|
197
|
+
* principals working in the same repository do not share memories through
|
|
198
|
+
* a common `project-*` namespace.
|
|
199
|
+
*
|
|
200
|
+
* Namespaces-disabled gate: when `namespacesEnabled` is false, the
|
|
201
|
+
* storage router maps every namespace to the same `memoryDir`. Returning
|
|
202
|
+
* `project-*` in that mode would create apparent route separation with
|
|
203
|
+
* no actual storage isolation — a false-isolation trap. In that mode we
|
|
204
|
+
* return `baseNamespace` unchanged so coding mode degrades to the existing
|
|
205
|
+
* unscoped behavior.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
applyCodingNamespaceOverlay(sessionKey: string | undefined, baseNamespace: string): string {
|
|
210
|
+
if (!resolveNamespaceCapabilities(this.deps.config).namespaces) return baseNamespace;
|
|
211
|
+
const codingContext = this.deps.getCodingContextForSession(sessionKey);
|
|
212
|
+
const overlay = resolveCodingNamespaceOverlay(codingContext, this.deps.config.codingMode, this.deps.config.defaultNamespace);
|
|
213
|
+
if (!overlay) return baseNamespace;
|
|
214
|
+
return combineNamespaces(baseNamespace, overlay.namespace);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Register a peer ID for a session so recall can inject the peer's
|
|
219
|
+
* profile into context (issue #679 PR 3/5). Pass `null` to clear.
|
|
220
|
+
*
|
|
221
|
+
* Connectors and the `before_agent_start` hook call this when the
|
|
222
|
+
* session's counter-party is known. The ID is validated against
|
|
223
|
+
* `PEER_ID_PATTERN` before storing.
|
|
224
|
+
*
|
|
225
|
+
* Fail-closed (Codex P1 review): an invalid peerId clears any
|
|
226
|
+
* previously registered mapping for the session rather than silently
|
|
227
|
+
* keeping stale data. This prevents a malformed metadata update from
|
|
228
|
+
* mixing one peer's profile context into another session.
|
|
229
|
+
*
|
|
230
|
+
* Defensive init (Cursor review + rule 16): `Object.create(
|
|
231
|
+
* Orchestrator.prototype)` stubs in legacy tests skip class-field
|
|
232
|
+
* initializers, so `_peerIdBySession` may be undefined. Mirror the
|
|
233
|
+
* same guard used by `setCodingContextForSession`.
|
|
234
|
+
*/
|
|
235
|
+
setPeerIdForSession(sessionKey: string, peerId: string | null): void {
|
|
236
|
+
if (typeof sessionKey !== "string" || sessionKey.length === 0) return;
|
|
237
|
+
// Defensive init — mirrors setCodingContextForSession (rule 16);
|
|
238
|
+
// runs on the orchestrator via deps (PR #1802 review).
|
|
239
|
+
this.deps.ensureSessionBindingMaps();
|
|
240
|
+
if (peerId === null) {
|
|
241
|
+
this.deps._peerIdBySession.delete(sessionKey);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Basic pattern guard — full validation lives in peers/storage.ts.
|
|
245
|
+
// Invalid input is fail-closed: clear the existing mapping so stale
|
|
246
|
+
// peer context can't bleed in after a bad metadata update (Codex P1).
|
|
247
|
+
if (
|
|
248
|
+
typeof peerId !== "string" ||
|
|
249
|
+
peerId.length === 0 ||
|
|
250
|
+
peerId.length > 64 ||
|
|
251
|
+
!/^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/.test(peerId)
|
|
252
|
+
) {
|
|
253
|
+
log.warn(`setPeerIdForSession: invalid peerId — clearing session mapping`);
|
|
254
|
+
this.deps._peerIdBySession.delete(sessionKey);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.deps._peerIdBySession.set(sessionKey, peerId);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Return the peer ID registered for a session, or `null` when none
|
|
262
|
+
* is set. Used by `recallInternal` to inject the peer profile section.
|
|
263
|
+
* Defensive `_peerIdBySession` lookup — legacy orchestrator-flush tests
|
|
264
|
+
* use `Object.create(Orchestrator.prototype)` which skips class-field
|
|
265
|
+
* initializers, so the Map may be undefined on stubs.
|
|
266
|
+
*/
|
|
267
|
+
getPeerIdForSession(sessionKey: string | undefined): string | null {
|
|
268
|
+
if (typeof sessionKey !== "string" || sessionKey.length === 0) return null;
|
|
269
|
+
return this.deps._peerIdBySession?.get(sessionKey) ?? null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async runDeepSleepGovernanceNow(options?: {
|
|
273
|
+
dryRun?: boolean;
|
|
274
|
+
storage?: StorageManager;
|
|
275
|
+
}): Promise<{ scannedMemories: number; appliedActionCount: number; notes?: string }> {
|
|
276
|
+
const targetStorage = options?.storage ?? this.deps.storage;
|
|
277
|
+
const { runMemoryGovernance } = await import("../maintenance/memory-governance.js");
|
|
278
|
+
const { summarizeGovernanceResultForDreams } = await import("../maintenance/dreams-ledger.js");
|
|
279
|
+
const govResult = await runMemoryGovernance({
|
|
280
|
+
memoryDir: targetStorage.dir,
|
|
281
|
+
mode: options?.dryRun === true ? "shadow" : "apply",
|
|
282
|
+
});
|
|
283
|
+
if (options?.dryRun !== true) {
|
|
284
|
+
try {
|
|
285
|
+
await this.deps.processEntitySynthesisQueue(
|
|
286
|
+
this.deps.storageDirNamespace(targetStorage.dir),
|
|
287
|
+
5,
|
|
288
|
+
);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
log.debug(`deep-sleep governance: entity synthesis refresh failed after apply: ${error}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return summarizeGovernanceResultForDreams(govResult, options?.dryRun === true);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async generateDaySummary(
|
|
297
|
+
memories: string | MemoryFile[],
|
|
298
|
+
): Promise<DaySummaryResult | null> {
|
|
299
|
+
if (this.deps.initPromise) {
|
|
300
|
+
let initGateTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
301
|
+
try {
|
|
302
|
+
await Promise.race([
|
|
303
|
+
this.deps.initPromise.catch(() => undefined),
|
|
304
|
+
new Promise((resolve) => {
|
|
305
|
+
initGateTimeoutHandle = setTimeout(
|
|
306
|
+
resolve,
|
|
307
|
+
this.deps.config.initGateTimeoutMs,
|
|
308
|
+
);
|
|
309
|
+
}),
|
|
310
|
+
]);
|
|
311
|
+
} finally {
|
|
312
|
+
if (initGateTimeoutHandle) clearTimeout(initGateTimeoutHandle);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return this.deps.extraction.generateDaySummary(memories);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async flushSession(
|
|
319
|
+
sessionKey: string,
|
|
320
|
+
options: {
|
|
321
|
+
reason: string;
|
|
322
|
+
abortSignal?: AbortSignal;
|
|
323
|
+
bufferKey?: string;
|
|
324
|
+
},
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
const explicitBufferKey =
|
|
327
|
+
typeof options.bufferKey === "string" && options.bufferKey.length > 0
|
|
328
|
+
? options.bufferKey
|
|
329
|
+
: null;
|
|
330
|
+
const discoveredBufferKeys =
|
|
331
|
+
explicitBufferKey ||
|
|
332
|
+
typeof sessionKey !== "string" ||
|
|
333
|
+
sessionKey.length === 0 ||
|
|
334
|
+
typeof this.deps.buffer.findBufferKeysForSession !== "function"
|
|
335
|
+
? []
|
|
336
|
+
: await this.deps.buffer.findBufferKeysForSession(sessionKey);
|
|
337
|
+
const bufferKeys = explicitBufferKey
|
|
338
|
+
? [explicitBufferKey]
|
|
339
|
+
: discoveredBufferKeys.length > 0
|
|
340
|
+
? discoveredBufferKeys
|
|
341
|
+
: typeof sessionKey === "string" && sessionKey.length > 0
|
|
342
|
+
? [sessionKey]
|
|
343
|
+
: ["default"];
|
|
344
|
+
for (const bufferKey of bufferKeys) {
|
|
345
|
+
const turns = this.deps.buffer.getTurns(bufferKey);
|
|
346
|
+
if (turns.length === 0) continue;
|
|
347
|
+
await new Promise<void>((resolve, reject) => {
|
|
348
|
+
void this.deps.queueBufferedExtraction(turns, "trigger_mode", {
|
|
349
|
+
bufferKey,
|
|
350
|
+
clearBufferAfterExtraction: true,
|
|
351
|
+
skipDedupeCheck: true,
|
|
352
|
+
abortSignal: options.abortSignal,
|
|
353
|
+
onTaskSettled: (error) => (error ? reject(error) : resolve()),
|
|
354
|
+
})
|
|
355
|
+
.catch(reject);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Lazily construct the CorrectionService for passive capture. Stateless
|
|
361
|
+
* across requests (per #1580 design); cached for the orchestrator's life. */
|
|
362
|
+
passiveCorrectionService(): CorrectionService {
|
|
363
|
+
if (this.deps._passiveCorrectionService) return this.deps._passiveCorrectionService;
|
|
364
|
+
this.deps._passiveCorrectionService = createCorrectionService({
|
|
365
|
+
orchestrator: this.deps.orchestratorSelf,
|
|
366
|
+
// Session-scoped write ACL (review: "passive capture bypasses write
|
|
367
|
+
// ACL"). A correction detected in session S plans only against S readable
|
|
368
|
+
// namespaces and applies only to writable ones (rule 42) — passive
|
|
369
|
+
// capture never becomes a cross-tenant mutation vector. Mirrors the
|
|
370
|
+
// access-service createCorrectionService wiring rather than bypassing it.
|
|
371
|
+
resolveAuthorizedNamespace: async (req) => {
|
|
372
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.deps.config);
|
|
373
|
+
const ns = req.namespace ?? defaultNamespaceForPrincipal(principal, this.deps.config);
|
|
374
|
+
if (!canWriteNamespace(principal, ns, this.deps.config)) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`passive correction: namespace "${ns}" is not writable for principal ${principal ?? "(none)"}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return ns;
|
|
380
|
+
},
|
|
381
|
+
resolveReadableNamespaces: (req) => {
|
|
382
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.deps.config);
|
|
383
|
+
return recallNamespacesForPrincipal(principal, this.deps.config);
|
|
384
|
+
},
|
|
385
|
+
canWriteNamespace: async (req) => {
|
|
386
|
+
const principal = req.principal || resolvePrincipal(req.sessionKey, this.deps.config);
|
|
387
|
+
return canWriteNamespace(principal, req.namespace, this.deps.config);
|
|
388
|
+
},
|
|
389
|
+
llmComplete: async ({ system, user }) => {
|
|
390
|
+
const llmResult = await this.deps.localLlm.chatCompletion(
|
|
391
|
+
[
|
|
392
|
+
{ role: "system", content: system },
|
|
393
|
+
{ role: "user", content: user },
|
|
394
|
+
],
|
|
395
|
+
{ operation: "correction-classify", priority: "background" },
|
|
396
|
+
);
|
|
397
|
+
if (!llmResult) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
"passive correction classify+draft: local LLM unavailable (disabled or in cooldown)",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return llmResult.content;
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
return this.deps._passiveCorrectionService;
|
|
406
|
+
}
|
|
407
|
+
}
|