@remnic/core 9.3.742 → 9.3.743
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +10 -10
- package/dist/access-cli.js +29 -29
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +15 -15
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +14 -14
- package/dist/access-operations-batch.js +12 -12
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +13 -13
- package/dist/{access-service-K8n12dXu.d.ts → access-service-BeW5GOg1.d.ts} +3 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +9 -9
- 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-memory-bridge.js +3 -3
- package/dist/active-recall.d.ts +1 -1
- 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/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +2 -2
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +10 -5
- package/dist/capabilities.js +1 -1
- package/dist/{catalog-_Bap6vfD.d.ts → catalog-CX0CjOaf.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 +2 -2
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-X5PMDVY4.js → chunk-3EA5LIFJ.js} +4 -4
- package/dist/{chunk-YNHRWFAP.js → chunk-4DLFJJOQ.js} +2 -2
- package/dist/{chunk-WIMKOTNY.js → chunk-5QANTTY2.js} +2 -2
- package/dist/{chunk-AM7YKD7V.js → chunk-5UNSGRCR.js} +5 -5
- package/dist/{chunk-Y6PNYJVO.js → chunk-A2KKLCOS.js} +2 -2
- package/dist/{chunk-O4IWVWSW.js → chunk-AVBT5WEW.js} +3 -3
- package/dist/{chunk-QUNH5UZH.js → chunk-BYLC4DKI.js} +2 -2
- package/dist/{chunk-UOVDO6TC.js → chunk-CHELXDTO.js} +3 -3
- package/dist/{chunk-TQQN64J2.js → chunk-DBSMLRTO.js} +2 -2
- package/dist/{chunk-EZZUNKMJ.js → chunk-DYK27X7V.js} +3 -3
- package/dist/{chunk-K2N6WIZM.js → chunk-E7RGJQWP.js} +2 -2
- package/dist/{chunk-M4WI23X3.js → chunk-EEKDRAWX.js} +2 -2
- package/dist/{chunk-GFJ5ABKS.js → chunk-G5FP7VSQ.js} +11 -11
- package/dist/{chunk-BE6NEQLF.js → chunk-HAZVVOYT.js} +3 -3
- package/dist/{chunk-OT5K26NG.js → chunk-HM6UJPPO.js} +2 -2
- package/dist/{chunk-PI4JHLDD.js → chunk-IP5SANO2.js} +2 -2
- package/dist/{chunk-WHEF76BQ.js → chunk-JRZV3767.js} +2 -2
- package/dist/{chunk-OS5RKJOH.js → chunk-JZ7P26GF.js} +2 -2
- package/dist/{chunk-Y62QPRKV.js → chunk-K5LLP4CY.js} +2 -2
- package/dist/{chunk-C3Z5YJSM.js → chunk-LOPZ2DWR.js} +2 -2
- package/dist/{chunk-BJPL5WAL.js → chunk-MXDG6SRH.js} +3 -3
- package/dist/{chunk-UYYSOMS2.js → chunk-NBP3YHHS.js} +4 -4
- package/dist/{chunk-USHVHI6J.js → chunk-NNL4LJAD.js} +3 -3
- package/dist/{chunk-GPX5E36D.js → chunk-OSMBC3JU.js} +2 -2
- package/dist/{chunk-OUSOMUFV.js → chunk-PE6XR4GG.js} +2 -2
- package/dist/{chunk-CMPXLMC3.js → chunk-PI23H7FV.js} +3 -3
- package/dist/{chunk-WGDE2TJP.js → chunk-QOGXUNWL.js} +4 -4
- package/dist/{chunk-ELL5VYXC.js → chunk-RNWWAS5V.js} +3 -3
- package/dist/{chunk-JMMSVRF4.js → chunk-TQPSOTC7.js} +7 -7
- package/dist/{chunk-AX4TRSRE.js → chunk-U5WVRMCA.js} +2 -2
- package/dist/{chunk-7ADHJ6SU.js → chunk-VE54E6RP.js} +4 -4
- package/dist/{chunk-7HUGLMSW.js → chunk-VO4VARZL.js} +465 -321
- package/dist/chunk-VO4VARZL.js.map +1 -0
- package/dist/{chunk-G7Q4D2G3.js → chunk-VSF7AAGE.js} +1 -3
- package/dist/chunk-VSF7AAGE.js.map +1 -0
- package/dist/{chunk-OVYBENIJ.js → chunk-W3M34XT4.js} +3 -3
- package/dist/{chunk-NHJYUIII.js → chunk-WPLRJ4XM.js} +7 -7
- package/dist/{chunk-UEIF7RWQ.js → chunk-XEADGIAA.js} +6 -6
- package/dist/{cli-BEOLy1xo.d.ts → cli-BYad9YYY.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +21 -21
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- 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/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +2 -2
- 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-UERKJESC.js → contradiction-scan-3FJYWS3G.js} +3 -3
- 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/day-summary.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +2 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +2 -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 -4
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-GQ3UQNIL.js → first-start-migration-4I3VDSYS.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 +9 -9
- package/dist/index.js +36 -36
- 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/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-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- 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 +6 -6
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +2 -2
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +11 -11
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +6 -6
- package/dist/{orchestrator-Dr-1v5EL.d.ts → orchestrator-CupigmWu.d.ts} +163 -7
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +24 -24
- 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/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +3 -3
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +3 -3
- 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-DsIEiCka.d.ts → semantic-consolidation-ByzGwC9c.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +2 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/shared-context/manager.js +2 -2
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +3 -3
- 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-CSKL7BDG.js → tier-stats-5XF4UBRQ.js} +2 -2
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CglNn2V-.d.ts → types-5Jik3r4G.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/package.json +2 -2
- package/src/capabilities.test.ts +0 -2
- package/src/capabilities.ts +7 -6
- package/src/orchestration/contradiction-linking-coordinator.ts +336 -0
- package/src/orchestration/recall-section-coordinator.ts +349 -0
- package/src/orchestrator.ts +67 -400
- package/dist/chunk-7HUGLMSW.js.map +0 -1
- package/dist/chunk-G7Q4D2G3.js.map +0 -1
- /package/dist/{chunk-X5PMDVY4.js.map → chunk-3EA5LIFJ.js.map} +0 -0
- /package/dist/{chunk-YNHRWFAP.js.map → chunk-4DLFJJOQ.js.map} +0 -0
- /package/dist/{chunk-WIMKOTNY.js.map → chunk-5QANTTY2.js.map} +0 -0
- /package/dist/{chunk-AM7YKD7V.js.map → chunk-5UNSGRCR.js.map} +0 -0
- /package/dist/{chunk-Y6PNYJVO.js.map → chunk-A2KKLCOS.js.map} +0 -0
- /package/dist/{chunk-O4IWVWSW.js.map → chunk-AVBT5WEW.js.map} +0 -0
- /package/dist/{chunk-QUNH5UZH.js.map → chunk-BYLC4DKI.js.map} +0 -0
- /package/dist/{chunk-UOVDO6TC.js.map → chunk-CHELXDTO.js.map} +0 -0
- /package/dist/{chunk-TQQN64J2.js.map → chunk-DBSMLRTO.js.map} +0 -0
- /package/dist/{chunk-EZZUNKMJ.js.map → chunk-DYK27X7V.js.map} +0 -0
- /package/dist/{chunk-K2N6WIZM.js.map → chunk-E7RGJQWP.js.map} +0 -0
- /package/dist/{chunk-M4WI23X3.js.map → chunk-EEKDRAWX.js.map} +0 -0
- /package/dist/{chunk-GFJ5ABKS.js.map → chunk-G5FP7VSQ.js.map} +0 -0
- /package/dist/{chunk-BE6NEQLF.js.map → chunk-HAZVVOYT.js.map} +0 -0
- /package/dist/{chunk-OT5K26NG.js.map → chunk-HM6UJPPO.js.map} +0 -0
- /package/dist/{chunk-PI4JHLDD.js.map → chunk-IP5SANO2.js.map} +0 -0
- /package/dist/{chunk-WHEF76BQ.js.map → chunk-JRZV3767.js.map} +0 -0
- /package/dist/{chunk-OS5RKJOH.js.map → chunk-JZ7P26GF.js.map} +0 -0
- /package/dist/{chunk-Y62QPRKV.js.map → chunk-K5LLP4CY.js.map} +0 -0
- /package/dist/{chunk-C3Z5YJSM.js.map → chunk-LOPZ2DWR.js.map} +0 -0
- /package/dist/{chunk-BJPL5WAL.js.map → chunk-MXDG6SRH.js.map} +0 -0
- /package/dist/{chunk-UYYSOMS2.js.map → chunk-NBP3YHHS.js.map} +0 -0
- /package/dist/{chunk-USHVHI6J.js.map → chunk-NNL4LJAD.js.map} +0 -0
- /package/dist/{chunk-GPX5E36D.js.map → chunk-OSMBC3JU.js.map} +0 -0
- /package/dist/{chunk-OUSOMUFV.js.map → chunk-PE6XR4GG.js.map} +0 -0
- /package/dist/{chunk-CMPXLMC3.js.map → chunk-PI23H7FV.js.map} +0 -0
- /package/dist/{chunk-WGDE2TJP.js.map → chunk-QOGXUNWL.js.map} +0 -0
- /package/dist/{chunk-ELL5VYXC.js.map → chunk-RNWWAS5V.js.map} +0 -0
- /package/dist/{chunk-JMMSVRF4.js.map → chunk-TQPSOTC7.js.map} +0 -0
- /package/dist/{chunk-AX4TRSRE.js.map → chunk-U5WVRMCA.js.map} +0 -0
- /package/dist/{chunk-7ADHJ6SU.js.map → chunk-VE54E6RP.js.map} +0 -0
- /package/dist/{chunk-OVYBENIJ.js.map → chunk-W3M34XT4.js.map} +0 -0
- /package/dist/{chunk-NHJYUIII.js.map → chunk-WPLRJ4XM.js.map} +0 -0
- /package/dist/{chunk-UEIF7RWQ.js.map → chunk-XEADGIAA.js.map} +0 -0
- /package/dist/{contradiction-scan-UERKJESC.js.map → contradiction-scan-3FJYWS3G.js.map} +0 -0
- /package/dist/{first-start-migration-GQ3UQNIL.js.map → first-start-migration-4I3VDSYS.js.map} +0 -0
- /package/dist/{tier-stats-CSKL7BDG.js.map → tier-stats-5XF4UBRQ.js.map} +0 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contradiction-linking coordinator — extracted from the orchestrator (issue #1526).
|
|
3
|
+
*
|
|
4
|
+
* Owns the write-path semantic-analysis subsystem: contradiction detection
|
|
5
|
+
* (QMD similarity search + LLM verification), deferred contradiction
|
|
6
|
+
* auto-resolve (#1645 — retires superseded memories only after the new write's
|
|
7
|
+
* tombstone status is known), and memory link suggestion. Behavior-preserving
|
|
8
|
+
* move from orchestrator.ts — no logic changes; the orchestrator constructs one
|
|
9
|
+
* instance and keeps thin delegating methods so existing call sites (writeMemory
|
|
10
|
+
* fact-write branches) continue to work.
|
|
11
|
+
*
|
|
12
|
+
* Config, storage, search, namespace resolution, and the extraction engine are
|
|
13
|
+
* accessed through getter callbacks (not captured at construction) so that
|
|
14
|
+
* post-construction reassignment of the orchestrator's live fields is honored.
|
|
15
|
+
* This mirrors the RecallRerankCoordinator / ConversationIndexCoordinator
|
|
16
|
+
* accessor pattern.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
MemoryLink,
|
|
21
|
+
PluginConfig,
|
|
22
|
+
QmdSearchResult,
|
|
23
|
+
} from "../types.js";
|
|
24
|
+
// StorageManager type comes from the package barrel (type-only) so this
|
|
25
|
+
// module does not add a direct storage.ts import (ratchet #1533).
|
|
26
|
+
import type { StorageManager } from "../index.js";
|
|
27
|
+
import type { ExtractionEngine } from "../extraction.js";
|
|
28
|
+
import { log } from "../logger.js";
|
|
29
|
+
import { resolveIndexingCapabilities } from "../capabilities.js";
|
|
30
|
+
import { deindexMemory } from "../temporal-index.js";
|
|
31
|
+
|
|
32
|
+
/** Result type of {@link ContradictionLinkingCoordinator.checkForContradiction}. */
|
|
33
|
+
export interface ContradictionResult {
|
|
34
|
+
supersededId: string;
|
|
35
|
+
confidence: number;
|
|
36
|
+
reason: string;
|
|
37
|
+
supersededPath: string;
|
|
38
|
+
supersededCreated: string;
|
|
39
|
+
supersededTags: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Subset of {@link ContradictionResult} used by
|
|
44
|
+
* {@link ContradictionLinkingCoordinator.applyDeferredContradictionResolve}.
|
|
45
|
+
* The deferred resolve runs AFTER writeMemory and only needs the supersede
|
|
46
|
+
* target fields (not confidence, which the caller already consumed).
|
|
47
|
+
*/
|
|
48
|
+
export interface ContradictionResolveTarget {
|
|
49
|
+
supersededId: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
supersededPath: string;
|
|
52
|
+
supersededCreated: string;
|
|
53
|
+
supersededTags: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Coordinator for the contradiction-detection + memory-linking subsystem.
|
|
58
|
+
*
|
|
59
|
+
* The three methods mirror the orchestrator's former private methods
|
|
60
|
+
* `checkForContradiction`, `applyDeferredContradictionResolve`, and
|
|
61
|
+
* `suggestLinksForMemory` byte-for-byte in logic.
|
|
62
|
+
*/
|
|
63
|
+
export class ContradictionLinkingCoordinator {
|
|
64
|
+
private readonly getConfig: () => PluginConfig;
|
|
65
|
+
private readonly isSearchAvailable: () => boolean;
|
|
66
|
+
private readonly searchAcrossNamespaces: (options: {
|
|
67
|
+
query: string;
|
|
68
|
+
namespaces?: string[];
|
|
69
|
+
maxResults?: number;
|
|
70
|
+
mode?: "search" | "hybrid" | "bm25" | "vector";
|
|
71
|
+
}) => Promise<QmdSearchResult[]>;
|
|
72
|
+
private readonly extractMemoryIdsFromResults: (
|
|
73
|
+
results: QmdSearchResult[],
|
|
74
|
+
) => string[];
|
|
75
|
+
private readonly namespaceFromPath: (p: string) => string;
|
|
76
|
+
private readonly storageForNamespace: (
|
|
77
|
+
namespace: string,
|
|
78
|
+
) => Promise<StorageManager>;
|
|
79
|
+
private readonly getExtraction: () => ExtractionEngine;
|
|
80
|
+
|
|
81
|
+
constructor(options: {
|
|
82
|
+
getConfig: () => PluginConfig;
|
|
83
|
+
isSearchAvailable: () => boolean;
|
|
84
|
+
searchAcrossNamespaces: (options: {
|
|
85
|
+
query: string;
|
|
86
|
+
namespaces?: string[];
|
|
87
|
+
maxResults?: number;
|
|
88
|
+
mode?: "search" | "hybrid" | "bm25" | "vector";
|
|
89
|
+
}) => Promise<QmdSearchResult[]>;
|
|
90
|
+
extractMemoryIdsFromResults: (results: QmdSearchResult[]) => string[];
|
|
91
|
+
namespaceFromPath: (p: string) => string;
|
|
92
|
+
storageForNamespace: (namespace: string) => Promise<StorageManager>;
|
|
93
|
+
getExtraction: () => ExtractionEngine;
|
|
94
|
+
}) {
|
|
95
|
+
this.getConfig = options.getConfig;
|
|
96
|
+
this.isSearchAvailable = options.isSearchAvailable;
|
|
97
|
+
this.searchAcrossNamespaces = options.searchAcrossNamespaces;
|
|
98
|
+
this.extractMemoryIdsFromResults = options.extractMemoryIdsFromResults;
|
|
99
|
+
this.namespaceFromPath = options.namespaceFromPath;
|
|
100
|
+
this.storageForNamespace = options.storageForNamespace;
|
|
101
|
+
this.getExtraction = options.getExtraction;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a new memory contradicts an existing one.
|
|
106
|
+
* Uses QMD to find similar memories, then LLM to verify contradiction.
|
|
107
|
+
*/
|
|
108
|
+
async checkForContradiction(
|
|
109
|
+
content: string,
|
|
110
|
+
category: string,
|
|
111
|
+
namespaceScope: string,
|
|
112
|
+
): Promise<ContradictionResult | null> {
|
|
113
|
+
if (!this.isSearchAvailable()) return null;
|
|
114
|
+
|
|
115
|
+
const config = this.getConfig();
|
|
116
|
+
|
|
117
|
+
// Search for similar memories
|
|
118
|
+
const results = await this.searchAcrossNamespaces({
|
|
119
|
+
query: content,
|
|
120
|
+
namespaces: [namespaceScope],
|
|
121
|
+
maxResults: 5,
|
|
122
|
+
mode: "search",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
for (const result of results) {
|
|
126
|
+
// Check similarity threshold
|
|
127
|
+
if (result.score < config.contradictionSimilarityThreshold) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get the existing memory
|
|
132
|
+
const memoryId = this.extractMemoryIdsFromResults([result])[0];
|
|
133
|
+
if (!memoryId) continue;
|
|
134
|
+
|
|
135
|
+
const resultNamespace = this.namespaceFromPath(result.path);
|
|
136
|
+
if (resultNamespace !== namespaceScope) continue;
|
|
137
|
+
const resultStorage = await this.storageForNamespace(resultNamespace);
|
|
138
|
+
const existingMemory = await resultStorage.getMemoryById(memoryId);
|
|
139
|
+
if (!existingMemory) continue;
|
|
140
|
+
|
|
141
|
+
// Skip memories already resolved or explicitly forgotten. Other
|
|
142
|
+
// non-active statuses remain valid contradiction candidates.
|
|
143
|
+
if (
|
|
144
|
+
existingMemory.frontmatter.status === "superseded" ||
|
|
145
|
+
existingMemory.frontmatter.status === "forgotten"
|
|
146
|
+
) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Verify contradiction with LLM
|
|
151
|
+
const extraction = this.getExtraction();
|
|
152
|
+
const verification = await extraction.verifyContradiction(
|
|
153
|
+
{ content, category },
|
|
154
|
+
{
|
|
155
|
+
id: existingMemory.frontmatter.id,
|
|
156
|
+
content: existingMemory.content,
|
|
157
|
+
category: existingMemory.frontmatter.category,
|
|
158
|
+
created: existingMemory.frontmatter.created,
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!verification) continue;
|
|
163
|
+
|
|
164
|
+
// Check if it's a real contradiction with high confidence
|
|
165
|
+
if (
|
|
166
|
+
verification.isContradiction &&
|
|
167
|
+
verification.confidence >= config.contradictionMinConfidence
|
|
168
|
+
) {
|
|
169
|
+
// When the LLM says the existing memory is newer (whichIsNewer ===
|
|
170
|
+
// "first") the incoming fact is the stale one in both resolve modes —
|
|
171
|
+
// log and continue so the caller never marks contradictionDetected and
|
|
172
|
+
// the semantic-skip gate can discard the outdated write normally.
|
|
173
|
+
if (verification.whichIsNewer === "first") {
|
|
174
|
+
log.info(
|
|
175
|
+
`detected contradiction (confidence: ${verification.confidence}): ${existingMemory.frontmatter.id} vs new memory — existing is newer, incoming fact is stale`,
|
|
176
|
+
);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// The new fact is newer than the existing one. When auto-resolve is
|
|
181
|
+
// enabled, the caller retires the old memory AFTER the new write lands
|
|
182
|
+
// and its tombstone status is known (#1645: defer auto-resolve until
|
|
183
|
+
// tombstone status is known — a tombstone-blocked new write must not
|
|
184
|
+
// retire the only active copy, leaving neither memory active). When
|
|
185
|
+
// disabled, the old memory stays active for manual review. The
|
|
186
|
+
// contradiction info is returned in both cases so the caller can apply
|
|
187
|
+
// the deferred retire post-write.
|
|
188
|
+
|
|
189
|
+
// Return the contradiction info regardless of auto-resolve setting.
|
|
190
|
+
// The caller uses this to set `contradictionDetected=true` which
|
|
191
|
+
// prevents the semantic-skip guard from silently dropping a
|
|
192
|
+
// legitimately contradictory update (the regression this fixes).
|
|
193
|
+
log.info(
|
|
194
|
+
`detected contradiction (confidence: ${verification.confidence}): ${existingMemory.frontmatter.id} vs new memory${config.contradictionAutoResolve ? " (auto-resolved)" : " (queued for manual review)"}`,
|
|
195
|
+
);
|
|
196
|
+
return {
|
|
197
|
+
supersededId: existingMemory.frontmatter.id,
|
|
198
|
+
confidence: verification.confidence,
|
|
199
|
+
reason: verification.reasoning,
|
|
200
|
+
supersededPath: existingMemory.path,
|
|
201
|
+
supersededCreated: existingMemory.frontmatter.created,
|
|
202
|
+
supersededTags: existingMemory.frontmatter.tags ?? [],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* #1645: Complete the deferred contradiction auto-resolve after writeMemory
|
|
212
|
+
* returns and the new write's tombstone status is known. Retires the old
|
|
213
|
+
* memory + deindexes it ONLY when the new write is genuinely active (not
|
|
214
|
+
* tombstone-blocked / pending_review). A blocked write must not retire the
|
|
215
|
+
* only active copy — deferring here closes the "contradictionAutoResolve
|
|
216
|
+
* supersedes before tombstone status is known" defect class.
|
|
217
|
+
*/
|
|
218
|
+
async applyDeferredContradictionResolve(
|
|
219
|
+
contradiction: ContradictionResolveTarget | null | undefined,
|
|
220
|
+
storage: StorageManager,
|
|
221
|
+
newMemoryId: string,
|
|
222
|
+
postWriteGuard: boolean,
|
|
223
|
+
): Promise<void> {
|
|
224
|
+
const config = this.getConfig();
|
|
225
|
+
// #1645 yG2: clear the pre-write `supersedes` link from a blocked row so
|
|
226
|
+
// it doesn't claim to supersede a still-active memory (best-effort).
|
|
227
|
+
if (postWriteGuard && contradiction && config.contradictionAutoResolve) {
|
|
228
|
+
try {
|
|
229
|
+
const blockedRow = await storage.getMemoryById(newMemoryId);
|
|
230
|
+
if (blockedRow?.frontmatter?.supersedes) {
|
|
231
|
+
await storage.writeMemoryFrontmatter(blockedRow, { supersedes: undefined });
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
log.warn(
|
|
235
|
+
`contradiction auto-resolve supersedes clear failed for blocked ${newMemoryId}: ${err}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (
|
|
240
|
+
!contradiction ||
|
|
241
|
+
!config.contradictionAutoResolve ||
|
|
242
|
+
postWriteGuard
|
|
243
|
+
) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
await storage.supersedeMemory(
|
|
248
|
+
contradiction.supersededId,
|
|
249
|
+
newMemoryId,
|
|
250
|
+
contradiction.reason,
|
|
251
|
+
);
|
|
252
|
+
if (
|
|
253
|
+
resolveIndexingCapabilities(config).queryAwareIndexing &&
|
|
254
|
+
contradiction.supersededPath
|
|
255
|
+
) {
|
|
256
|
+
deindexMemory(
|
|
257
|
+
config.memoryDir,
|
|
258
|
+
contradiction.supersededPath,
|
|
259
|
+
contradiction.supersededCreated,
|
|
260
|
+
contradiction.supersededTags,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
} catch (err) {
|
|
264
|
+
log.warn(
|
|
265
|
+
`contradiction auto-resolve supersede failed for ${contradiction.supersededId}: ${err}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Memory Linking (Phase 3A)
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Suggest links for a new memory based on similar existing memories.
|
|
276
|
+
*/
|
|
277
|
+
async suggestLinksForMemory(
|
|
278
|
+
content: string,
|
|
279
|
+
category: string,
|
|
280
|
+
namespaceScope: string,
|
|
281
|
+
): Promise<MemoryLink[]> {
|
|
282
|
+
if (!this.isSearchAvailable()) return [];
|
|
283
|
+
|
|
284
|
+
// Search for related memories
|
|
285
|
+
const results = await this.searchAcrossNamespaces({
|
|
286
|
+
query: content,
|
|
287
|
+
namespaces: [namespaceScope],
|
|
288
|
+
maxResults: 5,
|
|
289
|
+
mode: "search",
|
|
290
|
+
});
|
|
291
|
+
if (results.length === 0) return [];
|
|
292
|
+
|
|
293
|
+
// Get full memory details for candidates
|
|
294
|
+
const candidates: Array<{ id: string; content: string; category: string }> =
|
|
295
|
+
[];
|
|
296
|
+
for (const result of results) {
|
|
297
|
+
const memoryId = this.extractMemoryIdsFromResults([result])[0];
|
|
298
|
+
if (!memoryId) continue;
|
|
299
|
+
|
|
300
|
+
const resultNamespace = this.namespaceFromPath(result.path);
|
|
301
|
+
if (resultNamespace !== namespaceScope) continue;
|
|
302
|
+
const resultStorage = await this.storageForNamespace(resultNamespace);
|
|
303
|
+
const memory = await resultStorage.getMemoryById(memoryId);
|
|
304
|
+
if (
|
|
305
|
+
memory &&
|
|
306
|
+
memory.frontmatter.status !== "superseded" &&
|
|
307
|
+
memory.frontmatter.status !== "forgotten"
|
|
308
|
+
) {
|
|
309
|
+
candidates.push({
|
|
310
|
+
id: memory.frontmatter.id,
|
|
311
|
+
content: memory.content,
|
|
312
|
+
category: memory.frontmatter.category,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (candidates.length === 0) return [];
|
|
318
|
+
|
|
319
|
+
// Ask LLM for link suggestions
|
|
320
|
+
const extraction = this.getExtraction();
|
|
321
|
+
const suggestions = await extraction.suggestLinks(
|
|
322
|
+
{ content, category },
|
|
323
|
+
candidates,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (!suggestions || suggestions.links.length === 0) return [];
|
|
327
|
+
|
|
328
|
+
// Convert to MemoryLink format
|
|
329
|
+
return suggestions.links.map((link) => ({
|
|
330
|
+
targetId: link.targetId,
|
|
331
|
+
linkType: link.linkType,
|
|
332
|
+
strength: link.strength,
|
|
333
|
+
reason: link.reason || undefined,
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall section coordinator — extracted from the orchestrator (issue #1526).
|
|
3
|
+
*
|
|
4
|
+
* Owns the recall-output section-budgeting subsystem: section enablement
|
|
5
|
+
* lookups, per-section maxChars/number resolution, section bucket appends,
|
|
6
|
+
* budget-aware section truncation, and final ordered assembly of recall
|
|
7
|
+
* sections within the configured character budget. Also builds the
|
|
8
|
+
* LastRecallBudgetSummary and collects the sources used for X-ray tracing.
|
|
9
|
+
*
|
|
10
|
+
* Behavior-preserving move from orchestrator.ts — no logic changes; the
|
|
11
|
+
* orchestrator constructs one instance and keeps thin delegating methods so
|
|
12
|
+
* existing call sites (recallInternal, consolidation) continue to work.
|
|
13
|
+
*
|
|
14
|
+
* Config is accessed through a getter callback (not captured at construction)
|
|
15
|
+
* so that post-construction reassignment of the orchestrator's live
|
|
16
|
+
* `config` field is honored. This mirrors the ConversationIndexCoordinator /
|
|
17
|
+
* TierMigrationCoordinator accessor pattern.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { PluginConfig, RecallSectionConfig } from "../types.js";
|
|
21
|
+
import type { LastRecallBudgetSummary } from "../recall-state.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Coordinator for the recall section budgeting + assembly subsystem.
|
|
25
|
+
*
|
|
26
|
+
* All methods are pure with respect to the coordinator's own state — they
|
|
27
|
+
* read config via the getter and operate on the caller-provided section
|
|
28
|
+
* buckets. No caches are held because the config-derived values are cheap
|
|
29
|
+
* to recompute and the buckets are transient (per-recall-call).
|
|
30
|
+
*/
|
|
31
|
+
export class RecallSectionCoordinator {
|
|
32
|
+
private readonly getConfig: () => PluginConfig;
|
|
33
|
+
private readonly resolveSectionEnabled: (
|
|
34
|
+
sectionId: string,
|
|
35
|
+
defaultEnabled: boolean,
|
|
36
|
+
) => boolean;
|
|
37
|
+
|
|
38
|
+
constructor(options: {
|
|
39
|
+
getConfig: () => PluginConfig;
|
|
40
|
+
resolveSectionEnabled?: (
|
|
41
|
+
sectionId: string,
|
|
42
|
+
defaultEnabled: boolean,
|
|
43
|
+
) => boolean;
|
|
44
|
+
}) {
|
|
45
|
+
this.getConfig = options.getConfig;
|
|
46
|
+
// Default to the coordinator's own config-based check so standalone usage
|
|
47
|
+
// (e.g. config-recall-pipeline tests) keeps self-gating. The orchestrator
|
|
48
|
+
// injects its own resolver so appendRecallSection flows through the same
|
|
49
|
+
// overridable isRecallSectionEnabled that the recallInternal computation
|
|
50
|
+
// gates use — keeping compute-side and append-side enablement coherent
|
|
51
|
+
// (seam 13). This preserves the recall-hardening test seam where tests
|
|
52
|
+
// override orchestrator.isRecallSectionEnabled to isolate sections.
|
|
53
|
+
this.resolveSectionEnabled =
|
|
54
|
+
options.resolveSectionEnabled ??
|
|
55
|
+
((id, def) => this.isRecallSectionEnabled(id, def));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getRecallSectionEntry(
|
|
59
|
+
sectionId: string,
|
|
60
|
+
): RecallSectionConfig | undefined {
|
|
61
|
+
const pipeline = Array.isArray(this.getConfig().recallPipeline)
|
|
62
|
+
? this.getConfig().recallPipeline
|
|
63
|
+
: [];
|
|
64
|
+
return pipeline.find((entry) => entry.id === sectionId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isRecallSectionEnabled(
|
|
68
|
+
sectionId: string,
|
|
69
|
+
defaultEnabled: boolean = true,
|
|
70
|
+
): boolean {
|
|
71
|
+
const entry = this.getRecallSectionEntry(sectionId);
|
|
72
|
+
if (!entry) return defaultEnabled;
|
|
73
|
+
return entry.enabled !== false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
isSpecializedRecallSectionEnabled(
|
|
77
|
+
sectionId: string,
|
|
78
|
+
topLevelEnabled: boolean,
|
|
79
|
+
): boolean {
|
|
80
|
+
const entry = this.getRecallSectionEntry(sectionId);
|
|
81
|
+
if (!entry) return topLevelEnabled;
|
|
82
|
+
return entry.enabled === true || (topLevelEnabled && entry.enabled !== false);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getRecallSectionMaxChars(
|
|
86
|
+
sectionId: string,
|
|
87
|
+
): number | null | undefined {
|
|
88
|
+
const entry = this.getRecallSectionEntry(sectionId);
|
|
89
|
+
if (!entry) return undefined;
|
|
90
|
+
if (entry.maxChars === null) return null;
|
|
91
|
+
if (typeof entry.maxChars !== "number") return undefined;
|
|
92
|
+
return Math.max(0, Math.floor(entry.maxChars));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getRecallSectionNumber(
|
|
96
|
+
sectionId: string,
|
|
97
|
+
key: keyof RecallSectionConfig,
|
|
98
|
+
): number | undefined {
|
|
99
|
+
const entry = this.getRecallSectionEntry(sectionId);
|
|
100
|
+
if (!entry) return undefined;
|
|
101
|
+
const value = entry[key];
|
|
102
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
103
|
+
return Math.max(0, Math.floor(value));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
appendRecallSection(
|
|
107
|
+
sectionBuckets: Map<string, string[]>,
|
|
108
|
+
sectionId: string,
|
|
109
|
+
content: string,
|
|
110
|
+
): boolean {
|
|
111
|
+
// Returns true when the section was actually appended to sectionBuckets,
|
|
112
|
+
// false when it was dropped (disabled, empty, or maxChars===0). Callers
|
|
113
|
+
// that need to know whether injection occurred (e.g. xray annotation for
|
|
114
|
+
// peer-profile) must gate on this return value rather than on whether the
|
|
115
|
+
// section text was computed (Codex P2 finding, PR #764).
|
|
116
|
+
if (!this.resolveSectionEnabled(sectionId, true)) return false;
|
|
117
|
+
const trimmed = content.trim();
|
|
118
|
+
if (trimmed.length === 0) return false;
|
|
119
|
+
|
|
120
|
+
const maxChars = this.getRecallSectionMaxChars(sectionId);
|
|
121
|
+
let finalContent = trimmed;
|
|
122
|
+
if (maxChars === 0) return false;
|
|
123
|
+
if (typeof maxChars === "number" && finalContent.length > maxChars) {
|
|
124
|
+
finalContent = `${finalContent.slice(0, maxChars)}\n\n...(trimmed)\n`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const existing = sectionBuckets.get(sectionId) ?? [];
|
|
128
|
+
existing.push(finalContent);
|
|
129
|
+
sectionBuckets.set(sectionId, existing);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
truncateRecallSectionToBudget(
|
|
134
|
+
content: string,
|
|
135
|
+
maxChars: number,
|
|
136
|
+
): string {
|
|
137
|
+
if (maxChars <= 0) return "";
|
|
138
|
+
if (content.length <= maxChars) return content;
|
|
139
|
+
const suffix = "\n\n...(memory context trimmed)";
|
|
140
|
+
if (maxChars <= suffix.length) {
|
|
141
|
+
return content.slice(0, maxChars);
|
|
142
|
+
}
|
|
143
|
+
return `${content.slice(0, maxChars - suffix.length)}${suffix}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protectedRecallSectionIds(
|
|
147
|
+
sectionBuckets: Map<string, string[]>,
|
|
148
|
+
): Set<string> {
|
|
149
|
+
const protectedIds = new Set<string>();
|
|
150
|
+
if ((sectionBuckets.get("memories")?.length ?? 0) > 0) {
|
|
151
|
+
protectedIds.add("memories");
|
|
152
|
+
}
|
|
153
|
+
return protectedIds;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
protectedRecallReservationChars(content: string): number {
|
|
157
|
+
const headingBoundary = content.indexOf("\n\n");
|
|
158
|
+
const headingChars =
|
|
159
|
+
headingBoundary >= 0 ? headingBoundary + 2 : Math.min(content.length, 24);
|
|
160
|
+
return Math.min(content.length, Math.max(headingChars, 24));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
estimateReservedRecallBudget(
|
|
164
|
+
entries: Array<{ id: string; content: string }>,
|
|
165
|
+
startIndex: number,
|
|
166
|
+
protectedIds: Set<string>,
|
|
167
|
+
alreadyIncludedCount: number,
|
|
168
|
+
): number {
|
|
169
|
+
const separatorLength = "\n\n---\n\n".length;
|
|
170
|
+
let reserved = 0;
|
|
171
|
+
let simulatedIncluded = alreadyIncludedCount;
|
|
172
|
+
for (let i = startIndex; i < entries.length; i += 1) {
|
|
173
|
+
const entry = entries[i];
|
|
174
|
+
if (!entry || !protectedIds.has(entry.id)) continue;
|
|
175
|
+
if (simulatedIncluded > 0) {
|
|
176
|
+
reserved += separatorLength;
|
|
177
|
+
}
|
|
178
|
+
reserved += this.protectedRecallReservationChars(entry.content);
|
|
179
|
+
simulatedIncluded += 1;
|
|
180
|
+
}
|
|
181
|
+
return reserved;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getRecallBudgetChars(override?: number): number {
|
|
185
|
+
if (
|
|
186
|
+
typeof override === "number" &&
|
|
187
|
+
Number.isFinite(override) &&
|
|
188
|
+
override >= 0
|
|
189
|
+
) {
|
|
190
|
+
return Math.floor(override);
|
|
191
|
+
}
|
|
192
|
+
const configuredBudget = this.getConfig().recallBudgetChars;
|
|
193
|
+
if (
|
|
194
|
+
typeof configuredBudget === "number" &&
|
|
195
|
+
Number.isFinite(configuredBudget) &&
|
|
196
|
+
configuredBudget >= 0
|
|
197
|
+
) {
|
|
198
|
+
return Math.floor(configuredBudget);
|
|
199
|
+
}
|
|
200
|
+
const tokenBudget = this.getConfig().maxMemoryTokens;
|
|
201
|
+
if (
|
|
202
|
+
typeof tokenBudget === "number" &&
|
|
203
|
+
Number.isFinite(tokenBudget) &&
|
|
204
|
+
tokenBudget >= 0
|
|
205
|
+
) {
|
|
206
|
+
return Math.floor(tokenBudget * 4);
|
|
207
|
+
}
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
assembleRecallSections(
|
|
212
|
+
sectionBuckets: Map<string, string[]>,
|
|
213
|
+
budgetOverride?: number,
|
|
214
|
+
): {
|
|
215
|
+
sections: string[];
|
|
216
|
+
includedIds: string[];
|
|
217
|
+
omittedIds: string[];
|
|
218
|
+
truncated: boolean;
|
|
219
|
+
finalChars: number;
|
|
220
|
+
} {
|
|
221
|
+
const orderedEntries: Array<{ id: string; content: string }> = [];
|
|
222
|
+
const pipeline = Array.isArray(this.getConfig().recallPipeline)
|
|
223
|
+
? this.getConfig().recallPipeline
|
|
224
|
+
: [];
|
|
225
|
+
const orderedIds = pipeline
|
|
226
|
+
.filter((entry) => entry.enabled !== false)
|
|
227
|
+
.map((entry) => entry.id);
|
|
228
|
+
const seen = new Set<string>();
|
|
229
|
+
|
|
230
|
+
for (const id of orderedIds) {
|
|
231
|
+
const chunks = sectionBuckets.get(id);
|
|
232
|
+
if (!chunks || chunks.length === 0) continue;
|
|
233
|
+
orderedEntries.push({ id, content: chunks.join("\n\n") });
|
|
234
|
+
seen.add(id);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const [id, chunks] of sectionBuckets.entries()) {
|
|
238
|
+
if (seen.has(id)) continue;
|
|
239
|
+
if (chunks.length === 0) continue;
|
|
240
|
+
orderedEntries.push({ id, content: chunks.join("\n\n") });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const budget = this.getRecallBudgetChars(budgetOverride);
|
|
244
|
+
if (budget === 0) {
|
|
245
|
+
return {
|
|
246
|
+
sections: [],
|
|
247
|
+
includedIds: [],
|
|
248
|
+
omittedIds: orderedEntries.map((entry) => entry.id),
|
|
249
|
+
truncated: orderedEntries.length > 0,
|
|
250
|
+
finalChars: 0,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const separator = "\n\n---\n\n";
|
|
255
|
+
const protectedIds = this.protectedRecallSectionIds(sectionBuckets);
|
|
256
|
+
const sections: string[] = [];
|
|
257
|
+
const includedIds: string[] = [];
|
|
258
|
+
const omittedIds: string[] = [];
|
|
259
|
+
let usedChars = 0;
|
|
260
|
+
let truncated = false;
|
|
261
|
+
|
|
262
|
+
for (let index = 0; index < orderedEntries.length; index += 1) {
|
|
263
|
+
const entry = orderedEntries[index]!;
|
|
264
|
+
const separatorChars = sections.length > 0 ? separator.length : 0;
|
|
265
|
+
const reserve = protectedIds.has(entry.id)
|
|
266
|
+
? 0
|
|
267
|
+
: this.estimateReservedRecallBudget(
|
|
268
|
+
orderedEntries,
|
|
269
|
+
index + 1,
|
|
270
|
+
protectedIds,
|
|
271
|
+
sections.length + 1,
|
|
272
|
+
);
|
|
273
|
+
const availableForEntry = budget - usedChars - separatorChars - reserve;
|
|
274
|
+
if (availableForEntry <= 0) {
|
|
275
|
+
omittedIds.push(entry.id);
|
|
276
|
+
truncated = true;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const finalContent = this.truncateRecallSectionToBudget(
|
|
280
|
+
entry.content,
|
|
281
|
+
availableForEntry,
|
|
282
|
+
);
|
|
283
|
+
if (!finalContent) {
|
|
284
|
+
omittedIds.push(entry.id);
|
|
285
|
+
truncated = true;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (finalContent.length < entry.content.length) {
|
|
289
|
+
truncated = true;
|
|
290
|
+
}
|
|
291
|
+
sections.push(finalContent);
|
|
292
|
+
includedIds.push(entry.id);
|
|
293
|
+
usedChars += separatorChars + finalContent.length;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
sections,
|
|
298
|
+
includedIds,
|
|
299
|
+
omittedIds,
|
|
300
|
+
truncated,
|
|
301
|
+
finalChars: usedChars,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
buildLastRecallBudgetSummary(options: {
|
|
306
|
+
requestedTopK?: number;
|
|
307
|
+
recallResultLimit: number;
|
|
308
|
+
qmdFetchLimit: number;
|
|
309
|
+
qmdHybridFetchLimit: number;
|
|
310
|
+
finalContextChars?: number;
|
|
311
|
+
truncated?: boolean;
|
|
312
|
+
includedSections?: string[];
|
|
313
|
+
omittedSections?: string[];
|
|
314
|
+
}): LastRecallBudgetSummary {
|
|
315
|
+
return {
|
|
316
|
+
requestedTopK: options.requestedTopK,
|
|
317
|
+
appliedTopK: options.recallResultLimit,
|
|
318
|
+
recallBudgetChars: this.getRecallBudgetChars(),
|
|
319
|
+
maxMemoryTokens: this.getConfig().maxMemoryTokens,
|
|
320
|
+
qmdFetchLimit: options.qmdFetchLimit,
|
|
321
|
+
qmdHybridFetchLimit: options.qmdHybridFetchLimit,
|
|
322
|
+
finalContextChars: options.finalContextChars,
|
|
323
|
+
truncated: options.truncated,
|
|
324
|
+
includedSections: [...(options.includedSections ?? [])],
|
|
325
|
+
omittedSections: [...(options.omittedSections ?? [])],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
collectLastRecallSources(
|
|
330
|
+
sectionBuckets: Map<string, string[]>,
|
|
331
|
+
recallSource:
|
|
332
|
+
| "none"
|
|
333
|
+
| "hot_qmd"
|
|
334
|
+
| "hot_embedding"
|
|
335
|
+
| "cold_fallback"
|
|
336
|
+
| "recent_scan",
|
|
337
|
+
): string[] {
|
|
338
|
+
const used = new Set<string>();
|
|
339
|
+
if (recallSource !== "none") {
|
|
340
|
+
used.add(recallSource);
|
|
341
|
+
}
|
|
342
|
+
for (const [sectionId, chunks] of sectionBuckets.entries()) {
|
|
343
|
+
if (chunks.length > 0) {
|
|
344
|
+
used.add(sectionId);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return [...used];
|
|
348
|
+
}
|
|
349
|
+
}
|