@ai-agent-forge/plugin-memory 0.85.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.
Files changed (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Memory embedding providers for hybrid retrieval (混合检索 · 向量侧).
3
+ *
4
+ * POC-verified (2026-09, 100k corpus): high tier (Xenova/bge-m3, 1024d) and
5
+ * low tier (Xenova/bge-small-zh-v1.5, 512d) inference is deterministic
6
+ * bit-exact across runs; high-tier drift queries reach re@10 100% after
7
+ * reranking, low tier 83%. Tier presets are caller configuration — this module
8
+ * only exposes a modelId-parameterized interface (宪法: 不绑死单一 provider).
9
+ *
10
+ * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at
11
+ * first embed (惯例参考 packages/agent-forge/src/utils/photon.ts); type
12
+ * positions use type-only imports. The deterministic mock provider keeps tests
13
+ * offline (门禁: deterministic mock AI; 真实模型不进 CI).
14
+ */
15
+ /**
16
+ * Batch embedding provider. Implementations must be deterministic for
17
+ * identical input and must not silently degrade: any failure propagates.
18
+ */
19
+ import { join } from "node:path";
20
+ import { importHostOrBareModule } from "./host-module-import.js";
21
+ import { purgeZeroByteCacheArtifacts } from "./model-cache-hygiene.js";
22
+ const DEFAULT_TRANSFORMERS_MODEL_ID = "Xenova/bge-m3";
23
+ const DEFAULT_TRANSFORMERS_DIMENSIONS = 1024;
24
+ const DEFAULT_TRANSFORMERS_BATCH_SIZE = 16;
25
+ const DEFAULT_REMOTE_HOST = "https://huggingface.co";
26
+ function fnv1a32(input) {
27
+ let hash = 0x811c9dc5;
28
+ for (let offset = 0; offset < input.length; offset += 1) {
29
+ hash ^= input.charCodeAt(offset);
30
+ hash = Math.imul(hash, 0x01000193);
31
+ }
32
+ return hash >>> 0;
33
+ }
34
+ function embedDeterministicMock(text, dimensions) {
35
+ const values = new Array(dimensions).fill(0);
36
+ const tokens = text.toLowerCase().split(/[^\p{L}\p{N}]+/u);
37
+ for (const token of tokens) {
38
+ if (token.length === 0) {
39
+ continue;
40
+ }
41
+ // 多轮混淆累加: 每个 token 经 FNV-1a 后再派生 3 个维度沉点,
42
+ // 放大不同文本间的向量差异; 全程无随机、无时钟。
43
+ let mixed = fnv1a32(token);
44
+ for (let round = 0; round < 3; round += 1) {
45
+ mixed = Math.imul(mixed ^ 0x9e3779b9, 0x85ebca6b) >>> 0;
46
+ values[mixed % dimensions] += 1;
47
+ }
48
+ }
49
+ let sumSquares = 0;
50
+ for (const value of values) {
51
+ sumSquares += value * value;
52
+ }
53
+ if (sumSquares === 0) {
54
+ // 空文本/全标点: 确定性零向量; L2 范数为 0, 不归一化 (避免除零 NaN)。
55
+ return values;
56
+ }
57
+ const norm = Math.sqrt(sumSquares);
58
+ return values.map((value) => value / norm);
59
+ }
60
+ /**
61
+ * Deterministic mock embedding provider for offline tests: tokenizes by
62
+ * `[^\p{L}\p{N}]+`, hashes each token with FNV-1a plus multi-round mixing into
63
+ * dimension indices, then L2-normalizes. Same text → same vector, no random,
64
+ * no clock. POC 已用真实模型验证 bit-exact; mock 只承担确定性测试门禁。
65
+ */
66
+ export function createDeterministicMockEmbeddingProvider(options) {
67
+ const dimensions = options?.dimensions ?? 64;
68
+ return {
69
+ modelId: "mock-embedding@1",
70
+ dimensions,
71
+ embed: async (texts) => texts.map((text) => embedDeterministicMock(text, dimensions)),
72
+ };
73
+ }
74
+ const createDefaultTransformersLoadImpl = (moduleEntryPath) => {
75
+ return async (modelId, cacheDir, remoteHost) => {
76
+ const transformers = await importHostOrBareModule(moduleEntryPath ?? "@huggingface/transformers");
77
+ transformers.env.cacheDir = cacheDir;
78
+ transformers.env.remoteHost = remoteHost;
79
+ const extractor = await transformers.pipeline("feature-extraction", modelId, { dtype: "q8" });
80
+ return {
81
+ extract: async (texts) => {
82
+ const output = await extractor(texts, { pooling: "cls", normalize: true });
83
+ return { toList: () => output.tolist() };
84
+ },
85
+ };
86
+ };
87
+ };
88
+ /**
89
+ * Transformers.js-backed embedding provider (POC 档位: 高档默认
90
+ * Xenova/bge-m3/1024d; 低档由调用方传 modelId "Xenova/bge-small-zh-v1.5" +
91
+ * dimensions 512)。
92
+ *
93
+ * 镜像序列: 首次 embed 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传
94
+ * ["https://huggingface.co", "https://hf-mirror.com"]); 某 host 加载抛错则
95
+ * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次
96
+ * (并发 embed 共享同一 in-flight promise); 加载失败不缓存, 下次 embed 重新
97
+ * 走完整序列, 失败原因原样保留, 不静默降级。
98
+ *
99
+ * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名
100
+ * (modelId, cacheDir, remoteHost) => backend。
101
+ */
102
+ export function createTransformersEmbeddingProvider(options) {
103
+ const modelId = options.modelId ?? DEFAULT_TRANSFORMERS_MODEL_ID;
104
+ const dimensions = options.dimensions ?? DEFAULT_TRANSFORMERS_DIMENSIONS;
105
+ const batchSize = options.batchSize ?? DEFAULT_TRANSFORMERS_BATCH_SIZE;
106
+ const cacheDir = options.cacheDir;
107
+ const remoteHosts = options.remoteHosts ?? [DEFAULT_REMOTE_HOST];
108
+ const load = options.loadImpl ?? createDefaultTransformersLoadImpl(options.moduleEntryPath);
109
+ let backendPromise = null;
110
+ const loadBackend = () => {
111
+ const existing = backendPromise;
112
+ if (existing) {
113
+ return existing;
114
+ }
115
+ const attempt = (async () => {
116
+ let lastError;
117
+ for (const remoteHost of remoteHosts) {
118
+ try {
119
+ return await load(modelId, cacheDir, remoteHost);
120
+ }
121
+ catch (error) {
122
+ lastError = error;
123
+ // 镜像轮换前清 0 字节残骸: 失败下载留下的空文件会被 transformers
124
+ // 当作缓存命中, 毒化下一个 host 的重试 (model-cache-hygiene)。
125
+ purgeZeroByteCacheArtifacts(join(cacheDir, modelId));
126
+ }
127
+ }
128
+ if (lastError === undefined)
129
+ throw new Error(`no remote hosts configured for model ${modelId}`);
130
+ throw lastError;
131
+ })();
132
+ backendPromise = attempt;
133
+ attempt.catch(() => {
134
+ // 失败不缓存: 下次 embed 重新走镜像序列 (显式失败, 不静默降级)。
135
+ if (backendPromise === attempt) {
136
+ backendPromise = null;
137
+ }
138
+ });
139
+ return attempt;
140
+ };
141
+ return {
142
+ modelId,
143
+ dimensions,
144
+ embed: async (texts) => {
145
+ if (texts.length === 0) {
146
+ return [];
147
+ }
148
+ const backend = await loadBackend();
149
+ const vectors = [];
150
+ for (let offset = 0; offset < texts.length; offset += batchSize) {
151
+ const batchTexts = texts.slice(offset, offset + batchSize);
152
+ const rows = (await backend.extract(batchTexts)).toList();
153
+ for (const row of rows) {
154
+ if (row.length !== dimensions) {
155
+ throw new Error(`embedding dimension mismatch: model ${modelId} returned ${row.length} dims, provider declares ${dimensions}`);
156
+ }
157
+ vectors.push(row);
158
+ }
159
+ }
160
+ return vectors;
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=embedding-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-provider.js","sourceRoot":"","sources":["../../src/memory/embedding-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;GAGG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AA6BvE,MAAM,6BAA6B,GAAG,eAAe,CAAC;AACtD,MAAM,+BAA+B,GAAG,IAAI,CAAC;AAC7C,MAAM,+BAA+B,GAAG,EAAE,CAAC;AAC3C,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD,SAAS,OAAO,CAAC,KAAa,EAAU;IACvC,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;QACzD,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AAAA,CAClB;AAED,SAAS,sBAAsB,CAAC,IAAY,EAAE,UAAkB,EAAY;IAC3E,MAAM,MAAM,GAAG,IAAI,KAAK,CAAS,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IAC3D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,SAAS;QACV,CAAC;QACD,8EAA0C;QAC1C,uEAA2B;QAC3B,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAC3C,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IACD,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,UAAU,IAAI,KAAK,GAAG,KAAK,CAAC;IAC7B,CAAC;IACD,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACtB,8FAA8C;QAC9C,OAAO,MAAM,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;AAAA,CAC3C;AAED;;;;;GAKG;AACH,MAAM,UAAU,wCAAwC,CAAC,OAExD,EAA6B;IAC7B,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,EAAE,CAAC;IAC7C,OAAO;QACN,OAAO,EAAE,kBAAkB;QAC3B,UAAU;QACV,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;KACrF,CAAC;AAAA,CACF;AAED,MAAM,iCAAiC,GAAG,CAAC,eAAwB,EAAiC,EAAE,CAAC;IACtG,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,CAAC;QAC/C,MAAM,YAAY,GAAG,MAAM,sBAAsB,CAChD,eAAe,IAAI,2BAA2B,CAC9C,CAAC;QACF,YAAY,CAAC,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACrC,YAAY,CAAC,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC;QACzC,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9F,OAAO;YACN,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC3E,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAgB,EAAE,CAAC;YAAA,CACvD;SACD,CAAC;IAAA,CACF,CAAC;AAAA,CACF,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mCAAmC,CAAC,OASnD,EAA6B;IAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,6BAA6B,CAAC;IACjE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,+BAA+B,CAAC;IACzE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,+BAA+B,CAAC;IACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,iCAAiC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE5F,IAAI,cAAc,GAAmD,IAAI,CAAC;IAC1E,MAAM,WAAW,GAAG,GAA4C,EAAE,CAAC;QAClE,MAAM,QAAQ,GAAG,cAAc,CAAC;QAChC,IAAI,QAAQ,EAAE,CAAC;YACd,OAAO,QAAQ,CAAC;QACjB,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,IAAI,SAAkB,CAAC;YACvB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;gBACtC,IAAI,CAAC;oBACJ,OAAO,MAAM,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;gBAClD,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,SAAS,GAAG,KAAK,CAAC;oBAClB,uFAA2C;oBAC3C,8EAAgD;oBAChD,2BAA2B,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;gBACtD,CAAC;YACF,CAAC;YACD,IAAI,SAAS,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;YAChG,MAAM,SAAS,CAAC;QAAA,CAChB,CAAC,EAAE,CAAC;QACL,cAAc,GAAG,OAAO,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YACnB,yFAAyC;YACzC,IAAI,cAAc,KAAK,OAAO,EAAE,CAAC;gBAChC,cAAc,GAAG,IAAI,CAAC;YACvB,CAAC;QAAA,CACD,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IAAA,CACf,CAAC;IAEF,OAAO;QACN,OAAO;QACP,UAAU;QACV,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;YACvB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,EAAE,CAAC;YACX,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;YACpC,MAAM,OAAO,GAAe,EAAE,CAAC;YAC/B,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC;gBACjE,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;gBAC3D,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC1D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACxB,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;wBAC/B,MAAM,IAAI,KAAK,CACd,uCAAuC,OAAO,aAAa,GAAG,CAAC,MAAM,4BAA4B,UAAU,EAAE,CAC7G,CAAC;oBACH,CAAC;oBACD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;YACF,CAAC;YACD,OAAO,OAAO,CAAC;QAAA,CACf;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * Memory embedding providers for hybrid retrieval (混合检索 · 向量侧).\n *\n * POC-verified (2026-09, 100k corpus): high tier (Xenova/bge-m3, 1024d) and\n * low tier (Xenova/bge-small-zh-v1.5, 512d) inference is deterministic\n * bit-exact across runs; high-tier drift queries reach re@10 100% after\n * reranking, low tier 83%. Tier presets are caller configuration — this module\n * only exposes a modelId-parameterized interface (宪法: 不绑死单一 provider).\n *\n * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at\n * first embed (惯例参考 packages/agent-forge/src/utils/photon.ts); type\n * positions use type-only imports. The deterministic mock provider keeps tests\n * offline (门禁: deterministic mock AI; 真实模型不进 CI).\n */\n\n/**\n * Batch embedding provider. Implementations must be deterministic for\n * identical input and must not silently degrade: any failure propagates.\n */\nimport { join } from \"node:path\";\nimport { importHostOrBareModule } from \"./host-module-import.ts\";\nimport { purgeZeroByteCacheArtifacts } from \"./model-cache-hygiene.ts\";\n\nexport interface MemoryEmbeddingProviderV1 {\n\treadonly modelId: string;\n\treadonly dimensions: number;\n\t/**\n\t * Batch embed. Vectors are L2-normalized float arrays. Deterministic for\n\t * identical input. Plain text mapping — no instruction prefix is added\n\t * here; query-side instructions for bge-small-zh family are the caller's\n\t * responsibility (低档 bge-small-zh 系查询侧指令由调用方负责添加).\n\t */\n\tembed(texts: readonly string[]): Promise<readonly (readonly number[])[]>;\n}\n\n/**\n * Minimal structural seam around a transformers.js feature-extraction\n * pipeline, so tests can inject a fake without touching the real loader.\n */\nexport interface TransformersEmbeddingBackendV1 {\n\t/** Extract features for a batch of texts; one vector per input, input order. */\n\textract: (texts: string[]) => Promise<{ toList: () => number[][] }>;\n}\n\nexport type TransformersEmbeddingLoadImpl = (\n\tmodelId: string,\n\tcacheDir: string,\n\tremoteHost: string,\n) => Promise<TransformersEmbeddingBackendV1>;\n\nconst DEFAULT_TRANSFORMERS_MODEL_ID = \"Xenova/bge-m3\";\nconst DEFAULT_TRANSFORMERS_DIMENSIONS = 1024;\nconst DEFAULT_TRANSFORMERS_BATCH_SIZE = 16;\nconst DEFAULT_REMOTE_HOST = \"https://huggingface.co\";\n\nfunction fnv1a32(input: string): number {\n\tlet hash = 0x811c9dc5;\n\tfor (let offset = 0; offset < input.length; offset += 1) {\n\t\thash ^= input.charCodeAt(offset);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn hash >>> 0;\n}\n\nfunction embedDeterministicMock(text: string, dimensions: number): number[] {\n\tconst values = new Array<number>(dimensions).fill(0);\n\tconst tokens = text.toLowerCase().split(/[^\\p{L}\\p{N}]+/u);\n\tfor (const token of tokens) {\n\t\tif (token.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\t\t// 多轮混淆累加: 每个 token 经 FNV-1a 后再派生 3 个维度沉点,\n\t\t// 放大不同文本间的向量差异; 全程无随机、无时钟。\n\t\tlet mixed = fnv1a32(token);\n\t\tfor (let round = 0; round < 3; round += 1) {\n\t\t\tmixed = Math.imul(mixed ^ 0x9e3779b9, 0x85ebca6b) >>> 0;\n\t\t\tvalues[mixed % dimensions] += 1;\n\t\t}\n\t}\n\tlet sumSquares = 0;\n\tfor (const value of values) {\n\t\tsumSquares += value * value;\n\t}\n\tif (sumSquares === 0) {\n\t\t// 空文本/全标点: 确定性零向量; L2 范数为 0, 不归一化 (避免除零 NaN)。\n\t\treturn values;\n\t}\n\tconst norm = Math.sqrt(sumSquares);\n\treturn values.map((value) => value / norm);\n}\n\n/**\n * Deterministic mock embedding provider for offline tests: tokenizes by\n * `[^\\p{L}\\p{N}]+`, hashes each token with FNV-1a plus multi-round mixing into\n * dimension indices, then L2-normalizes. Same text → same vector, no random,\n * no clock. POC 已用真实模型验证 bit-exact; mock 只承担确定性测试门禁。\n */\nexport function createDeterministicMockEmbeddingProvider(options?: {\n\treadonly dimensions?: number;\n}): MemoryEmbeddingProviderV1 {\n\tconst dimensions = options?.dimensions ?? 64;\n\treturn {\n\t\tmodelId: \"mock-embedding@1\",\n\t\tdimensions,\n\t\tembed: async (texts) => texts.map((text) => embedDeterministicMock(text, dimensions)),\n\t};\n}\n\nconst createDefaultTransformersLoadImpl = (moduleEntryPath?: string): TransformersEmbeddingLoadImpl => {\n\treturn async (modelId, cacheDir, remoteHost) => {\n\t\tconst transformers = await importHostOrBareModule<typeof import(\"@huggingface/transformers\")>(\n\t\t\tmoduleEntryPath ?? \"@huggingface/transformers\",\n\t\t);\n\t\ttransformers.env.cacheDir = cacheDir;\n\t\ttransformers.env.remoteHost = remoteHost;\n\t\tconst extractor = await transformers.pipeline(\"feature-extraction\", modelId, { dtype: \"q8\" });\n\t\treturn {\n\t\t\textract: async (texts) => {\n\t\t\t\tconst output = await extractor(texts, { pooling: \"cls\", normalize: true });\n\t\t\t\treturn { toList: () => output.tolist() as number[][] };\n\t\t\t},\n\t\t};\n\t};\n};\n\n/**\n * Transformers.js-backed embedding provider (POC 档位: 高档默认\n * Xenova/bge-m3/1024d; 低档由调用方传 modelId \"Xenova/bge-small-zh-v1.5\" +\n * dimensions 512)。\n *\n * 镜像序列: 首次 embed 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传\n * [\"https://huggingface.co\", \"https://hf-mirror.com\"]); 某 host 加载抛错则\n * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次\n * (并发 embed 共享同一 in-flight promise); 加载失败不缓存, 下次 embed 重新\n * 走完整序列, 失败原因原样保留, 不静默降级。\n *\n * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名\n * (modelId, cacheDir, remoteHost) => backend。\n */\nexport function createTransformersEmbeddingProvider(options: {\n\treadonly modelId?: string;\n\treadonly dimensions?: number;\n\treadonly cacheDir: string;\n\treadonly remoteHosts?: readonly string[];\n\treadonly batchSize?: number;\n\treadonly loadImpl?: TransformersEmbeddingLoadImpl;\n\t/** Host-resolved transformers entry path (install-store visibility); undefined = bare import. */\n\treadonly moduleEntryPath?: string;\n}): MemoryEmbeddingProviderV1 {\n\tconst modelId = options.modelId ?? DEFAULT_TRANSFORMERS_MODEL_ID;\n\tconst dimensions = options.dimensions ?? DEFAULT_TRANSFORMERS_DIMENSIONS;\n\tconst batchSize = options.batchSize ?? DEFAULT_TRANSFORMERS_BATCH_SIZE;\n\tconst cacheDir = options.cacheDir;\n\tconst remoteHosts = options.remoteHosts ?? [DEFAULT_REMOTE_HOST];\n\tconst load = options.loadImpl ?? createDefaultTransformersLoadImpl(options.moduleEntryPath);\n\n\tlet backendPromise: Promise<TransformersEmbeddingBackendV1> | null = null;\n\tconst loadBackend = (): Promise<TransformersEmbeddingBackendV1> => {\n\t\tconst existing = backendPromise;\n\t\tif (existing) {\n\t\t\treturn existing;\n\t\t}\n\t\tconst attempt = (async () => {\n\t\t\tlet lastError: unknown;\n\t\t\tfor (const remoteHost of remoteHosts) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await load(modelId, cacheDir, remoteHost);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlastError = error;\n\t\t\t\t\t// 镜像轮换前清 0 字节残骸: 失败下载留下的空文件会被 transformers\n\t\t\t\t\t// 当作缓存命中, 毒化下一个 host 的重试 (model-cache-hygiene)。\n\t\t\t\t\tpurgeZeroByteCacheArtifacts(join(cacheDir, modelId));\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lastError === undefined) throw new Error(`no remote hosts configured for model ${modelId}`);\n\t\t\tthrow lastError;\n\t\t})();\n\t\tbackendPromise = attempt;\n\t\tattempt.catch(() => {\n\t\t\t// 失败不缓存: 下次 embed 重新走镜像序列 (显式失败, 不静默降级)。\n\t\t\tif (backendPromise === attempt) {\n\t\t\t\tbackendPromise = null;\n\t\t\t}\n\t\t});\n\t\treturn attempt;\n\t};\n\n\treturn {\n\t\tmodelId,\n\t\tdimensions,\n\t\tembed: async (texts) => {\n\t\t\tif (texts.length === 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst backend = await loadBackend();\n\t\t\tconst vectors: number[][] = [];\n\t\t\tfor (let offset = 0; offset < texts.length; offset += batchSize) {\n\t\t\t\tconst batchTexts = texts.slice(offset, offset + batchSize);\n\t\t\t\tconst rows = (await backend.extract(batchTexts)).toList();\n\t\t\t\tfor (const row of rows) {\n\t\t\t\t\tif (row.length !== dimensions) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`embedding dimension mismatch: model ${modelId} returned ${row.length} dims, provider declares ${dimensions}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tvectors.push(row);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn vectors;\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Memory cross-encoder reranker for hybrid retrieval (混合检索 · 精排侧).
3
+ *
4
+ * POC-verified (2026-09, 100k corpus): reranking drift-query retrieval with
5
+ * Xenova/bge-reranker-base lifts high-tier recall to re@10 100% (低档 83%),
6
+ * inference deterministic across runs. Reranking is an independently
7
+ * disable-able component (宪法: 独立可禁用); tiers are caller configuration.
8
+ *
9
+ * Model note: default "Xenova/bge-reranker-base" is the only officially
10
+ * converted (transformers.js) reranker verified in POC; q8 weights ~280MB,
11
+ * 512-token context — the caller owns the write-side length contract
12
+ * (statement ≤700 chars); overlong inputs are gracefully truncated by the
13
+ * tokenizer (`truncation: true`). Upgrade path bge-reranker-v2-m3 is a
14
+ * modelId swap away, but it currently has no official transformers.js
15
+ * conversion.
16
+ *
17
+ * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at
18
+ * first rerank (惯例参考 packages/agent-forge/src/utils/photon.ts); type
19
+ * positions use type-only imports. Any failure propagates — 不静默降级.
20
+ */
21
+ export interface MemoryRerankerV1 {
22
+ readonly modelId: string;
23
+ /** Score each doc against the query; higher = more relevant (raw logits, order-preserving). */
24
+ rerank(query: string, docs: readonly string[]): Promise<readonly number[]>;
25
+ }
26
+ /**
27
+ * Minimal structural seam around a transformers.js sequence-classification
28
+ * pair, so tests can inject a fake without touching the real loader.
29
+ */
30
+ export interface TransformersRerankerBackendV1 {
31
+ /** Score docs against the query; result[i] corresponds to docs[i]. */
32
+ score: (query: string, docs: string[]) => Promise<number[]>;
33
+ }
34
+ export type TransformersRerankerLoadImpl = (modelId: string, cacheDir: string, remoteHost: string) => Promise<TransformersRerankerBackendV1>;
35
+ /**
36
+ * Transformers.js-backed cross-encoder reranker (两档共用默认
37
+ * Xenova/bge-reranker-base, 见文件头模型说明)。
38
+ *
39
+ * 镜像序列: 首次 rerank 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传
40
+ * ["https://huggingface.co", "https://hf-mirror.com"]); 某 host 加载抛错则
41
+ * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次
42
+ * (并发 rerank 共享同一 in-flight promise); 加载失败不缓存, 下次 rerank
43
+ * 重新走完整序列, 失败原因原样保留, 不静默降级。
44
+ *
45
+ * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名
46
+ * (modelId, cacheDir, remoteHost) => backend。
47
+ */
48
+ export declare function createTransformersReranker(options: {
49
+ readonly modelId?: string;
50
+ readonly cacheDir: string;
51
+ readonly remoteHosts?: readonly string[];
52
+ readonly loadImpl?: TransformersRerankerLoadImpl;
53
+ /** Host-resolved transformers entry path (install-store visibility); undefined = bare import. */
54
+ readonly moduleEntryPath?: string;
55
+ }): MemoryRerankerV1;
56
+ //# sourceMappingURL=embedding-reranker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-reranker.d.ts","sourceRoot":"","sources":["../../src/memory/embedding-reranker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;CAC3E;AAED;;;GAGG;AACH,MAAM,WAAW,6BAA6B;IAC7C,sEAAsE;IACtE,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,4BAA4B,GAAG,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,KACd,OAAO,CAAC,6BAA6B,CAAC,CAAC;AAsC5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,4BAA4B,CAAC;IACjD,iGAAiG;IACjG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC,GAAG,gBAAgB,CA+CnB","sourcesContent":["/**\n * Memory cross-encoder reranker for hybrid retrieval (混合检索 · 精排侧).\n *\n * POC-verified (2026-09, 100k corpus): reranking drift-query retrieval with\n * Xenova/bge-reranker-base lifts high-tier recall to re@10 100% (低档 83%),\n * inference deterministic across runs. Reranking is an independently\n * disable-able component (宪法: 独立可禁用); tiers are caller configuration.\n *\n * Model note: default \"Xenova/bge-reranker-base\" is the only officially\n * converted (transformers.js) reranker verified in POC; q8 weights ~280MB,\n * 512-token context — the caller owns the write-side length contract\n * (statement ≤700 chars); overlong inputs are gracefully truncated by the\n * tokenizer (`truncation: true`). Upgrade path bge-reranker-v2-m3 is a\n * modelId swap away, but it currently has no official transformers.js\n * conversion.\n *\n * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at\n * first rerank (惯例参考 packages/agent-forge/src/utils/photon.ts); type\n * positions use type-only imports. Any failure propagates — 不静默降级.\n */\n\n/**\n * Cross-encoder reranker. Higher score = more relevant; raw logits are only\n * order-preserving, not calibrated probabilities.\n */\nimport { join } from \"node:path\";\nimport { importHostOrBareModule } from \"./host-module-import.ts\";\nimport { purgeZeroByteCacheArtifacts } from \"./model-cache-hygiene.ts\";\n\nexport interface MemoryRerankerV1 {\n\treadonly modelId: string;\n\t/** Score each doc against the query; higher = more relevant (raw logits, order-preserving). */\n\trerank(query: string, docs: readonly string[]): Promise<readonly number[]>;\n}\n\n/**\n * Minimal structural seam around a transformers.js sequence-classification\n * pair, so tests can inject a fake without touching the real loader.\n */\nexport interface TransformersRerankerBackendV1 {\n\t/** Score docs against the query; result[i] corresponds to docs[i]. */\n\tscore: (query: string, docs: string[]) => Promise<number[]>;\n}\n\nexport type TransformersRerankerLoadImpl = (\n\tmodelId: string,\n\tcacheDir: string,\n\tremoteHost: string,\n) => Promise<TransformersRerankerBackendV1>;\n\nconst DEFAULT_RERANKER_MODEL_ID = \"Xenova/bge-reranker-base\";\nconst DEFAULT_REMOTE_HOST = \"https://huggingface.co\";\n\n/** Structural view of the tokenizer call; returns the model-input object. */\ntype RerankTokenizerCall = (\n\ttexts: string[],\n\toptions: { text_pair: string[]; padding: boolean; truncation: boolean },\n) => unknown;\n\n/** Structural view of the sequence-classification forward pass. */\ntype RerankModelCall = (inputs: unknown) => Promise<{ logits: { data: ArrayLike<number> } }>;\n\nconst createDefaultTransformersLoadImpl = (moduleEntryPath?: string): TransformersRerankerLoadImpl => {\n\treturn async (modelId, cacheDir, remoteHost) => {\n\t\tconst transformers = await importHostOrBareModule<typeof import(\"@huggingface/transformers\")>(\n\t\t\tmoduleEntryPath ?? \"@huggingface/transformers\",\n\t\t);\n\t\ttransformers.env.cacheDir = cacheDir;\n\t\ttransformers.env.remoteHost = remoteHost;\n\t\tconst tokenizer = (await transformers.AutoTokenizer.from_pretrained(modelId)) as unknown as RerankTokenizerCall;\n\t\tconst model = (await transformers.AutoModelForSequenceClassification.from_pretrained(modelId, {\n\t\t\tdtype: \"q8\",\n\t\t})) as unknown as RerankModelCall;\n\t\treturn {\n\t\t\tscore: async (query, docs) => {\n\t\t\t\tconst inputs = tokenizer(\n\t\t\t\t\tdocs.map(() => query),\n\t\t\t\t\t{ text_pair: docs, padding: true, truncation: true },\n\t\t\t\t);\n\t\t\t\tconst output = await model(inputs);\n\t\t\t\treturn Array.from(output.logits.data);\n\t\t\t},\n\t\t};\n\t};\n};\n\n/**\n * Transformers.js-backed cross-encoder reranker (两档共用默认\n * Xenova/bge-reranker-base, 见文件头模型说明)。\n *\n * 镜像序列: 首次 rerank 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传\n * [\"https://huggingface.co\", \"https://hf-mirror.com\"]); 某 host 加载抛错则\n * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次\n * (并发 rerank 共享同一 in-flight promise); 加载失败不缓存, 下次 rerank\n * 重新走完整序列, 失败原因原样保留, 不静默降级。\n *\n * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名\n * (modelId, cacheDir, remoteHost) => backend。\n */\nexport function createTransformersReranker(options: {\n\treadonly modelId?: string;\n\treadonly cacheDir: string;\n\treadonly remoteHosts?: readonly string[];\n\treadonly loadImpl?: TransformersRerankerLoadImpl;\n\t/** Host-resolved transformers entry path (install-store visibility); undefined = bare import. */\n\treadonly moduleEntryPath?: string;\n}): MemoryRerankerV1 {\n\tconst modelId = options.modelId ?? DEFAULT_RERANKER_MODEL_ID;\n\tconst cacheDir = options.cacheDir;\n\tconst remoteHosts = options.remoteHosts ?? [DEFAULT_REMOTE_HOST];\n\tconst load = options.loadImpl ?? createDefaultTransformersLoadImpl(options.moduleEntryPath);\n\n\tlet backendPromise: Promise<TransformersRerankerBackendV1> | null = null;\n\tconst loadBackend = (): Promise<TransformersRerankerBackendV1> => {\n\t\tconst existing = backendPromise;\n\t\tif (existing) {\n\t\t\treturn existing;\n\t\t}\n\t\tconst attempt = (async () => {\n\t\t\tlet lastError: unknown;\n\t\t\tfor (const remoteHost of remoteHosts) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await load(modelId, cacheDir, remoteHost);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlastError = error;\n\t\t\t\t\t// 镜像轮换前清 0 字节残骸 (embedding-provider 同理, model-cache-hygiene)。\n\t\t\t\t\tpurgeZeroByteCacheArtifacts(join(cacheDir, modelId));\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lastError === undefined) throw new Error(`no remote hosts configured for model ${modelId}`);\n\t\t\tthrow lastError;\n\t\t})();\n\t\tbackendPromise = attempt;\n\t\tattempt.catch(() => {\n\t\t\t// 失败不缓存: 下次 rerank 重新走镜像序列 (显式失败, 不静默降级)。\n\t\t\tif (backendPromise === attempt) {\n\t\t\t\tbackendPromise = null;\n\t\t\t}\n\t\t});\n\t\treturn attempt;\n\t};\n\n\treturn {\n\t\tmodelId,\n\t\trerank: async (query, docs) => {\n\t\t\tif (docs.length === 0) {\n\t\t\t\t// 空 docs 直接返回, 不触发模型加载 (lazy-load 只发生在真正需要打分时)。\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst backend = await loadBackend();\n\t\t\treturn backend.score(query, [...docs]);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Memory cross-encoder reranker for hybrid retrieval (混合检索 · 精排侧).
3
+ *
4
+ * POC-verified (2026-09, 100k corpus): reranking drift-query retrieval with
5
+ * Xenova/bge-reranker-base lifts high-tier recall to re@10 100% (低档 83%),
6
+ * inference deterministic across runs. Reranking is an independently
7
+ * disable-able component (宪法: 独立可禁用); tiers are caller configuration.
8
+ *
9
+ * Model note: default "Xenova/bge-reranker-base" is the only officially
10
+ * converted (transformers.js) reranker verified in POC; q8 weights ~280MB,
11
+ * 512-token context — the caller owns the write-side length contract
12
+ * (statement ≤700 chars); overlong inputs are gracefully truncated by the
13
+ * tokenizer (`truncation: true`). Upgrade path bge-reranker-v2-m3 is a
14
+ * modelId swap away, but it currently has no official transformers.js
15
+ * conversion.
16
+ *
17
+ * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at
18
+ * first rerank (惯例参考 packages/agent-forge/src/utils/photon.ts); type
19
+ * positions use type-only imports. Any failure propagates — 不静默降级.
20
+ */
21
+ /**
22
+ * Cross-encoder reranker. Higher score = more relevant; raw logits are only
23
+ * order-preserving, not calibrated probabilities.
24
+ */
25
+ import { join } from "node:path";
26
+ import { importHostOrBareModule } from "./host-module-import.js";
27
+ import { purgeZeroByteCacheArtifacts } from "./model-cache-hygiene.js";
28
+ const DEFAULT_RERANKER_MODEL_ID = "Xenova/bge-reranker-base";
29
+ const DEFAULT_REMOTE_HOST = "https://huggingface.co";
30
+ const createDefaultTransformersLoadImpl = (moduleEntryPath) => {
31
+ return async (modelId, cacheDir, remoteHost) => {
32
+ const transformers = await importHostOrBareModule(moduleEntryPath ?? "@huggingface/transformers");
33
+ transformers.env.cacheDir = cacheDir;
34
+ transformers.env.remoteHost = remoteHost;
35
+ const tokenizer = (await transformers.AutoTokenizer.from_pretrained(modelId));
36
+ const model = (await transformers.AutoModelForSequenceClassification.from_pretrained(modelId, {
37
+ dtype: "q8",
38
+ }));
39
+ return {
40
+ score: async (query, docs) => {
41
+ const inputs = tokenizer(docs.map(() => query), { text_pair: docs, padding: true, truncation: true });
42
+ const output = await model(inputs);
43
+ return Array.from(output.logits.data);
44
+ },
45
+ };
46
+ };
47
+ };
48
+ /**
49
+ * Transformers.js-backed cross-encoder reranker (两档共用默认
50
+ * Xenova/bge-reranker-base, 见文件头模型说明)。
51
+ *
52
+ * 镜像序列: 首次 rerank 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传
53
+ * ["https://huggingface.co", "https://hf-mirror.com"]); 某 host 加载抛错则
54
+ * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次
55
+ * (并发 rerank 共享同一 in-flight promise); 加载失败不缓存, 下次 rerank
56
+ * 重新走完整序列, 失败原因原样保留, 不静默降级。
57
+ *
58
+ * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名
59
+ * (modelId, cacheDir, remoteHost) => backend。
60
+ */
61
+ export function createTransformersReranker(options) {
62
+ const modelId = options.modelId ?? DEFAULT_RERANKER_MODEL_ID;
63
+ const cacheDir = options.cacheDir;
64
+ const remoteHosts = options.remoteHosts ?? [DEFAULT_REMOTE_HOST];
65
+ const load = options.loadImpl ?? createDefaultTransformersLoadImpl(options.moduleEntryPath);
66
+ let backendPromise = null;
67
+ const loadBackend = () => {
68
+ const existing = backendPromise;
69
+ if (existing) {
70
+ return existing;
71
+ }
72
+ const attempt = (async () => {
73
+ let lastError;
74
+ for (const remoteHost of remoteHosts) {
75
+ try {
76
+ return await load(modelId, cacheDir, remoteHost);
77
+ }
78
+ catch (error) {
79
+ lastError = error;
80
+ // 镜像轮换前清 0 字节残骸 (embedding-provider 同理, model-cache-hygiene)。
81
+ purgeZeroByteCacheArtifacts(join(cacheDir, modelId));
82
+ }
83
+ }
84
+ if (lastError === undefined)
85
+ throw new Error(`no remote hosts configured for model ${modelId}`);
86
+ throw lastError;
87
+ })();
88
+ backendPromise = attempt;
89
+ attempt.catch(() => {
90
+ // 失败不缓存: 下次 rerank 重新走镜像序列 (显式失败, 不静默降级)。
91
+ if (backendPromise === attempt) {
92
+ backendPromise = null;
93
+ }
94
+ });
95
+ return attempt;
96
+ };
97
+ return {
98
+ modelId,
99
+ rerank: async (query, docs) => {
100
+ if (docs.length === 0) {
101
+ // 空 docs 直接返回, 不触发模型加载 (lazy-load 只发生在真正需要打分时)。
102
+ return [];
103
+ }
104
+ const backend = await loadBackend();
105
+ return backend.score(query, [...docs]);
106
+ },
107
+ };
108
+ }
109
+ //# sourceMappingURL=embedding-reranker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedding-reranker.js","sourceRoot":"","sources":["../../src/memory/embedding-reranker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory cross-encoder reranker for hybrid retrieval (混合检索 · 精排侧).\n *\n * POC-verified (2026-09, 100k corpus): reranking drift-query retrieval with\n * Xenova/bge-reranker-base lifts high-tier recall to re@10 100% (低档 83%),\n * inference deterministic across runs. Reranking is an independently\n * disable-able component (宪法: 独立可禁用); tiers are caller configuration.\n *\n * Model note: default \"Xenova/bge-reranker-base\" is the only officially\n * converted (transformers.js) reranker verified in POC; q8 weights ~280MB,\n * 512-token context — the caller owns the write-side length contract\n * (statement ≤700 chars); overlong inputs are gracefully truncated by the\n * tokenizer (`truncation: true`). Upgrade path bge-reranker-v2-m3 is a\n * modelId swap away, but it currently has no official transformers.js\n * conversion.\n *\n * Runtime dep @huggingface/transformers is lazy-loaded via `await import` at\n * first rerank (惯例参考 packages/agent-forge/src/utils/photon.ts); type\n * positions use type-only imports. Any failure propagates — 不静默降级.\n */\n\n/**\n * Cross-encoder reranker. Higher score = more relevant; raw logits are only\n * order-preserving, not calibrated probabilities.\n */\nimport { join } from \"node:path\";\nimport { importHostOrBareModule } from \"./host-module-import.ts\";\nimport { purgeZeroByteCacheArtifacts } from \"./model-cache-hygiene.ts\";\n\nexport interface MemoryRerankerV1 {\n\treadonly modelId: string;\n\t/** Score each doc against the query; higher = more relevant (raw logits, order-preserving). */\n\trerank(query: string, docs: readonly string[]): Promise<readonly number[]>;\n}\n\n/**\n * Minimal structural seam around a transformers.js sequence-classification\n * pair, so tests can inject a fake without touching the real loader.\n */\nexport interface TransformersRerankerBackendV1 {\n\t/** Score docs against the query; result[i] corresponds to docs[i]. */\n\tscore: (query: string, docs: string[]) => Promise<number[]>;\n}\n\nexport type TransformersRerankerLoadImpl = (\n\tmodelId: string,\n\tcacheDir: string,\n\tremoteHost: string,\n) => Promise<TransformersRerankerBackendV1>;\n\nconst DEFAULT_RERANKER_MODEL_ID = \"Xenova/bge-reranker-base\";\nconst DEFAULT_REMOTE_HOST = \"https://huggingface.co\";\n\n/** Structural view of the tokenizer call; returns the model-input object. */\ntype RerankTokenizerCall = (\n\ttexts: string[],\n\toptions: { text_pair: string[]; padding: boolean; truncation: boolean },\n) => unknown;\n\n/** Structural view of the sequence-classification forward pass. */\ntype RerankModelCall = (inputs: unknown) => Promise<{ logits: { data: ArrayLike<number> } }>;\n\nconst createDefaultTransformersLoadImpl = (moduleEntryPath?: string): TransformersRerankerLoadImpl => {\n\treturn async (modelId, cacheDir, remoteHost) => {\n\t\tconst transformers = await importHostOrBareModule<typeof import(\"@huggingface/transformers\")>(\n\t\t\tmoduleEntryPath ?? \"@huggingface/transformers\",\n\t\t);\n\t\ttransformers.env.cacheDir = cacheDir;\n\t\ttransformers.env.remoteHost = remoteHost;\n\t\tconst tokenizer = (await transformers.AutoTokenizer.from_pretrained(modelId)) as unknown as RerankTokenizerCall;\n\t\tconst model = (await transformers.AutoModelForSequenceClassification.from_pretrained(modelId, {\n\t\t\tdtype: \"q8\",\n\t\t})) as unknown as RerankModelCall;\n\t\treturn {\n\t\t\tscore: async (query, docs) => {\n\t\t\t\tconst inputs = tokenizer(\n\t\t\t\t\tdocs.map(() => query),\n\t\t\t\t\t{ text_pair: docs, padding: true, truncation: true },\n\t\t\t\t);\n\t\t\t\tconst output = await model(inputs);\n\t\t\t\treturn Array.from(output.logits.data);\n\t\t\t},\n\t\t};\n\t};\n};\n\n/**\n * Transformers.js-backed cross-encoder reranker (两档共用默认\n * Xenova/bge-reranker-base, 见文件头模型说明)。\n *\n * 镜像序列: 首次 rerank 时按 `remoteHosts` 顺序逐个尝试加载 (调用方通常传\n * [\"https://huggingface.co\", \"https://hf-mirror.com\"]); 某 host 加载抛错则\n * 尝试下一个, 全部失败时抛最后一个错误。host 确定后模型句柄只加载一次\n * (并发 rerank 共享同一 in-flight promise); 加载失败不缓存, 下次 rerank\n * 重新走完整序列, 失败原因原样保留, 不静默降级。\n *\n * `loadImpl` 是测试 seam: 注入后绕过真实加载, 签名\n * (modelId, cacheDir, remoteHost) => backend。\n */\nexport function createTransformersReranker(options: {\n\treadonly modelId?: string;\n\treadonly cacheDir: string;\n\treadonly remoteHosts?: readonly string[];\n\treadonly loadImpl?: TransformersRerankerLoadImpl;\n\t/** Host-resolved transformers entry path (install-store visibility); undefined = bare import. */\n\treadonly moduleEntryPath?: string;\n}): MemoryRerankerV1 {\n\tconst modelId = options.modelId ?? DEFAULT_RERANKER_MODEL_ID;\n\tconst cacheDir = options.cacheDir;\n\tconst remoteHosts = options.remoteHosts ?? [DEFAULT_REMOTE_HOST];\n\tconst load = options.loadImpl ?? createDefaultTransformersLoadImpl(options.moduleEntryPath);\n\n\tlet backendPromise: Promise<TransformersRerankerBackendV1> | null = null;\n\tconst loadBackend = (): Promise<TransformersRerankerBackendV1> => {\n\t\tconst existing = backendPromise;\n\t\tif (existing) {\n\t\t\treturn existing;\n\t\t}\n\t\tconst attempt = (async () => {\n\t\t\tlet lastError: unknown;\n\t\t\tfor (const remoteHost of remoteHosts) {\n\t\t\t\ttry {\n\t\t\t\t\treturn await load(modelId, cacheDir, remoteHost);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlastError = error;\n\t\t\t\t\t// 镜像轮换前清 0 字节残骸 (embedding-provider 同理, model-cache-hygiene)。\n\t\t\t\t\tpurgeZeroByteCacheArtifacts(join(cacheDir, modelId));\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lastError === undefined) throw new Error(`no remote hosts configured for model ${modelId}`);\n\t\t\tthrow lastError;\n\t\t})();\n\t\tbackendPromise = attempt;\n\t\tattempt.catch(() => {\n\t\t\t// 失败不缓存: 下次 rerank 重新走镜像序列 (显式失败, 不静默降级)。\n\t\t\tif (backendPromise === attempt) {\n\t\t\t\tbackendPromise = null;\n\t\t\t}\n\t\t});\n\t\treturn attempt;\n\t};\n\n\treturn {\n\t\tmodelId,\n\t\trerank: async (query, docs) => {\n\t\t\tif (docs.length === 0) {\n\t\t\t\t// 空 docs 直接返回, 不触发模型加载 (lazy-load 只发生在真正需要打分时)。\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst backend = await loadBackend();\n\t\t\treturn backend.score(query, [...docs]);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Memory Foundation — canonical atom, identity, and immutable snapshots (1C.1a).
3
+ *
4
+ * A canonical atom is the immutable committed fact. Derived state (attention,
5
+ * effective status, purgeAt) is NOT part of the atom; it is rebuilt from
6
+ * lifecycle events (1C.1b/1C.2). Everything returned to hosts and plugins is a
7
+ * detached, deep-frozen snapshot: mutating the input after `buildMemoryAtomV1`
8
+ * never changes the stored atom.
9
+ *
10
+ * Field semantics follow `docs/design/记忆系统设计.md` §4 (记录模型), which is the
11
+ * authoritative source for memory fields.
12
+ */
13
+ import type { JsonValue } from "@agent-forge/plugin-sdk";
14
+ /** Source of one memory: where the fact came from, optionally pinned by revision/digest. */
15
+ export interface MemorySourceRefV1 {
16
+ readonly kind: "session" | "entry" | "artifact" | "tool" | "state";
17
+ readonly id: string;
18
+ readonly revision?: string;
19
+ readonly digest?: string;
20
+ }
21
+ /** Namespace-scoped search facet. Facets are atom facts, never derived. */
22
+ export interface MemoryFacetV1 {
23
+ readonly namespace: string;
24
+ readonly schemaVersion: number;
25
+ readonly key: string;
26
+ readonly value: string;
27
+ }
28
+ /** Typed relation between atoms. Weights/validity belong to projections, not here. */
29
+ export interface MemoryRelationV1 {
30
+ readonly relationId: string;
31
+ readonly namespace: string;
32
+ readonly schemaVersion: number;
33
+ readonly kind: string;
34
+ readonly targetMemoryId?: string;
35
+ readonly targetRef?: MemorySourceRefV1;
36
+ readonly confidence?: number;
37
+ readonly relationRevision: string;
38
+ }
39
+ export interface MemoryTimeRangeV1 {
40
+ readonly from?: string;
41
+ readonly to?: string;
42
+ }
43
+ /** Applicability narrowing; empty means "applies without narrowing". */
44
+ export interface MemoryApplicabilityV1 {
45
+ readonly scenes?: readonly string[];
46
+ readonly projects?: readonly string[];
47
+ readonly tasks?: readonly string[];
48
+ readonly timeRange?: MemoryTimeRangeV1;
49
+ readonly exceptions?: readonly string[];
50
+ }
51
+ /** Preference envelope: the generic schema frozen for 1C; policy semantics come later. */
52
+ export interface MemoryPreferenceEnvelopeV1 {
53
+ readonly subject: "user" | "project" | "task" | "environment";
54
+ readonly key: string;
55
+ readonly preferredValue: JsonValue;
56
+ readonly alternatives?: readonly JsonValue[];
57
+ readonly scope: {
58
+ readonly level: "task" | "project" | "scene" | "timeRange" | "profile-private" | "user-default";
59
+ readonly scenes?: readonly string[];
60
+ readonly projects?: readonly string[];
61
+ readonly tasks?: readonly string[];
62
+ readonly timeRange?: MemoryTimeRangeV1;
63
+ readonly exceptions?: readonly string[];
64
+ };
65
+ readonly evidence: {
66
+ readonly class: "explicit" | "repeated_behavior" | "inferred";
67
+ readonly sourceRefs: readonly MemorySourceRefV1[];
68
+ readonly confidence: number;
69
+ };
70
+ readonly applicabilityConfidence: number;
71
+ readonly confirmedAt?: string;
72
+ }
73
+ /** Provenance for synthesis/compaction memories derived from other atoms. */
74
+ export interface MemoryProvenanceV1 {
75
+ readonly sourceMemoryIds: readonly string[];
76
+ readonly purgeGroupId: string;
77
+ readonly containsSourceContent: boolean;
78
+ }
79
+ export type MemoryScopeV1 = "session" | "cycle" | "long-term";
80
+ export type MemoryKindV1 = "observation" | "preference" | "fact" | "decision" | "constraint" | "inference" | "synthesis";
81
+ export type MemoryEvidenceClassV1 = "explicit" | "repeated_behavior" | "tool_or_test" | "derived";
82
+ /** The immutable canonical atom. Once committed, no field may change in place. */
83
+ export interface MemoryAtomV1<TPayload = JsonValue> {
84
+ readonly memoryId: string;
85
+ readonly contractVersion: string;
86
+ readonly schemaVersion: number;
87
+ readonly scope: MemoryScopeV1;
88
+ readonly retentionMode: string;
89
+ readonly owner: string;
90
+ readonly profileId: string;
91
+ readonly shareGroupId?: string;
92
+ /**
93
+ * Suite (方案) this atom belongs to (方案系统设计 §6.1/§11, M5). Absent
94
+ * (or protocol-level `null` on the wire, normalized to absent here) marks a
95
+ * legacy pre-M5 atom: such atoms are fail-safe INVISIBLE to every
96
+ * suite-scoped read and only readable through suite-unscoped queries. The
97
+ * one cross-suite exception is an explicitly promoted user-default
98
+ * preference (see {@link memorySuiteVisibility}).
99
+ */
100
+ readonly suiteId?: string;
101
+ readonly retentionPolicyVersion: string;
102
+ readonly memoryKind: MemoryKindV1;
103
+ readonly payload: TPayload;
104
+ readonly preference?: MemoryPreferenceEnvelopeV1;
105
+ readonly occurredAt: string;
106
+ readonly recordedAt: string;
107
+ readonly sourceRefs: readonly MemorySourceRefV1[];
108
+ readonly observationId: string;
109
+ readonly sessionRefs: readonly string[];
110
+ readonly agentInstanceRefs: readonly string[];
111
+ readonly projectRefs: readonly string[];
112
+ readonly subjectRefs: readonly string[];
113
+ readonly facets: readonly MemoryFacetV1[];
114
+ readonly relations: readonly MemoryRelationV1[];
115
+ readonly provenance?: MemoryProvenanceV1;
116
+ readonly confidence: number;
117
+ readonly importance: number;
118
+ readonly applicability?: MemoryApplicabilityV1;
119
+ readonly evidenceClass: MemoryEvidenceClassV1;
120
+ readonly contentRevision: string;
121
+ readonly writeReason: string;
122
+ }
123
+ /**
124
+ * Validates a canonical memory atom and returns a detached, deep-frozen
125
+ * snapshot. Unknown fields, wrong enums, non-JSON payloads, preference-kind
126
+ * atoms without a preference envelope, and identity fields (memoryId,
127
+ * observationId, contentRevision, owner) that are not non-empty strings are
128
+ * rejected.
129
+ */
130
+ export declare function validateMemoryAtomV1<TPayload = JsonValue>(value: unknown): MemoryAtomV1<TPayload>;
131
+ /** Builds a detached, frozen canonical atom (validate + freeze). */
132
+ export declare function buildMemoryAtomV1<TPayload = JsonValue>(atom: MemoryAtomV1<TPayload>): MemoryAtomV1<TPayload>;
133
+ /** FNV-1a 64-bit digest over the stable JSON serialization of `value`. */
134
+ export declare function memoryContentDigest(value: JsonValue): string;
135
+ /** Idempotent submission identity: the same (owner, observationId) is one observation. */
136
+ export declare function memoryObservationKey(owner: string, observationId: string): string;
137
+ /** Why a record was excluded from a suite-scoped read (读取边界惰性可观测). */
138
+ export type MemorySuiteVisibilityV1 =
139
+ /** The atom matches the queried suite, or is an explicitly promoted user-default preference. */
140
+ "visible"
141
+ /** Legacy (pre-M5) atom without a suiteId — fail-safe invisible to every suite. */
142
+ | "legacy"
143
+ /** The atom belongs to a different suite. */
144
+ | "foreign-suite";
145
+ /**
146
+ * Skipped-record counters attached to suite-scoped reads. The read boundary is
147
+ * lazy and observable (设计 §11 三段式): excluded atoms are counted, never
148
+ * silently dropped.
149
+ */
150
+ export interface MemorySuiteFilterStatsV1 {
151
+ /** Legacy (suiteId-less) atoms skipped as invisible to any suite. */
152
+ readonly legacySkipped: number;
153
+ /** Atoms bound to a different suiteId that were skipped. */
154
+ readonly foreignSuiteSkipped: number;
155
+ }
156
+ /**
157
+ * Suite-scoped visibility of one atom (方案系统设计 §6.1/§11):
158
+ * - `atom.suiteId === suiteId` → visible;
159
+ * - an explicitly promoted user-default preference (scope.level
160
+ * "user-default" WITH `confirmedAt` — the canonical explicit-confirmation
161
+ * marker set only by the explicit promotion channel) ignores the suiteId
162
+ * filter and is visible in every suite (§6.1 跨方案偏好);
163
+ * - absent suiteId (legacy/存量) → invisible to every suite (fail-safe
164
+ * default), counted via {@link MemorySuiteFilterStatsV1};
165
+ * - any other suiteId → invisible.
166
+ */
167
+ export declare function memorySuiteVisibility(atom: MemoryAtomV1, suiteId: string): MemorySuiteVisibilityV1;
168
+ //# sourceMappingURL=foundation.d.ts.map