@remnic/core 9.25.6 → 9.26.0
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 +2 -2
- package/dist/access-admin-ops-surface.js +41 -41
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +2 -2
- package/dist/access-authorization-probe.js +42 -42
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +41 -41
- package/dist/access-cli.js +98 -97
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http-lcm-compaction.d.ts +130 -0
- package/dist/access-http-lcm-compaction.js +10 -0
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +53 -52
- package/dist/access-identity-continuity-surface.d.ts +1 -1
- package/dist/access-identity-continuity-surface.js +41 -41
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +41 -41
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +47 -47
- package/dist/access-namespace-preflight.js +41 -41
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +41 -41
- package/dist/access-operations-batch.js +43 -43
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +45 -45
- package/dist/access-recall-concurrency.d.ts +2 -2
- package/dist/access-recall-concurrency.js +41 -41
- package/dist/access-recall-response.d.ts +2 -2
- package/dist/access-recall-response.js +41 -41
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +41 -41
- package/dist/access-schema.d.ts +16 -1
- package/dist/access-schema.js +8 -8
- package/dist/{access-service-BJQ22URi.d.ts → access-service-DL8FJnTj.d.ts} +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +41 -41
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-5OE5QOZB.js → auto-sync-UPTUVE4X.js} +3 -3
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
- package/dist/{chunk-BL4JHZSO.js → chunk-2XP3PWL3.js} +40 -40
- package/dist/{chunk-AQ2ZQYDI.js → chunk-4KKV7W62.js} +6 -6
- package/dist/chunk-4Z4XNEWZ.js +86 -0
- package/dist/chunk-4Z4XNEWZ.js.map +1 -0
- package/dist/{chunk-32XTXKIA.js → chunk-67DSDRUB.js} +22 -14
- package/dist/chunk-67DSDRUB.js.map +1 -0
- package/dist/{chunk-2PHX3I5C.js → chunk-6NNNURLK.js} +8 -8
- package/dist/{chunk-BLAKS3CP.js → chunk-77CNV7DY.js} +4 -4
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-WIGBTWXA.js → chunk-7XXCIO6X.js} +2 -2
- package/dist/{chunk-7CPOZH6Y.js → chunk-AJ7QCSIS.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-UMFLAACL.js → chunk-E6EAB4J6.js} +9 -9
- package/dist/{chunk-WSEA52RI.js → chunk-EXM2Q555.js} +2 -2
- package/dist/{chunk-G6MGAE36.js → chunk-F6LSNPPB.js} +5 -5
- package/dist/{chunk-7KXQ44PA.js → chunk-FE5FKTCI.js} +5 -5
- package/dist/{chunk-5J7HJ7RT.js → chunk-H2PBM562.js} +2 -2
- package/dist/{chunk-OIE242WM.js → chunk-HBVOJNV4.js} +3 -3
- package/dist/{chunk-PS5OG75R.js → chunk-IHMXPG4P.js} +2 -2
- package/dist/{chunk-3UOSVTFR.js → chunk-JCVQV6QX.js} +11 -11
- package/dist/{chunk-P25IPRXP.js → chunk-KGTD2KZJ.js} +28 -8
- package/dist/chunk-KGTD2KZJ.js.map +1 -0
- package/dist/{chunk-NY6W6CJW.js → chunk-KPWLZBXZ.js} +2 -2
- package/dist/{chunk-W33DEUKR.js → chunk-LIIQPDFN.js} +2 -2
- package/dist/{chunk-D7MAWMMK.js → chunk-M2KGXXTL.js} +2 -2
- package/dist/{chunk-UOURUMBN.js → chunk-MIFF37NN.js} +2 -2
- package/dist/{chunk-37VPMR4E.js → chunk-NKDVXEFU.js} +6 -6
- package/dist/{chunk-ZPUFQGUF.js → chunk-PQPSZICJ.js} +5 -5
- package/dist/{chunk-ES36HTSC.js → chunk-PTN6UAUA.js} +4 -4
- package/dist/{chunk-FR75B4UU.js → chunk-PX7VDW7R.js} +408 -721
- package/dist/chunk-PX7VDW7R.js.map +1 -0
- package/dist/{chunk-S2BBISHU.js → chunk-RYHWZ52A.js} +4 -4
- package/dist/{chunk-ZN4UZWMT.js → chunk-SK3EYPKC.js} +2 -2
- package/dist/{chunk-TPYZ727G.js → chunk-SN3ZESWQ.js} +6 -6
- package/dist/{chunk-BUK2FXOL.js → chunk-TKBDJTXG.js} +4 -4
- package/dist/{chunk-4B77XSG5.js → chunk-UV72DEZD.js} +5 -5
- package/dist/{chunk-K4DP5KZA.js → chunk-UZK7VVPD.js} +14 -14
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-SSYBI43M.js → chunk-VGEH3STZ.js} +6 -6
- package/dist/{chunk-NRV4Y3PM.js → chunk-W4FBGYGK.js} +1 -1
- package/dist/{chunk-IR4LGIH6.js → chunk-WMDRLTNA.js} +2 -2
- package/dist/{chunk-MU7ZHSZE.js → chunk-WZFAPHIK.js} +2 -2
- package/dist/{chunk-JA4HEXV7.js → chunk-XAAIUXJO.js} +2 -2
- package/dist/{chunk-VPSAVXHM.js → chunk-XNUBF6RF.js} +7 -7
- package/dist/{chunk-3CHQZVAL.js → chunk-Y6E37WRG.js} +5 -5
- package/dist/{chunk-E5A6C4A5.js → chunk-YN4AUGNF.js} +48 -48
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-7IGWPXVC.js → chunk-ZHP4SUPB.js} +2 -2
- package/dist/{cli-1W7cNjJJ.d.ts → cli-BoouX6LX.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +75 -74
- package/dist/compounding/engine.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +16 -16
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/{first-start-migration-BKNGOFED.js → first-start-migration-LEXL3XYL.js} +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +177 -176
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +9 -9
- package/dist/maintenance/memory-governance.js +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/migrate.js +15 -15
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +5 -5
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-toolkit.js +28 -28
- package/dist/orchestration/maintenance.js +11 -11
- package/dist/{orchestrator-BtA1wC5K.d.ts → orchestrator-CFJ_TG6D.d.ts} +15 -10
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +98 -97
- package/dist/resume-bundles.js +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +6 -6
- package/dist/session-namespace-bindings.d.ts +27 -0
- package/dist/session-namespace-bindings.js +243 -0
- package/dist/session-namespace-bindings.js.map +1 -0
- package/dist/storage.js +4 -4
- package/dist/testing/lifecycle-matrix.d.ts +100 -0
- package/dist/testing/lifecycle-matrix.js +149 -0
- package/dist/testing/lifecycle-matrix.js.map +1 -0
- package/dist/transfer/backup.js +4 -4
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/verified-recall.js +6 -6
- package/package.json +22 -2
- package/src/access-http-lcm-compaction.ts +116 -0
- package/src/access-http.test.ts +218 -0
- package/src/access-http.ts +8 -8
- package/src/access-schema-pi.test.ts +9 -0
- package/src/access-schema.ts +29 -4
- package/src/access-surface-catalog.test.ts +1 -0
- package/src/meetings/recall-exclusion.test.ts +4 -2
- package/src/orchestration/generic-recall-paths.ts +119 -0
- package/src/orchestration/namespace-read-fanout.ts +0 -12
- package/src/orchestration/orchestrator-helpers.ts +0 -35
- package/src/orchestration/recall-internal.ts +6 -6
- package/src/orchestration/recall-search-pipeline.test.ts +194 -28
- package/src/orchestration/recall-search-pipeline.ts +39 -89
- package/src/orchestrator.ts +5 -17
- package/src/recall-cold-deadline.test.ts +11 -13
- package/src/session-namespace-bindings.test.ts +358 -0
- package/src/session-namespace-bindings.ts +301 -0
- package/src/testing/lifecycle-matrix.ts +17 -7
- package/src/testing/subjects/generic-recall-paths.test.ts +157 -0
- package/dist/chunk-32XTXKIA.js.map +0 -1
- package/dist/chunk-FR75B4UU.js.map +0 -1
- package/dist/chunk-P25IPRXP.js.map +0 -1
- package/src/recall/archive-scoring.ts +0 -544
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-http-lcm-compaction.js.map} +0 -0
- /package/dist/{auto-sync-5OE5QOZB.js.map → auto-sync-UPTUVE4X.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-BL4JHZSO.js.map → chunk-2XP3PWL3.js.map} +0 -0
- /package/dist/{chunk-AQ2ZQYDI.js.map → chunk-4KKV7W62.js.map} +0 -0
- /package/dist/{chunk-2PHX3I5C.js.map → chunk-6NNNURLK.js.map} +0 -0
- /package/dist/{chunk-BLAKS3CP.js.map → chunk-77CNV7DY.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-WIGBTWXA.js.map → chunk-7XXCIO6X.js.map} +0 -0
- /package/dist/{chunk-7CPOZH6Y.js.map → chunk-AJ7QCSIS.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-UMFLAACL.js.map → chunk-E6EAB4J6.js.map} +0 -0
- /package/dist/{chunk-WSEA52RI.js.map → chunk-EXM2Q555.js.map} +0 -0
- /package/dist/{chunk-G6MGAE36.js.map → chunk-F6LSNPPB.js.map} +0 -0
- /package/dist/{chunk-7KXQ44PA.js.map → chunk-FE5FKTCI.js.map} +0 -0
- /package/dist/{chunk-5J7HJ7RT.js.map → chunk-H2PBM562.js.map} +0 -0
- /package/dist/{chunk-OIE242WM.js.map → chunk-HBVOJNV4.js.map} +0 -0
- /package/dist/{chunk-PS5OG75R.js.map → chunk-IHMXPG4P.js.map} +0 -0
- /package/dist/{chunk-3UOSVTFR.js.map → chunk-JCVQV6QX.js.map} +0 -0
- /package/dist/{chunk-NY6W6CJW.js.map → chunk-KPWLZBXZ.js.map} +0 -0
- /package/dist/{chunk-W33DEUKR.js.map → chunk-LIIQPDFN.js.map} +0 -0
- /package/dist/{chunk-D7MAWMMK.js.map → chunk-M2KGXXTL.js.map} +0 -0
- /package/dist/{chunk-UOURUMBN.js.map → chunk-MIFF37NN.js.map} +0 -0
- /package/dist/{chunk-37VPMR4E.js.map → chunk-NKDVXEFU.js.map} +0 -0
- /package/dist/{chunk-ZPUFQGUF.js.map → chunk-PQPSZICJ.js.map} +0 -0
- /package/dist/{chunk-ES36HTSC.js.map → chunk-PTN6UAUA.js.map} +0 -0
- /package/dist/{chunk-S2BBISHU.js.map → chunk-RYHWZ52A.js.map} +0 -0
- /package/dist/{chunk-ZN4UZWMT.js.map → chunk-SK3EYPKC.js.map} +0 -0
- /package/dist/{chunk-TPYZ727G.js.map → chunk-SN3ZESWQ.js.map} +0 -0
- /package/dist/{chunk-BUK2FXOL.js.map → chunk-TKBDJTXG.js.map} +0 -0
- /package/dist/{chunk-4B77XSG5.js.map → chunk-UV72DEZD.js.map} +0 -0
- /package/dist/{chunk-K4DP5KZA.js.map → chunk-UZK7VVPD.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-SSYBI43M.js.map → chunk-VGEH3STZ.js.map} +0 -0
- /package/dist/{chunk-NRV4Y3PM.js.map → chunk-W4FBGYGK.js.map} +0 -0
- /package/dist/{chunk-IR4LGIH6.js.map → chunk-WMDRLTNA.js.map} +0 -0
- /package/dist/{chunk-MU7ZHSZE.js.map → chunk-WZFAPHIK.js.map} +0 -0
- /package/dist/{chunk-JA4HEXV7.js.map → chunk-XAAIUXJO.js.map} +0 -0
- /package/dist/{chunk-VPSAVXHM.js.map → chunk-XNUBF6RF.js.map} +0 -0
- /package/dist/{chunk-3CHQZVAL.js.map → chunk-Y6E37WRG.js.map} +0 -0
- /package/dist/{chunk-E5A6C4A5.js.map → chunk-YN4AUGNF.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-7IGWPXVC.js.map → chunk-ZHP4SUPB.js.map} +0 -0
- /package/dist/{first-start-migration-BKNGOFED.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { QmdSearchResult } from "../types.js";
|
|
3
|
+
import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
4
|
+
import {
|
|
5
|
+
isActivityDigestPath,
|
|
6
|
+
isArtifactMemoryPath,
|
|
7
|
+
isMeetingRecordPath,
|
|
8
|
+
} from "./orchestrator-helpers.js";
|
|
9
|
+
|
|
10
|
+
export interface GenericRecallPathPolicy {
|
|
11
|
+
readonly memoryDir?: string;
|
|
12
|
+
readonly qmdCollection?: string;
|
|
13
|
+
readonly qmdColdCollection?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type GenericRecallPathSource = "filesystem" | "qmd";
|
|
17
|
+
|
|
18
|
+
const CATEGORY_MEMORY_ROOTS: Record<string, true> = Object.fromEntries(
|
|
19
|
+
ALL_CATEGORY_DIRS.map((directory) => [directory, true]),
|
|
20
|
+
);
|
|
21
|
+
const RESERVED_ARCHIVE_ROOTS: Record<string, true> = { archive: true, namespaces: true };
|
|
22
|
+
|
|
23
|
+
function isQmdCollectionPrefix(
|
|
24
|
+
prefix: string,
|
|
25
|
+
collection: string | undefined,
|
|
26
|
+
source: GenericRecallPathSource,
|
|
27
|
+
): boolean {
|
|
28
|
+
if (typeof collection !== "string" || collection.length === 0) return false;
|
|
29
|
+
if (prefix.startsWith(`${collection}--`)) return true;
|
|
30
|
+
if (prefix !== collection) return false;
|
|
31
|
+
if (source === "qmd") return true;
|
|
32
|
+
return (
|
|
33
|
+
!Object.hasOwn(CATEGORY_MEMORY_ROOTS, prefix) &&
|
|
34
|
+
!Object.hasOwn(RESERVED_ARCHIVE_ROOTS, prefix)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeQmdUriPath(relativePath: string, source: GenericRecallPathSource): string {
|
|
39
|
+
if (source !== "qmd" || !relativePath.startsWith("qmd://")) return relativePath;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = new URL(relativePath);
|
|
42
|
+
if (parsed.protocol !== "qmd:" || !parsed.hostname) return relativePath;
|
|
43
|
+
const collection = decodeURIComponent(parsed.hostname);
|
|
44
|
+
const pathname = decodeURIComponent(parsed.pathname.replace(/^\/+/, ""));
|
|
45
|
+
return pathname ? `${collection}/${pathname}` : collection;
|
|
46
|
+
} catch {
|
|
47
|
+
return relativePath;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stripQmdCollectionPrefix(
|
|
52
|
+
relativePath: string,
|
|
53
|
+
policy: GenericRecallPathPolicy,
|
|
54
|
+
source: GenericRecallPathSource,
|
|
55
|
+
): string {
|
|
56
|
+
const normalized = path.posix.normalize(normalizeQmdUriPath(relativePath, source).replace(/\\/g, "/"));
|
|
57
|
+
const slashIndex = normalized.indexOf("/");
|
|
58
|
+
if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return normalized;
|
|
59
|
+
|
|
60
|
+
const prefix = normalized.slice(0, slashIndex);
|
|
61
|
+
const matchesHotCollection = isQmdCollectionPrefix(prefix, policy.qmdCollection, source);
|
|
62
|
+
const matchesColdCollection = isQmdCollectionPrefix(prefix, policy.qmdColdCollection, source);
|
|
63
|
+
return matchesHotCollection || matchesColdCollection
|
|
64
|
+
? normalized.slice(slashIndex + 1)
|
|
65
|
+
: normalized;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function isTopLevelArchivePath(
|
|
69
|
+
filePath: string,
|
|
70
|
+
policy: GenericRecallPathPolicy = {},
|
|
71
|
+
source: GenericRecallPathSource = "filesystem",
|
|
72
|
+
): boolean {
|
|
73
|
+
const isAbsolutePath = path.isAbsolute(filePath);
|
|
74
|
+
const relative =
|
|
75
|
+
policy.memoryDir && isAbsolutePath
|
|
76
|
+
? path.relative(policy.memoryDir, path.resolve(filePath))
|
|
77
|
+
: filePath;
|
|
78
|
+
const normalized = !isAbsolutePath
|
|
79
|
+
? stripQmdCollectionPrefix(relative, policy, source)
|
|
80
|
+
: relative;
|
|
81
|
+
return /^(?:archive|namespaces[\\/][^\\/]+[\\/]archive)(?:[\\/]|$)/i.test(normalized);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isGenericRecallExcludedPath(
|
|
85
|
+
filePath: string,
|
|
86
|
+
policy: GenericRecallPathPolicy = {},
|
|
87
|
+
source: GenericRecallPathSource = "filesystem",
|
|
88
|
+
): boolean {
|
|
89
|
+
return (
|
|
90
|
+
isArtifactMemoryPath(filePath) ||
|
|
91
|
+
isActivityDigestPath(filePath, policy.memoryDir) ||
|
|
92
|
+
isTopLevelArchivePath(filePath, policy, source) ||
|
|
93
|
+
isMeetingRecordPath(filePath)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function filterRecallCandidates(
|
|
98
|
+
candidates: QmdSearchResult[],
|
|
99
|
+
options: {
|
|
100
|
+
namespacesEnabled: boolean;
|
|
101
|
+
recallNamespaces: string[];
|
|
102
|
+
resolveNamespace: (path: string) => string;
|
|
103
|
+
limit: number;
|
|
104
|
+
pathPolicy?: GenericRecallPathPolicy;
|
|
105
|
+
memoryRoot?: string;
|
|
106
|
+
},
|
|
107
|
+
): QmdSearchResult[] {
|
|
108
|
+
const pathPolicy =
|
|
109
|
+
options.pathPolicy ??
|
|
110
|
+
(options.memoryRoot === undefined ? undefined : { memoryDir: options.memoryRoot });
|
|
111
|
+
const scopedByNamespace = options.namespacesEnabled
|
|
112
|
+
? candidates.filter((r) =>
|
|
113
|
+
options.recallNamespaces.includes(r.namespace ?? options.resolveNamespace(r.path)),
|
|
114
|
+
)
|
|
115
|
+
: candidates;
|
|
116
|
+
return scopedByNamespace
|
|
117
|
+
.filter((r) => !isGenericRecallExcludedPath(r.path, pathPolicy, "qmd"))
|
|
118
|
+
.slice(0, Math.max(0, options.limit));
|
|
119
|
+
}
|
|
@@ -475,16 +475,4 @@ export class NamespaceReadFanoutCoordinator {
|
|
|
475
475
|
return lists.flat();
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
async readArchivedMemoriesForNamespaces(
|
|
479
|
-
namespaces: string[],
|
|
480
|
-
): Promise<MemoryFile[]> {
|
|
481
|
-
const uniq = Array.from(new Set(namespaces.filter(Boolean)));
|
|
482
|
-
const lists = await Promise.all(
|
|
483
|
-
uniq.map(async (ns) => {
|
|
484
|
-
const sm = await this.deps.storageRouter.storageFor(ns);
|
|
485
|
-
return sm.readArchivedMemories();
|
|
486
|
-
}),
|
|
487
|
-
);
|
|
488
|
-
return lists.flat();
|
|
489
|
-
}
|
|
490
478
|
}
|
|
@@ -641,21 +641,6 @@ export function isActivityDigestPath(filePath: string, memoryRoot?: string): boo
|
|
|
641
641
|
return ACTIVITY_DIGEST_ANYWHERE.test(filePath);
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
/**
|
|
645
|
-
* Paths that dedicated surfaces own and generic recall must never inject:
|
|
646
|
-
* artifacts, activity digests (issue #1899), and meeting records (issue #1900).
|
|
647
|
-
* One predicate shared by every recall filter site so the exclusion cannot drift.
|
|
648
|
-
* Explicit search paths (memory_search, activity search) do not apply this
|
|
649
|
-
* filter, so those surfaces still read them.
|
|
650
|
-
*/
|
|
651
|
-
export function isGenericRecallExcludedPath(filePath: string, memoryRoot?: string): boolean {
|
|
652
|
-
return (
|
|
653
|
-
isArtifactMemoryPath(filePath) ||
|
|
654
|
-
isActivityDigestPath(filePath, memoryRoot) ||
|
|
655
|
-
isMeetingRecordPath(filePath)
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
644
|
export function buildCompressionGuidelinesMarkdown(
|
|
660
645
|
events: MemoryActionEvent[],
|
|
661
646
|
generatedAtIso: string = new Date().toISOString(),
|
|
@@ -663,26 +648,6 @@ export function buildCompressionGuidelinesMarkdown(
|
|
|
663
648
|
return buildCompressionGuidelinesMarkdownV2(events, generatedAtIso);
|
|
664
649
|
}
|
|
665
650
|
|
|
666
|
-
export function filterRecallCandidates(
|
|
667
|
-
candidates: QmdSearchResult[],
|
|
668
|
-
options: {
|
|
669
|
-
namespacesEnabled: boolean;
|
|
670
|
-
recallNamespaces: string[];
|
|
671
|
-
resolveNamespace: (path: string) => string;
|
|
672
|
-
limit: number;
|
|
673
|
-
/** Memory root for top-level activity-digest detection. */
|
|
674
|
-
memoryRoot?: string;
|
|
675
|
-
},
|
|
676
|
-
): QmdSearchResult[] {
|
|
677
|
-
const scopedByNamespace = options.namespacesEnabled
|
|
678
|
-
? candidates.filter((r) =>
|
|
679
|
-
options.recallNamespaces.includes(r.namespace ?? options.resolveNamespace(r.path)),
|
|
680
|
-
)
|
|
681
|
-
: candidates;
|
|
682
|
-
return scopedByNamespace
|
|
683
|
-
.filter((r) => !isGenericRecallExcludedPath(r.path, options.memoryRoot))
|
|
684
|
-
.slice(0, Math.max(0, options.limit));
|
|
685
|
-
}
|
|
686
651
|
|
|
687
652
|
export function applyQueryAwareCandidateFilter(
|
|
688
653
|
candidates: QmdSearchResult[],
|
|
@@ -100,7 +100,7 @@ import {
|
|
|
100
100
|
type QueryAwarePrefilter,
|
|
101
101
|
type RecallInvocationOptions,
|
|
102
102
|
} from "../orchestrator.js";
|
|
103
|
-
import { isGenericRecallExcludedPath } from "./
|
|
103
|
+
import { isGenericRecallExcludedPath } from "./generic-recall-paths.js";
|
|
104
104
|
|
|
105
105
|
import type {
|
|
106
106
|
RecallSectionAppendOptions,
|
|
@@ -4165,8 +4165,7 @@ export class RecallInternalCoordinator {
|
|
|
4165
4165
|
memoryResults = memoryResults.filter((r) =>
|
|
4166
4166
|
recallNamespaces.includes(r.namespace ?? this.deps.namespaceFromPath(r.path)));
|
|
4167
4167
|
}
|
|
4168
|
-
|
|
4169
|
-
memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config.memoryDir));
|
|
4168
|
+
memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd"));
|
|
4170
4169
|
|
|
4171
4170
|
const isFullModeGraphAssist =
|
|
4172
4171
|
graphCaps.multiGraphMemory &&
|
|
@@ -4297,6 +4296,7 @@ export class RecallInternalCoordinator {
|
|
|
4297
4296
|
}
|
|
4298
4297
|
}
|
|
4299
4298
|
}
|
|
4299
|
+
memoryResults = memoryResults.filter((r) => !isGenericRecallExcludedPath(r.path, this.deps.config, "qmd"));
|
|
4300
4300
|
|
|
4301
4301
|
// Apply mandatory recall safety filters before deadline-bound scoring
|
|
4302
4302
|
// enrichment. If scoring times out, we must fall back to this filtered
|
|
@@ -4550,7 +4550,7 @@ export class RecallInternalCoordinator {
|
|
|
4550
4550
|
recallNamespaces,
|
|
4551
4551
|
resolveNamespace: (p) => this.deps.namespaceFromPath(p),
|
|
4552
4552
|
limit: embeddingFetchLimit,
|
|
4553
|
-
|
|
4553
|
+
pathPolicy: this.deps.config,
|
|
4554
4554
|
},
|
|
4555
4555
|
);
|
|
4556
4556
|
const boostedScoped = await this.deps.boostSearchResults(
|
|
@@ -4745,7 +4745,7 @@ export class RecallInternalCoordinator {
|
|
|
4745
4745
|
recallNamespaces,
|
|
4746
4746
|
resolveNamespace: (p) => this.deps.namespaceFromPath(p),
|
|
4747
4747
|
limit: embeddingFetchLimit,
|
|
4748
|
-
|
|
4748
|
+
pathPolicy: this.deps.config,
|
|
4749
4749
|
},
|
|
4750
4750
|
);
|
|
4751
4751
|
const boostedScoped = await this.deps.boostSearchResults(
|
|
@@ -4868,7 +4868,7 @@ export class RecallInternalCoordinator {
|
|
|
4868
4868
|
typeof asOfMs === "number" && Number.isFinite(asOfMs);
|
|
4869
4869
|
const activeMemories = memories.filter(
|
|
4870
4870
|
(m) => {
|
|
4871
|
-
if (isGenericRecallExcludedPath(m.path, this.deps.config
|
|
4871
|
+
if (isGenericRecallExcludedPath(m.path, this.deps.config)) return false;
|
|
4872
4872
|
const status = m.frontmatter.status;
|
|
4873
4873
|
if (!status || status === "active") return true;
|
|
4874
4874
|
if (status === "superseded") {
|
|
@@ -78,13 +78,12 @@ function scopedCandidatesStub(
|
|
|
78
78
|
seed.filter((r) => candidatePaths.has(r.path)).slice(0, Math.max(0, limit));
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
function queryAwareFallbackCoordinator(
|
|
82
|
+
seed: QmdSearchResult[],
|
|
83
|
+
): RecallSearchPipelineCoordinator {
|
|
84
84
|
const namespaceFromPath: RecallSearchPipelineDeps["namespaceFromPath"] = () => "default";
|
|
85
85
|
const deps = {
|
|
86
86
|
searchScopedMemoryCandidates: scopedCandidatesStub(seed),
|
|
87
|
-
readArchivedMemoriesForNamespaces,
|
|
88
87
|
namespaceFromPath,
|
|
89
88
|
config: { memoryDir: "/mem" },
|
|
90
89
|
} as unknown as RecallSearchPipelineDeps;
|
|
@@ -107,48 +106,85 @@ const SEED_PATHS = [MEETING_RECORD, "facts/a.md", "artifacts/y.md"];
|
|
|
107
106
|
const SEED_WITH_NON_RECALLABLE = SEED_PATHS.map((p) => result("default", p));
|
|
108
107
|
const PREFILTER = prefilter(SEED_PATHS);
|
|
109
108
|
|
|
110
|
-
test("
|
|
111
|
-
const coordinator =
|
|
112
|
-
|
|
113
|
-
// `return scopedSeedResults` early path at the archived-empty guard.
|
|
114
|
-
const out = await coordinator.searchLongTermArchiveFallback("quarterly report", ["default"], 10, PREFILTER);
|
|
109
|
+
test("query-aware fallback filters non-recallable scoped candidates", async () => {
|
|
110
|
+
const coordinator = queryAwareFallbackCoordinator(SEED_WITH_NON_RECALLABLE);
|
|
111
|
+
const out = await coordinator.searchQueryAwareFallback("quarterly report", 10, PREFILTER);
|
|
115
112
|
assert.deepEqual(
|
|
116
113
|
out.map((r) => r.path),
|
|
117
114
|
["facts/a.md"],
|
|
118
|
-
"meeting
|
|
115
|
+
"meeting records and artifacts must not leak through query-aware fallback",
|
|
119
116
|
);
|
|
120
117
|
});
|
|
121
118
|
|
|
122
|
-
test("
|
|
123
|
-
const coordinator =
|
|
124
|
-
|
|
125
|
-
// before archive scoring is even attempted.
|
|
126
|
-
const out = await coordinator.searchLongTermArchiveFallback("", ["default"], 10, PREFILTER);
|
|
119
|
+
test("query-aware fallback filters non-recallable scoped candidates for an empty query", async () => {
|
|
120
|
+
const coordinator = queryAwareFallbackCoordinator(SEED_WITH_NON_RECALLABLE);
|
|
121
|
+
const out = await coordinator.searchQueryAwareFallback("", 10, PREFILTER);
|
|
127
122
|
assert.deepEqual(out.map((r) => r.path), ["facts/a.md"]);
|
|
128
123
|
});
|
|
129
124
|
|
|
130
|
-
test("
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// not early-return that hit and (archive empty) yields nothing.
|
|
134
|
-
const coordinator = fallbackCoordinator([result("default", MEETING_RECORD)], []);
|
|
135
|
-
const out = await coordinator.searchLongTermArchiveFallback("quarterly", ["default"], 1, prefilter([MEETING_RECORD]));
|
|
125
|
+
test("query-aware fallback applies the cap after filtering", async () => {
|
|
126
|
+
const coordinator = queryAwareFallbackCoordinator([result("default", MEETING_RECORD)]);
|
|
127
|
+
const out = await coordinator.searchQueryAwareFallback("quarterly", 1, prefilter([MEETING_RECORD]));
|
|
136
128
|
assert.deepEqual(out.map((r) => r.path), []);
|
|
137
129
|
});
|
|
138
130
|
|
|
139
|
-
test("
|
|
131
|
+
test("query-aware fallback excludes root archive candidates before its cap", async () => {
|
|
132
|
+
const archivePath = "archive/2026-02-23/fact-archived.md";
|
|
133
|
+
const coordinator = queryAwareFallbackCoordinator([
|
|
134
|
+
result("default", archivePath),
|
|
135
|
+
result("default", "facts/a.md"),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const out = await coordinator.searchQueryAwareFallback(
|
|
139
|
+
"quarterly",
|
|
140
|
+
1,
|
|
141
|
+
prefilter([archivePath, "facts/a.md"]),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
assert.deepEqual(out.map((r) => r.path), ["facts/a.md"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("query-aware fallback retains non-active candidates for historical filtering", async () => {
|
|
148
|
+
let optionsSeen: { allowArchived?: boolean } | undefined;
|
|
149
|
+
const searchScopedMemoryCandidates: RecallSearchPipelineDeps["searchScopedMemoryCandidates"] = async (
|
|
150
|
+
candidatePaths,
|
|
151
|
+
_query,
|
|
152
|
+
limit,
|
|
153
|
+
options,
|
|
154
|
+
) => {
|
|
155
|
+
optionsSeen = options;
|
|
156
|
+
return [result("default", "facts/superseded.md")]
|
|
157
|
+
.filter((candidate) => candidatePaths.has(candidate.path))
|
|
158
|
+
.slice(0, limit);
|
|
159
|
+
};
|
|
160
|
+
const coordinator = new RecallSearchPipelineCoordinator({
|
|
161
|
+
config: { memoryDir: "/mem" },
|
|
162
|
+
namespaceFromPath: () => "default",
|
|
163
|
+
searchScopedMemoryCandidates,
|
|
164
|
+
} as unknown as RecallSearchPipelineDeps);
|
|
165
|
+
|
|
166
|
+
const out = await coordinator.searchQueryAwareFallback(
|
|
167
|
+
"historical incident",
|
|
168
|
+
1,
|
|
169
|
+
prefilter(["facts/superseded.md"]),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
assert.deepEqual(out.map((candidate) => candidate.path), ["facts/superseded.md"]);
|
|
173
|
+
assert.deepEqual(optionsSeen, { allowArchived: true });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("query-aware fallback overfetches scoped candidates before exclusion", async () => {
|
|
140
177
|
// The scoped candidate order puts the NON-recallable meeting record FIRST;
|
|
141
178
|
// capping the scoped fetch to the caller's limit (1) BEFORE the generic-recall
|
|
142
179
|
// exclusion would return only the meeting record, which is then filtered out —
|
|
143
180
|
// dropping facts/a.md with no refill. Overfetching the full candidate set first
|
|
144
181
|
// and excluding afterward must still surface the recallable hit at the cap.
|
|
145
|
-
const coordinator =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
);
|
|
149
|
-
const out = await coordinator.
|
|
182
|
+
const coordinator = queryAwareFallbackCoordinator([
|
|
183
|
+
result("default", MEETING_RECORD),
|
|
184
|
+
result("default", "facts/a.md"),
|
|
185
|
+
]);
|
|
186
|
+
const out = await coordinator.searchQueryAwareFallback(
|
|
150
187
|
"quarterly",
|
|
151
|
-
["default"],
|
|
152
188
|
1,
|
|
153
189
|
prefilter([MEETING_RECORD, "facts/a.md"]),
|
|
154
190
|
);
|
|
@@ -159,6 +195,7 @@ test("finding 1 — overfetches scoped seeds before exclusion so a capped fetch
|
|
|
159
195
|
);
|
|
160
196
|
});
|
|
161
197
|
|
|
198
|
+
|
|
162
199
|
function hotSeedCoordinator(seed: QmdSearchResult[]): RecallSearchPipelineCoordinator {
|
|
163
200
|
const deps = {
|
|
164
201
|
config: { memoryDir: "/mem", qmdSearchStrategy: "lex", searchBackend: "qmd" },
|
|
@@ -250,3 +287,132 @@ test("finding 1 (round-5) — excluded seeds must not fill the merge cap and sta
|
|
|
250
287
|
"excluded seeds ranked ahead must not fill the fetchLimit cap and starve the recallable primary hit",
|
|
251
288
|
);
|
|
252
289
|
});
|
|
290
|
+
|
|
291
|
+
test("QMD widening continues past archive-only pages", async () => {
|
|
292
|
+
const archived = Array.from({ length: 45 }, (_, index) =>
|
|
293
|
+
resultScored("default", `archive/2026-07-25/archive-${index}.md`, 2),
|
|
294
|
+
);
|
|
295
|
+
const live = resultScored("default", "facts/live.md", 1);
|
|
296
|
+
const ranked = [...archived, live];
|
|
297
|
+
const coordinator = new RecallSearchPipelineCoordinator({
|
|
298
|
+
config: { memoryDir: "/mem", qmdSearchStrategy: "lex", searchBackend: "qmd" },
|
|
299
|
+
qmd: {},
|
|
300
|
+
namespaceFromPath: () => "default",
|
|
301
|
+
searchAcrossNamespaces: async ({ maxResults }: { maxResults: number }) =>
|
|
302
|
+
ranked.slice(0, maxResults),
|
|
303
|
+
} as unknown as RecallSearchPipelineDeps);
|
|
304
|
+
|
|
305
|
+
const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
|
|
306
|
+
"archive-starved",
|
|
307
|
+
20,
|
|
308
|
+
20,
|
|
309
|
+
{
|
|
310
|
+
namespacesEnabled: false,
|
|
311
|
+
recallNamespaces: ["default"],
|
|
312
|
+
resolveNamespace: () => "default",
|
|
313
|
+
queryAwarePrefilter: {
|
|
314
|
+
candidatePaths: null,
|
|
315
|
+
temporalFromDate: null,
|
|
316
|
+
matchedTags: [],
|
|
317
|
+
expandedTags: [],
|
|
318
|
+
combination: "none",
|
|
319
|
+
filteredToFullSearch: false,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
assert.deepEqual(out.map((candidate) => candidate.path), ["facts/live.md"]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("QMD hybrid top-up runs when primary results are archive-only", async () => {
|
|
328
|
+
const archived = Array.from({ length: 20 }, (_, index) =>
|
|
329
|
+
resultScored("default", `archive/2026-07-25/archive-${index}.md`, 2),
|
|
330
|
+
);
|
|
331
|
+
const live = resultScored("default", "facts/hybrid-live.md", 1);
|
|
332
|
+
let hybridCalls = 0;
|
|
333
|
+
const coordinator = new RecallSearchPipelineCoordinator({
|
|
334
|
+
config: {
|
|
335
|
+
memoryDir: "/mem",
|
|
336
|
+
qmdSearchStrategy: "hybrid",
|
|
337
|
+
searchBackend: "qmd",
|
|
338
|
+
},
|
|
339
|
+
qmd: {
|
|
340
|
+
search: async (
|
|
341
|
+
_query: string,
|
|
342
|
+
_collection: string,
|
|
343
|
+
maxResults: number,
|
|
344
|
+
) => archived.slice(0, maxResults),
|
|
345
|
+
hybridSearch: async () => {
|
|
346
|
+
hybridCalls += 1;
|
|
347
|
+
return [live];
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
namespaceFromPath: () => "default",
|
|
351
|
+
} as unknown as RecallSearchPipelineDeps);
|
|
352
|
+
|
|
353
|
+
const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
|
|
354
|
+
"archive-starved-hybrid",
|
|
355
|
+
2,
|
|
356
|
+
2,
|
|
357
|
+
{
|
|
358
|
+
namespacesEnabled: false,
|
|
359
|
+
recallNamespaces: ["default"],
|
|
360
|
+
resolveNamespace: () => "default",
|
|
361
|
+
collection: "openclaw-engram",
|
|
362
|
+
queryAwarePrefilter: {
|
|
363
|
+
candidatePaths: null,
|
|
364
|
+
temporalFromDate: null,
|
|
365
|
+
matchedTags: [],
|
|
366
|
+
expandedTags: [],
|
|
367
|
+
combination: "none",
|
|
368
|
+
filteredToFullSearch: false,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
assert.ok(hybridCalls > 0, "archive-only primary pages must trigger hybrid top-up");
|
|
374
|
+
assert.deepEqual(out.map((candidate) => candidate.path), ["facts/hybrid-live.md"]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("QMD stops after an underfilled archive-only page", async () => {
|
|
378
|
+
const archived = [resultScored("default", "archive/2026-07-25/only.md", 2)];
|
|
379
|
+
let searchCalls = 0;
|
|
380
|
+
const coordinator = new RecallSearchPipelineCoordinator({
|
|
381
|
+
config: {
|
|
382
|
+
memoryDir: "/mem",
|
|
383
|
+
qmdSearchStrategy: "hybrid",
|
|
384
|
+
searchBackend: "qmd",
|
|
385
|
+
},
|
|
386
|
+
qmd: {
|
|
387
|
+
search: async () => {
|
|
388
|
+
searchCalls += 1;
|
|
389
|
+
return archived;
|
|
390
|
+
},
|
|
391
|
+
hybridSearch: async () => [],
|
|
392
|
+
},
|
|
393
|
+
namespaceFromPath: () => "default",
|
|
394
|
+
} as unknown as RecallSearchPipelineDeps);
|
|
395
|
+
|
|
396
|
+
const out = await coordinator.fetchQmdMemoryResultsWithArtifactTopUp(
|
|
397
|
+
"archive-underfilled",
|
|
398
|
+
20,
|
|
399
|
+
20,
|
|
400
|
+
{
|
|
401
|
+
namespacesEnabled: false,
|
|
402
|
+
recallNamespaces: ["default"],
|
|
403
|
+
resolveNamespace: () => "default",
|
|
404
|
+
collection: "openclaw-engram",
|
|
405
|
+
queryAwarePrefilter: {
|
|
406
|
+
candidatePaths: null,
|
|
407
|
+
temporalFromDate: null,
|
|
408
|
+
matchedTags: [],
|
|
409
|
+
expandedTags: [],
|
|
410
|
+
combination: "none",
|
|
411
|
+
filteredToFullSearch: false,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
assert.deepEqual(out, []);
|
|
417
|
+
assert.equal(searchCalls, 1, "an underfilled raw page must not trigger widening");
|
|
418
|
+
});
|