recallnest 3.0.0 → 4.0.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 (246) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +251 -258
  3. package/README_CN.md +226 -247
  4. package/assets/ui/app.js +6 -1
  5. package/bin/recallnest.mjs +20 -37
  6. package/docs/api-reference.md +8 -0
  7. package/docs/architecture.md +18 -13
  8. package/eval/cases-canary.json +107 -9
  9. package/integrations/examples/claude-agent-sdk/memory-agent.ts +2 -0
  10. package/integrations/examples/langchain/memory-chain.py +4 -0
  11. package/integrations/examples/openai-agents-sdk/memory-agent.py +4 -0
  12. package/package.json +12 -3
  13. package/src/activity-counter.ts +1 -0
  14. package/src/adaptive-retrieval.ts +23 -0
  15. package/src/api-server.ts +13 -11
  16. package/src/asset-sync.ts +26 -2
  17. package/src/audit-log.ts +2 -0
  18. package/src/auto-gc.ts +4 -3
  19. package/src/belief-history.ts +137 -31
  20. package/src/capture-engine.ts +459 -71
  21. package/src/cascade-forget.ts +7 -3
  22. package/src/checkpoint-mirror.ts +417 -0
  23. package/src/cli.ts +574 -54
  24. package/src/conflict-engine.ts +43 -15
  25. package/src/consolidation-engine.ts +24 -3
  26. package/src/context-collapse-renderer.ts +8 -1
  27. package/src/context-composer-stable.ts +5 -1
  28. package/src/context-composer.ts +11 -2
  29. package/src/distill-lock.ts +246 -74
  30. package/src/dream-pipeline.ts +3 -1
  31. package/src/embedder.ts +24 -0
  32. package/src/env-config.ts +136 -0
  33. package/src/flock.ts +97 -0
  34. package/src/forget-engine.ts +342 -58
  35. package/src/gateway-server.ts +4 -0
  36. package/src/ingest-plan.ts +56 -0
  37. package/src/ingest.ts +294 -121
  38. package/src/kg-contradiction.ts +753 -0
  39. package/src/kg-extractor.ts +17 -0
  40. package/src/kg-store.ts +1 -1
  41. package/src/known-item-eval.ts +455 -0
  42. package/src/llm-client.ts +13 -26
  43. package/src/llm-consolidation.ts +2 -2
  44. package/src/mcp-tool-deps.ts +6 -0
  45. package/src/mcp-tools-advanced.ts +80 -45
  46. package/src/mcp-tools-core.ts +23 -17
  47. package/src/mcp-tools-governance.ts +13 -7
  48. package/src/memory-assets.ts +144 -8
  49. package/src/memory-boundaries.ts +19 -0
  50. package/src/memory-evolution.ts +9 -0
  51. package/src/memory-lint.ts +1 -1
  52. package/src/memory-output.ts +311 -14
  53. package/src/memory-reconcile.ts +1437 -0
  54. package/src/memory-schema.ts +11 -0
  55. package/src/memory-store-port.ts +1 -0
  56. package/src/minis-archive.ts +151 -0
  57. package/src/noise-filter.ts +15 -2
  58. package/src/pii-detector.ts +31 -1
  59. package/src/pivot-apply.ts +55 -5
  60. package/src/pivot-evidence.ts +128 -0
  61. package/src/ppr-traversal.ts +25 -1
  62. package/src/preference-matcher.ts +40 -28
  63. package/src/prospective-memory.ts +7 -4
  64. package/src/retriever.ts +297 -57
  65. package/src/retroactive-boost.ts +3 -2
  66. package/src/runtime-config.ts +15 -1
  67. package/src/scope-policy.ts +1 -0
  68. package/src/session-distiller.ts +6 -2
  69. package/src/session-engine.ts +11 -4
  70. package/src/session-output.ts +20 -1
  71. package/src/session-schema.ts +5 -2
  72. package/src/session-store.ts +59 -12
  73. package/src/skill-promotion.ts +67 -1
  74. package/src/store-receipts.ts +109 -0
  75. package/src/store-write-budget.ts +37 -0
  76. package/src/store.ts +298 -57
  77. package/src/text-fingerprint.ts +25 -0
  78. package/src/trigger-store.ts +443 -0
  79. package/src/ui-server.ts +22 -21
  80. package/src/version-manager.ts +19 -5
  81. package/src/write-verifier.ts +16 -11
  82. package/src/__tests__/access-tracker-novelty.test.ts +0 -324
  83. package/src/__tests__/activity-counter.test.ts +0 -125
  84. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  85. package/src/__tests__/admission-control.test.ts +0 -206
  86. package/src/__tests__/age-label.test.ts +0 -33
  87. package/src/__tests__/anchor-generator.test.ts +0 -67
  88. package/src/__tests__/api-server-security.test.ts +0 -102
  89. package/src/__tests__/auto-consolidation.test.ts +0 -168
  90. package/src/__tests__/auto-recall.test.ts +0 -131
  91. package/src/__tests__/babel-integration.test.ts +0 -144
  92. package/src/__tests__/batch-dedup.test.ts +0 -65
  93. package/src/__tests__/belief-history.test.ts +0 -220
  94. package/src/__tests__/canary-eval.test.ts +0 -114
  95. package/src/__tests__/capture-engine.test.ts +0 -1328
  96. package/src/__tests__/capture-heuristic.test.ts +0 -171
  97. package/src/__tests__/cascade-forget.test.ts +0 -210
  98. package/src/__tests__/category-min-scores.test.ts +0 -116
  99. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  100. package/src/__tests__/circuit-breaker.test.ts +0 -99
  101. package/src/__tests__/cluster-consolidation.test.ts +0 -1236
  102. package/src/__tests__/confidence-metadata.test.ts +0 -125
  103. package/src/__tests__/confidence-tracker.test.ts +0 -261
  104. package/src/__tests__/conflict-advisor.test.ts +0 -206
  105. package/src/__tests__/conflict-escalation.test.ts +0 -149
  106. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  107. package/src/__tests__/conflict-output.test.ts +0 -85
  108. package/src/__tests__/conflict-store.test.ts +0 -418
  109. package/src/__tests__/connector-v1.test.ts +0 -181
  110. package/src/__tests__/consolidation-engine.test.ts +0 -520
  111. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  112. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  113. package/src/__tests__/context-composer-pins.test.ts +0 -64
  114. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  115. package/src/__tests__/context-reconstructor.test.ts +0 -65
  116. package/src/__tests__/context-renderer.test.ts +0 -91
  117. package/src/__tests__/continuity-eval.test.ts +0 -275
  118. package/src/__tests__/continuity-schema.test.ts +0 -97
  119. package/src/__tests__/contracts.test.ts +0 -262
  120. package/src/__tests__/conversation-importer.test.ts +0 -692
  121. package/src/__tests__/core-recall-eval.test.ts +0 -235
  122. package/src/__tests__/core-summary.test.ts +0 -109
  123. package/src/__tests__/data-checkup.test.ts +0 -304
  124. package/src/__tests__/decay-exemption.test.ts +0 -129
  125. package/src/__tests__/dedup-actions.test.ts +0 -58
  126. package/src/__tests__/dedup-guard.test.ts +0 -39
  127. package/src/__tests__/deterministic-id.test.ts +0 -39
  128. package/src/__tests__/distill-lock.test.ts +0 -305
  129. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  130. package/src/__tests__/doctor.test.ts +0 -319
  131. package/src/__tests__/dream-pipeline.test.ts +0 -820
  132. package/src/__tests__/dream-scope-isolation.test.ts +0 -386
  133. package/src/__tests__/dream-write-batching.test.ts +0 -211
  134. package/src/__tests__/drill-down.test.ts +0 -108
  135. package/src/__tests__/embedder.test.ts +0 -122
  136. package/src/__tests__/emotion-decay.test.ts +0 -46
  137. package/src/__tests__/emotion-detector.test.ts +0 -56
  138. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  139. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  140. package/src/__tests__/entity-resolver.test.ts +0 -58
  141. package/src/__tests__/env-config-invariant.test.ts +0 -80
  142. package/src/__tests__/env-config.test.ts +0 -211
  143. package/src/__tests__/error-signature-boost.test.ts +0 -87
  144. package/src/__tests__/error-signature.test.ts +0 -95
  145. package/src/__tests__/error-taxonomy.test.ts +0 -129
  146. package/src/__tests__/ethics-layer.test.ts +0 -476
  147. package/src/__tests__/event-segmenter.test.ts +0 -153
  148. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  149. package/src/__tests__/frequency-tracker.test.ts +0 -113
  150. package/src/__tests__/freshness.test.ts +0 -186
  151. package/src/__tests__/graph-export.test.ts +0 -489
  152. package/src/__tests__/hotness-blend.test.ts +0 -283
  153. package/src/__tests__/ingest-chunking.test.ts +0 -44
  154. package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
  155. package/src/__tests__/ingest.test.ts +0 -360
  156. package/src/__tests__/interference-detector.test.ts +0 -203
  157. package/src/__tests__/kg-extractor.test.ts +0 -192
  158. package/src/__tests__/kg-store.test.ts +0 -300
  159. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  160. package/src/__tests__/llm-consolidation.test.ts +0 -226
  161. package/src/__tests__/maintenance-locks.test.ts +0 -197
  162. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  163. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  164. package/src/__tests__/memory-boundaries.test.ts +0 -282
  165. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
  166. package/src/__tests__/memory-evolution.test.ts +0 -271
  167. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  168. package/src/__tests__/memory-lint.test.ts +0 -421
  169. package/src/__tests__/memory-output.test.ts +0 -224
  170. package/src/__tests__/memory-promotion.test.ts +0 -315
  171. package/src/__tests__/memory-utility.test.ts +0 -256
  172. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  173. package/src/__tests__/multi-vector.test.ts +0 -242
  174. package/src/__tests__/narrative-architecture.test.ts +0 -456
  175. package/src/__tests__/noise-filter.test.ts +0 -205
  176. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  177. package/src/__tests__/obsidian-connector.test.ts +0 -363
  178. package/src/__tests__/omitted-reporting.test.ts +0 -98
  179. package/src/__tests__/openai-contract.test.ts +0 -513
  180. package/src/__tests__/pending-review.test.ts +0 -77
  181. package/src/__tests__/pii-redact.test.ts +0 -333
  182. package/src/__tests__/pivot-apply.test.ts +0 -373
  183. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  184. package/src/__tests__/pivot-distill.test.ts +0 -1178
  185. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  186. package/src/__tests__/ppr-traversal.test.ts +0 -256
  187. package/src/__tests__/prediction-engine.test.ts +0 -349
  188. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  189. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  190. package/src/__tests__/preference-matcher.test.ts +0 -282
  191. package/src/__tests__/preference-slots.test.ts +0 -205
  192. package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
  193. package/src/__tests__/prospective-memory.test.ts +0 -283
  194. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  195. package/src/__tests__/query-entity-detector.test.ts +0 -171
  196. package/src/__tests__/query-expander-alias.test.ts +0 -146
  197. package/src/__tests__/query-expander.test.ts +0 -34
  198. package/src/__tests__/recall-gate.test.ts +0 -266
  199. package/src/__tests__/recall-governor.test.ts +0 -227
  200. package/src/__tests__/recall-mode.test.ts +0 -64
  201. package/src/__tests__/resolve-db-path.test.ts +0 -130
  202. package/src/__tests__/resolve-tier.test.ts +0 -64
  203. package/src/__tests__/result-synthesizer.test.ts +0 -198
  204. package/src/__tests__/resume-context.test.ts +0 -4898
  205. package/src/__tests__/retrieval-eval.test.ts +0 -163
  206. package/src/__tests__/retrieval-trace.test.ts +0 -213
  207. package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
  208. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  209. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  210. package/src/__tests__/retroactive-boost.test.ts +0 -223
  211. package/src/__tests__/salience-scoring.test.ts +0 -106
  212. package/src/__tests__/scope-inventory.test.ts +0 -200
  213. package/src/__tests__/scope-policy.test.ts +0 -69
  214. package/src/__tests__/scope-suggester.test.ts +0 -80
  215. package/src/__tests__/session-checkpoint.test.ts +0 -291
  216. package/src/__tests__/session-distiller.test.ts +0 -640
  217. package/src/__tests__/session-gc.test.ts +0 -215
  218. package/src/__tests__/skill-engine.test.ts +0 -552
  219. package/src/__tests__/skill-promotion.test.ts +0 -654
  220. package/src/__tests__/skill-verifier.test.ts +0 -122
  221. package/src/__tests__/source-heartbeat.test.ts +0 -254
  222. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  223. package/src/__tests__/store-patch-metadata.test.ts +0 -235
  224. package/src/__tests__/store-read-consistency.test.ts +0 -122
  225. package/src/__tests__/store-schema-migration.test.ts +0 -87
  226. package/src/__tests__/store-scope-audit.test.ts +0 -81
  227. package/src/__tests__/store-upsert.test.ts +0 -201
  228. package/src/__tests__/supersede-chain.test.ts +0 -154
  229. package/src/__tests__/synthesis-contract.test.ts +0 -252
  230. package/src/__tests__/synthesis-promotion.test.ts +0 -369
  231. package/src/__tests__/temporal-parser.test.ts +0 -241
  232. package/src/__tests__/temporal-validity.test.ts +0 -160
  233. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  234. package/src/__tests__/term-registry.test.ts +0 -121
  235. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  236. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  237. package/src/__tests__/topic-tag.test.ts +0 -113
  238. package/src/__tests__/trusted-memory.test.ts +0 -867
  239. package/src/__tests__/ui-server-security.test.ts +0 -93
  240. package/src/__tests__/usage-tracker.test.ts +0 -287
  241. package/src/__tests__/utility-weight.test.ts +0 -57
  242. package/src/__tests__/verbatim-self-recall.test.ts +0 -279
  243. package/src/__tests__/version-manager.test.ts +0 -248
  244. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  245. package/src/__tests__/workflow-observation.test.ts +0 -433
  246. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,211 +0,0 @@
1
- import { describe, expect, it, afterEach, beforeEach } from "bun:test";
2
-
3
- import * as envConfig from "../env-config.js";
4
-
5
- // Every RECALLNEST_* key this suite mutates. Snapshot + restore so we never leak
6
- // env state into sibling test files (the whole `bun test` run shares one process).
7
- const KEYS = [
8
- "RECALLNEST_MULTI_VECTOR",
9
- "RECALLNEST_EMOTION_SCORING",
10
- "RECALLNEST_PREDICTIVE_MEMORY",
11
- "RECALLNEST_SYNTHESIZE",
12
- "RECALLNEST_LLM_CONSOLIDATION",
13
- "RECALLNEST_CONSTRUCTIVE_RETRIEVAL",
14
- "RECALLNEST_NARRATIVE_MODE",
15
- "RECALLNEST_KG_MODE",
16
- "RECALLNEST_CORE_SUMMARY",
17
- "RECALLNEST_ERROR_SIGNATURE_BOOST",
18
- "RECALLNEST_USAGE_DECAY",
19
- "RECALLNEST_DATA_DIR",
20
- "RECALLNEST_MCP_TIER",
21
- "RECALLNEST_RECALL_MODE",
22
- "RECALLNEST_UI_PORT",
23
- "RECALLNEST_API_PORT",
24
- "RECALLNEST_DREAM_BUDGET_MS",
25
- "RECALLNEST_SYNTHESIS_MODEL",
26
- ];
27
-
28
- const saved: Record<string, string | undefined> = {};
29
-
30
- beforeEach(() => {
31
- for (const key of KEYS) {
32
- saved[key] = process.env[key];
33
- delete process.env[key];
34
- }
35
- });
36
-
37
- afterEach(() => {
38
- for (const key of KEYS) {
39
- if (saved[key] === undefined) delete process.env[key];
40
- else process.env[key] = saved[key];
41
- }
42
- });
43
-
44
- describe('env-config boolean flags — strict === "true"', () => {
45
- const accessors: Array<[string, () => boolean, string]> = [
46
- ["multiVector", envConfig.multiVector, "RECALLNEST_MULTI_VECTOR"],
47
- ["emotionScoring", envConfig.emotionScoring, "RECALLNEST_EMOTION_SCORING"],
48
- ["predictiveMemory", envConfig.predictiveMemory, "RECALLNEST_PREDICTIVE_MEMORY"],
49
- ["synthesize", envConfig.synthesize, "RECALLNEST_SYNTHESIZE"],
50
- ["llmConsolidation", envConfig.llmConsolidation, "RECALLNEST_LLM_CONSOLIDATION"],
51
- ["constructiveRetrieval", envConfig.constructiveRetrieval, "RECALLNEST_CONSTRUCTIVE_RETRIEVAL"],
52
- ["narrativeMode", envConfig.narrativeMode, "RECALLNEST_NARRATIVE_MODE"],
53
- ["kgMode", envConfig.kgMode, "RECALLNEST_KG_MODE"],
54
- ["coreSummary", envConfig.coreSummary, "RECALLNEST_CORE_SUMMARY"],
55
- ["errorSignatureBoost", envConfig.errorSignatureBoost, "RECALLNEST_ERROR_SIGNATURE_BOOST"],
56
- ["usageDecay", envConfig.usageDecay, "RECALLNEST_USAGE_DECAY"],
57
- ];
58
-
59
- for (const [name, accessor, key] of accessors) {
60
- it(`${name}: false unless the value is exactly "true"`, () => {
61
- delete process.env[key];
62
- expect(accessor()).toBe(false);
63
-
64
- process.env[key] = "true";
65
- expect(accessor()).toBe(true);
66
-
67
- // No trim, no case-folding, no truthy coercion — only the literal "true".
68
- for (const fuzzy of ["True", "TRUE", " true ", "1", "yes", "false", ""]) {
69
- process.env[key] = fuzzy;
70
- expect(accessor()).toBe(false);
71
- }
72
- });
73
- }
74
- });
75
-
76
- describe("env-config string settings — || default (empty string falls through)", () => {
77
- it('dataDir defaults to "data" when unset or empty', () => {
78
- delete process.env.RECALLNEST_DATA_DIR;
79
- expect(envConfig.dataDir()).toBe("data");
80
-
81
- process.env.RECALLNEST_DATA_DIR = ""; // || not ??
82
- expect(envConfig.dataDir()).toBe("data");
83
-
84
- process.env.RECALLNEST_DATA_DIR = "/tmp/custom-data";
85
- expect(envConfig.dataDir()).toBe("/tmp/custom-data");
86
- });
87
-
88
- it('mcpTier defaults to "advanced"; valid passes through; illegal value kept verbatim', () => {
89
- delete process.env.RECALLNEST_MCP_TIER;
90
- expect(envConfig.mcpTier()).toBe("advanced");
91
-
92
- process.env.RECALLNEST_MCP_TIER = ""; // || not ??
93
- expect(envConfig.mcpTier()).toBe("advanced");
94
-
95
- process.env.RECALLNEST_MCP_TIER = "core";
96
- expect(envConfig.mcpTier()).toBe("core");
97
-
98
- // No runtime validation — an unknown non-empty value is preserved as-is
99
- // (downstream shouldRegisterTool relies on this; do not coerce to default).
100
- process.env.RECALLNEST_MCP_TIER = "garbage";
101
- expect(envConfig.mcpTier()).toBe("garbage");
102
- });
103
- });
104
-
105
- describe("env-config raw passthrough — caller validates / clamps / falls back", () => {
106
- const rawAccessors: Array<[string, () => string | undefined, string]> = [
107
- ["recallModeRaw", envConfig.recallModeRaw, "RECALLNEST_RECALL_MODE"],
108
- ["uiPortRaw", envConfig.uiPortRaw, "RECALLNEST_UI_PORT"],
109
- ["apiPortRaw", envConfig.apiPortRaw, "RECALLNEST_API_PORT"],
110
- ];
111
-
112
- for (const [name, accessor, key] of rawAccessors) {
113
- it(`${name}: returns the raw env value, undefined when unset`, () => {
114
- delete process.env[key];
115
- expect(accessor()).toBeUndefined();
116
-
117
- process.env[key] = "verbatim-123";
118
- expect(accessor()).toBe("verbatim-123");
119
-
120
- // Raw passthrough: empty string is NOT defaulted here — the caller decides.
121
- process.env[key] = "";
122
- expect(accessor()).toBe("");
123
- });
124
- }
125
- });
126
-
127
- describe("env-config recallModeRaw — must not pre-default (preserves resolveRecallMode config fallback)", () => {
128
- it('returns the raw value or undefined, never a hardcoded mode like "summary"', () => {
129
- delete process.env.RECALLNEST_RECALL_MODE;
130
- // Must be undefined, NOT "summary" — resolveRecallMode owns the config fallback.
131
- expect(envConfig.recallModeRaw()).toBeUndefined();
132
-
133
- process.env.RECALLNEST_RECALL_MODE = "light";
134
- expect(envConfig.recallModeRaw()).toBe("light");
135
-
136
- // Invalid modes pass through verbatim — the whitelist check lives in resolveRecallMode.
137
- process.env.RECALLNEST_RECALL_MODE = "bogus";
138
- expect(envConfig.recallModeRaw()).toBe("bogus");
139
- });
140
- });
141
-
142
- describe("env-config accessors are lazy (read at call time, never frozen)", () => {
143
- it("reflect runtime mutation rather than a module-load snapshot", () => {
144
- delete process.env.RECALLNEST_EMOTION_SCORING;
145
- expect(envConfig.emotionScoring()).toBe(false);
146
-
147
- process.env.RECALLNEST_EMOTION_SCORING = "true";
148
- expect(envConfig.emotionScoring()).toBe(true); // would fail if cached at import
149
-
150
- delete process.env.RECALLNEST_EMOTION_SCORING;
151
- expect(envConfig.emotionScoring()).toBe(false);
152
- });
153
- });
154
-
155
- describe("env-config dreamBudgetMs — the two silent failure modes it exists to stop", () => {
156
- const FALLBACK = 2 * 60 * 60 * 1000;
157
-
158
- it("falls back when unset", () => {
159
- delete process.env.RECALLNEST_DREAM_BUDGET_MS;
160
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
161
- });
162
-
163
- it('empty string does NOT become 0 (old `?? default` let "" through → Number("") === 0 → every scope skipped)', () => {
164
- process.env.RECALLNEST_DREAM_BUDGET_MS = "";
165
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
166
- process.env.RECALLNEST_DREAM_BUDGET_MS = " ";
167
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
168
- });
169
-
170
- it('non-numeric does NOT become NaN (NaN deadline makes `Date.now() > deadline` permanently false → budget guard gone)', () => {
171
- for (const bad of ["2h", "abc", "30min", "1_000"]) {
172
- process.env.RECALLNEST_DREAM_BUDGET_MS = bad;
173
- const ms = envConfig.dreamBudgetMs(FALLBACK);
174
- expect(Number.isFinite(ms)).toBe(true);
175
- expect(ms).toBe(FALLBACK);
176
- // The regression this guards: a NaN budget silently disables the deadline.
177
- expect(Date.now() > Date.now() + ms).toBe(false);
178
- }
179
- });
180
-
181
- it("rejects zero and negatives (they would expire the deadline instantly)", () => {
182
- for (const bad of ["0", "-1", "-60000"]) {
183
- process.env.RECALLNEST_DREAM_BUDGET_MS = bad;
184
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(FALLBACK);
185
- }
186
- });
187
-
188
- it("accepts a valid override", () => {
189
- process.env.RECALLNEST_DREAM_BUDGET_MS = "900000";
190
- expect(envConfig.dreamBudgetMs(FALLBACK)).toBe(900_000);
191
- });
192
- });
193
-
194
- describe("env-config synthesisModel — dream 合成的模型旋钮", () => {
195
- it("未设 → undefined(调用方回落到 config.llm.model,保持老行为)", () => {
196
- delete process.env.RECALLNEST_SYNTHESIS_MODEL;
197
- expect(envConfig.synthesisModel()).toBeUndefined();
198
- });
199
-
200
- it("空串 / 纯空白也算未设,不会把模型名设成空字符串", () => {
201
- for (const blank of ["", " ", "\t"]) {
202
- process.env.RECALLNEST_SYNTHESIS_MODEL = blank;
203
- expect(envConfig.synthesisModel()).toBeUndefined();
204
- }
205
- });
206
-
207
- it("正常值去空白后返回", () => {
208
- process.env.RECALLNEST_SYNTHESIS_MODEL = " qwen-plus ";
209
- expect(envConfig.synthesisModel()).toBe("qwen-plus");
210
- });
211
- });
@@ -1,87 +0,0 @@
1
- import { describe, expect, it, afterEach, beforeEach } from "bun:test";
2
-
3
- import { applyErrorSignatureBoost } from "../retriever.js";
4
- import type { RetrievalResult } from "../retriever.js";
5
- import { extractErrorSignatures } from "../error-signature.js";
6
-
7
- const FLAG = "RECALLNEST_ERROR_SIGNATURE_BOOST";
8
-
9
- function makeResult(opts: { category: string; score: number; sigs?: string[] }): RetrievalResult {
10
- const metadata = opts.sigs ? JSON.stringify({ error_signature: opts.sigs }) : "{}";
11
- return {
12
- entry: {
13
- id: "m1",
14
- text: "x",
15
- scope: "test",
16
- category: opts.category,
17
- timestamp: "2026-06-13T00:00:00.000Z",
18
- metadata,
19
- },
20
- score: opts.score,
21
- sources: {},
22
- } as unknown as RetrievalResult;
23
- }
24
-
25
- let saved: string | undefined;
26
- beforeEach(() => {
27
- saved = process.env[FLAG];
28
- delete process.env[FLAG];
29
- });
30
- afterEach(() => {
31
- if (saved === undefined) delete process.env[FLAG];
32
- else process.env[FLAG] = saved;
33
- });
34
-
35
- describe("applyErrorSignatureBoost — A1 检索端精确召回 boost", () => {
36
- const QUERY = "build broke: xmlsec1 not found";
37
- const matchingSig = () => extractErrorSignatures({ problem: QUERY })[0];
38
-
39
- it("flag off(默认)→ 原样返回同一引用、分数不变", () => {
40
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
41
- const out = applyErrorSignatureBoost(input, QUERY);
42
- expect(out).toBe(input); // no-op,bit-identical
43
- expect(out[0].score).toBe(0.4);
44
- });
45
-
46
- it("flag on + 指纹命中 case → 乘法 boost (×1.5)", () => {
47
- process.env[FLAG] = "true";
48
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
49
- const out = applyErrorSignatureBoost(input, QUERY);
50
- expect(out[0].score).toBeCloseTo(0.6, 6); // 0.4 * 1.5
51
- });
52
-
53
- it("flag on + 命中但高分 → clamp 到 1、不溢出", () => {
54
- process.env[FLAG] = "true";
55
- const input = [makeResult({ category: "cases", score: 0.8, sigs: [matchingSig()] })];
56
- const out = applyErrorSignatureBoost(input, QUERY);
57
- expect(out[0].score).toBe(1); // 0.8 * 1.5 = 1.2 → clamp 1
58
- });
59
-
60
- it("flag on + 非 case category(patterns)→ 不 boost", () => {
61
- process.env[FLAG] = "true";
62
- const input = [makeResult({ category: "patterns", score: 0.4, sigs: [matchingSig()] })];
63
- const out = applyErrorSignatureBoost(input, QUERY);
64
- expect(out[0].score).toBe(0.4);
65
- });
66
-
67
- it("flag on + case 但指纹不重叠 → 不 boost", () => {
68
- process.env[FLAG] = "true";
69
- const input = [makeResult({ category: "cases", score: 0.4, sigs: ["zzz_nonexistent_signature_xyz"] })];
70
- const out = applyErrorSignatureBoost(input, QUERY);
71
- expect(out[0].score).toBe(0.4);
72
- });
73
-
74
- it("flag on + case 无 error_signature metadata → 不 boost", () => {
75
- process.env[FLAG] = "true";
76
- const input = [makeResult({ category: "cases", score: 0.4 })];
77
- const out = applyErrorSignatureBoost(input, QUERY);
78
- expect(out[0].score).toBe(0.4);
79
- });
80
-
81
- it("flag on + query 抽不出错误指纹 → 原样返回同一引用", () => {
82
- process.env[FLAG] = "true";
83
- const input = [makeResult({ category: "cases", score: 0.4, sigs: [matchingSig()] })];
84
- const out = applyErrorSignatureBoost(input, "今天天气不错,喝杯咖啡");
85
- expect(out).toBe(input);
86
- });
87
- });
@@ -1,95 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { extractErrorSignatures, MAX_SIGNATURES } from "../error-signature.js";
3
-
4
- describe("extractErrorSignatures — English patterns", () => {
5
- test("bare 'X not found' (no colon) is extracted — MemOS 原 6 正则会漏", () => {
6
- const sigs = extractErrorSignatures({ problem: "Build broke: xmlsec1 not found" });
7
- expect(sigs.some(s => s.includes("xmlsec1 not found"))).toBe(true);
8
- });
9
-
10
- test("'exit code N' is extracted", () => {
11
- const sigs = extractErrorSignatures({ problem: "Deploy failed with exit code 1" });
12
- expect(sigs.some(s => s.includes("exit code 1"))).toBe(true);
13
- });
14
-
15
- test("'<Name>Error: body' extracted and ranks top by specificity", () => {
16
- const sigs = extractErrorSignatures({ problem: "NameError: name foo is not defined" });
17
- expect(sigs.some(s => s.includes("NameError"))).toBe(true);
18
- expect(sigs[0]).toContain("NameError");
19
- });
20
-
21
- test("'No such file or directory' standalone is extracted (case-insensitive)", () => {
22
- const sigs = extractErrorSignatures({ problem: "open config: No such file or directory" });
23
- expect(sigs.some(s => /No such file or directory/i.test(s))).toBe(true);
24
- });
25
-
26
- test("bare 3-digit numbers in benign text do NOT false-match as HTTP error", () => {
27
- const sigs = extractErrorSignatures({ problem: "deployed 500 records and processed 404 rows in the weekly batch" });
28
- expect(sigs.some(s => s.includes("500 records") || s.includes("404 rows"))).toBe(false);
29
- });
30
-
31
- test("real HTTP status phrase is still extracted after tightening", () => {
32
- const sigs = extractErrorSignatures({ problem: "API call returned 500 Internal Server Error on submit" });
33
- expect(sigs.some(s => s.includes("500 Internal"))).toBe(true);
34
- });
35
- });
36
-
37
- describe("extractErrorSignatures — Chinese (MemOS 原版会被 alpha 检查滤掉)", () => {
38
- test("中文错误现象被抽取", () => {
39
- const sigs = extractErrorSignatures({ problem: "部署时权限被拒绝,找不到文件" });
40
- expect(sigs.some(s => s.includes("找不到") || s.includes("权限被拒"))).toBe(true);
41
- });
42
-
43
- test("中文短片段(<6 字符但 ≥3 中文字)保留", () => {
44
- const sigs = extractErrorSignatures({ problem: "找不到模块" });
45
- expect(sigs.length).toBeGreaterThan(0);
46
- });
47
- });
48
-
49
- describe("extractErrorSignatures — input gating", () => {
50
- test("outcome 成功态(无失败词)不被纳入,不抽反向信号", () => {
51
- const sigs = extractErrorSignatures({
52
- problem: "ImportError: no module named requests",
53
- outcome: "exit code 0 部署成功",
54
- });
55
- expect(sigs.some(s => s.includes("ImportError"))).toBe(true);
56
- expect(sigs.some(s => s.includes("exit code 0"))).toBe(false);
57
- });
58
-
59
- test("context 参与抽取(原始 stderr 常在 context)", () => {
60
- const sigs = extractErrorSignatures({
61
- problem: "服务起不来",
62
- context: "stderr: pg_config: command not found",
63
- });
64
- expect(sigs.some(s => s.includes("pg_config") || s.includes("command not found"))).toBe(true);
65
- });
66
-
67
- test("结果不超过 MAX_SIGNATURES 条", () => {
68
- const sigs = extractErrorSignatures({
69
- problem: "NameError: a. TypeError: b. ValueError: c. KeyError: d. exit code 1. 404 Not Found. 权限被拒绝.",
70
- });
71
- expect(sigs.length).toBeLessThanOrEqual(MAX_SIGNATURES);
72
- });
73
- });
74
-
75
- describe("extractErrorSignatures — 过泛中文词收紧(P3-A 检索端噪声治理)", () => {
76
- const BARE_GENERIC = /^(?:报错|出错|错误|异常|失败|超时|崩溃|无法|未定义|不能)[了的过着吧呢啊啦哦呀嘛。,!?!?,\s]*$/;
77
-
78
- test("裸过泛词 + 语气助词不被抽成指纹(失败了 / 超时了 / 崩溃了 / 报错了)", () => {
79
- for (const bare of ["部署失败了", "请求超时了", "程序崩溃了", "系统报错了"]) {
80
- const sigs = extractErrorSignatures({ problem: bare });
81
- expect(sigs.some(s => BARE_GENERIC.test(s.trim()))).toBe(false);
82
- }
83
- });
84
-
85
- test("过泛词带具体锚点(英文 / 路径 / 多位数字)时保留", () => {
86
- const sigs = extractErrorSignatures({ problem: "操作失败:xmlsec1 not found" });
87
- expect(sigs.some(s => s.includes("xmlsec1"))).toBe(true);
88
- });
89
-
90
- test("具体中文短语不受收紧影响(找不到 / 权限被拒 / 连接失败)", () => {
91
- expect(extractErrorSignatures({ problem: "找不到模块" }).length).toBeGreaterThan(0);
92
- expect(extractErrorSignatures({ problem: "权限被拒绝" }).some(s => s.includes("权限被拒"))).toBe(true);
93
- expect(extractErrorSignatures({ problem: "数据库连接失败" }).some(s => s.includes("连接失败"))).toBe(true);
94
- });
95
- });
@@ -1,129 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { classifyError, toErrorResult, runToolSafely } from "../error-taxonomy.js";
4
-
5
- // P-error-taxonomy: 借鉴 RepoPrompt CE 的 error taxonomy。
6
- // 每种失败标 {reasonCode, retryable, responsibility},让 agent 判断重试/改参/上报。
7
-
8
- describe("classifyError", () => {
9
- it("classifies transport errors as retryable / transport", () => {
10
- for (const m of ["ECONNRESET", "socket hang up", "fetch failed", "request timed out", "ETIMEDOUT"]) {
11
- const c = classifyError(new Error(m));
12
- expect(c.reasonCode).toBe("transport_error");
13
- expect(c.retryable).toBe(true);
14
- expect(c.responsibility).toBe("transport");
15
- }
16
- });
17
-
18
- it("classifies rate-limit / overload as retryable / peer", () => {
19
- for (const m of ["429 Too Many Requests", "rate limit exceeded", "529 overloaded", "quota exceeded"]) {
20
- const c = classifyError(new Error(m));
21
- expect(c.reasonCode).toBe("upstream_overloaded");
22
- expect(c.retryable).toBe(true);
23
- expect(c.responsibility).toBe("peer");
24
- }
25
- });
26
-
27
- it("classifies auth failures as non-retryable / host", () => {
28
- const c = classifyError(new Error("401 Unauthorized: invalid api key"));
29
- expect(c.reasonCode).toBe("upstream_auth");
30
- expect(c.retryable).toBe(false);
31
- expect(c.responsibility).toBe("host");
32
- });
33
-
34
- it("classifies missing-scope as non-retryable / peer", () => {
35
- const c = classifyError(new Error("search_memory requires a scope. Pass scope explicitly, provide sessionId..."));
36
- expect(c.reasonCode).toBe("scope_required");
37
- expect(c.retryable).toBe(false);
38
- expect(c.responsibility).toBe("peer");
39
- });
40
-
41
- it("classifies validation errors as invalid_input / peer", () => {
42
- const c = classifyError(new Error("validation failed: expected string received number"));
43
- expect(c.reasonCode).toBe("invalid_input");
44
- expect(c.responsibility).toBe("peer");
45
- });
46
-
47
- it("classifies store errors as app", () => {
48
- const c = classifyError(new Error("LanceDB table not found"));
49
- expect(c.reasonCode).toBe("store_error");
50
- expect(c.responsibility).toBe("app");
51
- });
52
-
53
- it("classifies config/env-missing as non-retryable / host (P2-1/P2-3)", () => {
54
- const c = classifyError(new Error("Environment variable OPENAI_API_KEY not set"));
55
- expect(c.reasonCode).toBe("config_missing");
56
- expect(c.retryable).toBe(false);
57
- expect(c.responsibility).toBe("host");
58
- });
59
-
60
- it("classifies bad memory references as invalid_memory_ref / peer — caller-side (P2-3)", () => {
61
- for (const m of [
62
- "Invalid memory ID format: bad",
63
- 'Ambiguous prefix "12345678" matches 2+ memories. Use a longer prefix or full ID.',
64
- "Memory 12345678 is outside accessible scopes",
65
- ]) {
66
- const c = classifyError(new Error(m));
67
- expect(c.reasonCode).toBe("invalid_memory_ref");
68
- expect(c.responsibility).toBe("peer");
69
- }
70
- });
71
-
72
- it("falls back to internal_error / app / non-retryable for unknown errors", () => {
73
- const c = classifyError(new Error("something totally unexpected happened"));
74
- expect(c.reasonCode).toBe("internal_error");
75
- expect(c.retryable).toBe(false);
76
- expect(c.responsibility).toBe("app");
77
- });
78
-
79
- it("handles non-Error throwables", () => {
80
- const c = classifyError("plain string failure");
81
- expect(c.reasonCode).toBe("internal_error");
82
- expect(c.responsibility).toBe("app");
83
- });
84
- });
85
-
86
- describe("toErrorResult", () => {
87
- it("produces an isError result with a machine-readable classification block", () => {
88
- const r = toErrorResult("search_memory", new Error("ECONNRESET"));
89
- expect(r.isError).toBe(true);
90
- expect(r.content[0].type).toBe("text");
91
- const text = r.content[0].text;
92
- expect(text).toContain('Tool "search_memory" failed');
93
- expect(text).toContain("reason_code: transport_error");
94
- expect(text).toContain("retryable: true");
95
- expect(text).toContain("responsibility: transport");
96
- expect(text).toContain("detail: ECONNRESET");
97
- });
98
-
99
- it("preserves the original detail for unknown errors", () => {
100
- const r = toErrorResult("forget_memory", new Error("boom"));
101
- expect(r.isError).toBe(true);
102
- expect(r.content[0].text).toContain("reason_code: internal_error");
103
- expect(r.content[0].text).toContain("detail: boom");
104
- });
105
- });
106
-
107
- describe("runToolSafely", () => {
108
- it("returns handler result on success (pass-through)", async () => {
109
- const ok = { content: [{ type: "text", text: "ok" }] };
110
- const r = await runToolSafely("search_memory", async () => ok);
111
- expect(r).toEqual(ok);
112
- });
113
-
114
- it("catches handler throw and returns a classified isError result", async () => {
115
- const r = await runToolSafely("search_memory", async () => { throw new Error("ECONNRESET"); }) as { isError: boolean; content: { text: string }[] };
116
- expect(r.isError).toBe(true);
117
- expect(r.content[0].text).toContain("reason_code: transport_error");
118
- });
119
-
120
- it("catches lazy-init failure and classifies it (P2-1 regression lock)", async () => {
121
- // 模拟 ensureComponents 抛错(缺 env var)——必须被分类返回 isError,而非裸抛绕过。
122
- const r = await runToolSafely("store_memory", async () => {
123
- throw new Error("Environment variable JINA_API_KEY not set");
124
- }) as { isError: boolean; content: { text: string }[] };
125
- expect(r.isError).toBe(true);
126
- expect(r.content[0].text).toContain("reason_code: config_missing");
127
- expect(r.content[0].text).toContain("responsibility: host");
128
- });
129
- });