@exaudeus/memory-mcp 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +20 -1
- package/dist/config.js +70 -6
- package/dist/embedder.d.ts +22 -0
- package/dist/embedder.js +50 -0
- package/dist/formatters.d.ts +10 -1
- package/dist/formatters.js +27 -1
- package/dist/index.js +517 -117
- package/dist/store.d.ts +14 -1
- package/dist/store.js +56 -2
- package/dist/text-analyzer.d.ts +12 -0
- package/dist/text-analyzer.js +28 -0
- package/dist/thresholds.d.ts +4 -0
- package/dist/thresholds.js +4 -0
- package/dist/types.d.ts +20 -1
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { MemoryConfig, BehaviorConfig } from './types.js';
|
|
1
|
+
import type { MemoryConfig, BehaviorConfig, EmbedderConfig } from './types.js';
|
|
2
|
+
import type { Embedder } from './embedder.js';
|
|
2
3
|
/** How the config was loaded — discriminated union so configFilePath
|
|
3
4
|
* only exists when source is 'file' (illegal states unrepresentable) */
|
|
4
5
|
export type ConfigOrigin = {
|
|
@@ -15,6 +16,8 @@ export interface LoadedConfig {
|
|
|
15
16
|
readonly origin: ConfigOrigin;
|
|
16
17
|
/** Resolved behavior config — present when a "behavior" block was found in memory-config.json */
|
|
17
18
|
readonly behavior?: BehaviorConfig;
|
|
19
|
+
/** Resolved embedder — shared across all lobes. Constructed from config or auto-detected. */
|
|
20
|
+
readonly embedder?: Embedder;
|
|
18
21
|
}
|
|
19
22
|
interface MemoryConfigFileBehavior {
|
|
20
23
|
staleDaysStandard?: number;
|
|
@@ -23,10 +26,26 @@ interface MemoryConfigFileBehavior {
|
|
|
23
26
|
maxDedupSuggestions?: number;
|
|
24
27
|
maxConflictPairs?: number;
|
|
25
28
|
}
|
|
29
|
+
interface MemoryConfigFileEmbedder {
|
|
30
|
+
provider?: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
dimensions?: number;
|
|
35
|
+
}
|
|
26
36
|
/** Parse and validate a behavior config block, falling back to defaults for each field.
|
|
27
37
|
* Warns to stderr for unknown keys (likely typos) and out-of-range values.
|
|
28
38
|
* Exported for testing — validates and clamps all fields. */
|
|
29
39
|
export declare function parseBehaviorConfig(raw?: MemoryConfigFileBehavior): BehaviorConfig;
|
|
40
|
+
/** Parse and validate an embedder config block.
|
|
41
|
+
* Returns undefined when block is absent (auto-detect mode).
|
|
42
|
+
* Exported for testing. */
|
|
43
|
+
export declare function parseEmbedderConfig(raw?: MemoryConfigFileEmbedder): EmbedderConfig | undefined;
|
|
44
|
+
/** Create an Embedder from config.
|
|
45
|
+
* - provider "none" → null (keyword-only)
|
|
46
|
+
* - provider "ollama" → LazyEmbedder wrapping OllamaEmbedder with config params
|
|
47
|
+
* - No config (auto-detect) → LazyEmbedder wrapping default OllamaEmbedder */
|
|
48
|
+
export declare function createEmbedderFromConfig(config?: EmbedderConfig): Embedder | undefined;
|
|
30
49
|
/** Load lobe configs with priority: memory-config.json -> env vars -> single-repo default */
|
|
31
50
|
export declare function getLobeConfigs(): LoadedConfig;
|
|
32
51
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -7,6 +7,7 @@ import { execFileSync } from 'child_process';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import os from 'os';
|
|
9
9
|
import { DEFAULT_STORAGE_BUDGET_BYTES } from './types.js';
|
|
10
|
+
import { OllamaEmbedder, LazyEmbedder } from './embedder.js';
|
|
10
11
|
import { DEFAULT_STALE_DAYS_STANDARD, DEFAULT_STALE_DAYS_PREFERENCES, DEFAULT_MAX_STALE_IN_BRIEFING, DEFAULT_MAX_DEDUP_SUGGESTIONS, DEFAULT_MAX_CONFLICT_PAIRS, } from './thresholds.js';
|
|
11
12
|
/** Validate and clamp a numeric threshold to a given range.
|
|
12
13
|
* Returns the default if the value is missing, NaN, or out of range. */
|
|
@@ -46,6 +47,61 @@ export function parseBehaviorConfig(raw) {
|
|
|
46
47
|
maxConflictPairs: clampThreshold(raw.maxConflictPairs, DEFAULT_MAX_CONFLICT_PAIRS, 1, 5),
|
|
47
48
|
};
|
|
48
49
|
}
|
|
50
|
+
/** Known embedder config keys — used to warn on typos/unknown fields. */
|
|
51
|
+
const KNOWN_EMBEDDER_KEYS = new Set([
|
|
52
|
+
'provider', 'model', 'baseUrl', 'timeoutMs', 'dimensions',
|
|
53
|
+
]);
|
|
54
|
+
const VALID_PROVIDERS = new Set(['ollama', 'none']);
|
|
55
|
+
/** Parse and validate an embedder config block.
|
|
56
|
+
* Returns undefined when block is absent (auto-detect mode).
|
|
57
|
+
* Exported for testing. */
|
|
58
|
+
export function parseEmbedderConfig(raw) {
|
|
59
|
+
if (!raw)
|
|
60
|
+
return undefined;
|
|
61
|
+
// Warn on unrecognized keys
|
|
62
|
+
for (const key of Object.keys(raw)) {
|
|
63
|
+
if (!KNOWN_EMBEDDER_KEYS.has(key)) {
|
|
64
|
+
process.stderr.write(`[memory-mcp] Unknown embedder config key "${key}" — ignored. ` +
|
|
65
|
+
`Valid keys: ${Array.from(KNOWN_EMBEDDER_KEYS).join(', ')}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Validate provider — default to 'ollama' if present but not set
|
|
69
|
+
const provider = raw.provider && VALID_PROVIDERS.has(raw.provider)
|
|
70
|
+
? raw.provider
|
|
71
|
+
: 'ollama';
|
|
72
|
+
if (raw.provider && !VALID_PROVIDERS.has(raw.provider)) {
|
|
73
|
+
process.stderr.write(`[memory-mcp] Unknown embedder provider "${raw.provider}" — using "ollama". Valid: ${Array.from(VALID_PROVIDERS).join(', ')}\n`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
provider,
|
|
77
|
+
model: raw.model,
|
|
78
|
+
baseUrl: raw.baseUrl,
|
|
79
|
+
timeoutMs: raw.timeoutMs !== undefined
|
|
80
|
+
? clampThreshold(raw.timeoutMs, 5000, 500, 30000)
|
|
81
|
+
: undefined,
|
|
82
|
+
dimensions: raw.dimensions !== undefined
|
|
83
|
+
? clampThreshold(raw.dimensions, 384, 64, 4096)
|
|
84
|
+
: undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Create an Embedder from config.
|
|
88
|
+
* - provider "none" → null (keyword-only)
|
|
89
|
+
* - provider "ollama" → LazyEmbedder wrapping OllamaEmbedder with config params
|
|
90
|
+
* - No config (auto-detect) → LazyEmbedder wrapping default OllamaEmbedder */
|
|
91
|
+
export function createEmbedderFromConfig(config) {
|
|
92
|
+
// Explicit opt-out
|
|
93
|
+
if (config?.provider === 'none')
|
|
94
|
+
return undefined;
|
|
95
|
+
// Explicit or default Ollama config
|
|
96
|
+
const candidate = new OllamaEmbedder({
|
|
97
|
+
model: config?.model,
|
|
98
|
+
baseUrl: config?.baseUrl,
|
|
99
|
+
timeoutMs: config?.timeoutMs,
|
|
100
|
+
dimensions: config?.dimensions,
|
|
101
|
+
});
|
|
102
|
+
// Both explicit "ollama" and auto-detect use LazyEmbedder for fast startup
|
|
103
|
+
return new LazyEmbedder(candidate);
|
|
104
|
+
}
|
|
49
105
|
function resolveRoot(root) {
|
|
50
106
|
return root
|
|
51
107
|
.replace(/^\$HOME\b/, process.env.HOME ?? '')
|
|
@@ -84,7 +140,7 @@ function resolveMemoryPath(repoRoot, workspaceName, explicitMemoryDir) {
|
|
|
84
140
|
/** If no lobe has alwaysInclude: true AND the legacy global store directory has actual entries,
|
|
85
141
|
* auto-create a "global" lobe pointing to it. Protects existing users who haven't updated their config.
|
|
86
142
|
* Only fires when the dir contains .md files — an empty dir doesn't trigger creation. */
|
|
87
|
-
function ensureAlwaysIncludeLobe(configs, behavior) {
|
|
143
|
+
function ensureAlwaysIncludeLobe(configs, behavior, embedder) {
|
|
88
144
|
const hasAlwaysInclude = Array.from(configs.values()).some(c => c.alwaysInclude);
|
|
89
145
|
if (hasAlwaysInclude)
|
|
90
146
|
return;
|
|
@@ -114,6 +170,7 @@ function ensureAlwaysIncludeLobe(configs, behavior) {
|
|
|
114
170
|
storageBudgetBytes: DEFAULT_STORAGE_BUDGET_BYTES,
|
|
115
171
|
alwaysInclude: true,
|
|
116
172
|
behavior,
|
|
173
|
+
embedder,
|
|
117
174
|
});
|
|
118
175
|
process.stderr.write(`[memory-mcp] Auto-created "global" lobe (alwaysInclude) from existing ${globalPath}\n`);
|
|
119
176
|
}
|
|
@@ -131,6 +188,8 @@ export function getLobeConfigs() {
|
|
|
131
188
|
else {
|
|
132
189
|
// Parse global behavior config once — applies to all lobes
|
|
133
190
|
const behavior = parseBehaviorConfig(external.behavior);
|
|
191
|
+
const embedderConfig = parseEmbedderConfig(external.embedder);
|
|
192
|
+
const embedder = createEmbedderFromConfig(embedderConfig);
|
|
134
193
|
for (const [name, config] of Object.entries(external.lobes)) {
|
|
135
194
|
if (!config.root) {
|
|
136
195
|
process.stderr.write(`[memory-mcp] Skipping lobe "${name}": missing "root" field\n`);
|
|
@@ -143,14 +202,15 @@ export function getLobeConfigs() {
|
|
|
143
202
|
storageBudgetBytes: (config.budgetMB ?? 2) * 1024 * 1024,
|
|
144
203
|
alwaysInclude: config.alwaysInclude ?? false,
|
|
145
204
|
behavior,
|
|
205
|
+
embedder,
|
|
146
206
|
});
|
|
147
207
|
}
|
|
148
208
|
if (configs.size > 0) {
|
|
149
209
|
// Reuse the already-parsed behavior config for the alwaysInclude fallback
|
|
150
210
|
const resolvedBehavior = external.behavior ? behavior : undefined;
|
|
151
|
-
ensureAlwaysIncludeLobe(configs, resolvedBehavior);
|
|
211
|
+
ensureAlwaysIncludeLobe(configs, resolvedBehavior, embedder);
|
|
152
212
|
process.stderr.write(`[memory-mcp] Loaded ${configs.size} lobe(s) from memory-config.json\n`);
|
|
153
|
-
return { configs, origin: { source: 'file', path: configPath }, behavior: resolvedBehavior };
|
|
213
|
+
return { configs, origin: { source: 'file', path: configPath }, behavior: resolvedBehavior, embedder };
|
|
154
214
|
}
|
|
155
215
|
}
|
|
156
216
|
}
|
|
@@ -162,6 +222,8 @@ export function getLobeConfigs() {
|
|
|
162
222
|
process.stderr.write(`[memory-mcp] Failed to parse memory-config.json: ${message}\n`);
|
|
163
223
|
}
|
|
164
224
|
}
|
|
225
|
+
// Auto-detect embedder for env var and default modes (no config file)
|
|
226
|
+
const autoEmbedder = createEmbedderFromConfig(undefined);
|
|
165
227
|
// 2. Try env var multi-repo mode
|
|
166
228
|
const workspacesJson = process.env.MEMORY_MCP_WORKSPACES;
|
|
167
229
|
if (workspacesJson) {
|
|
@@ -176,12 +238,13 @@ export function getLobeConfigs() {
|
|
|
176
238
|
memoryPath: resolveMemoryPath(repoRoot, name, explicitDir),
|
|
177
239
|
storageBudgetBytes: storageBudget,
|
|
178
240
|
alwaysInclude: false,
|
|
241
|
+
embedder: autoEmbedder,
|
|
179
242
|
});
|
|
180
243
|
}
|
|
181
244
|
if (configs.size > 0) {
|
|
182
|
-
ensureAlwaysIncludeLobe(configs);
|
|
245
|
+
ensureAlwaysIncludeLobe(configs, undefined, autoEmbedder);
|
|
183
246
|
process.stderr.write(`[memory-mcp] Loaded ${configs.size} lobe(s) from MEMORY_MCP_WORKSPACES env var\n`);
|
|
184
|
-
return { configs, origin: { source: 'env' } };
|
|
247
|
+
return { configs, origin: { source: 'env' }, embedder: autoEmbedder };
|
|
185
248
|
}
|
|
186
249
|
}
|
|
187
250
|
catch (e) {
|
|
@@ -197,8 +260,9 @@ export function getLobeConfigs() {
|
|
|
197
260
|
memoryPath: resolveMemoryPath(repoRoot, 'default', explicitDir),
|
|
198
261
|
storageBudgetBytes: storageBudget,
|
|
199
262
|
alwaysInclude: false,
|
|
263
|
+
embedder: autoEmbedder,
|
|
200
264
|
});
|
|
201
265
|
// No ensureAlwaysIncludeLobe here — single-repo default users have everything in one lobe
|
|
202
266
|
process.stderr.write(`[memory-mcp] Using single-lobe default mode (cwd: ${repoRoot})\n`);
|
|
203
|
-
return { configs, origin: { source: 'default' } };
|
|
267
|
+
return { configs, origin: { source: 'default' }, embedder: autoEmbedder };
|
|
204
268
|
}
|
package/dist/embedder.d.ts
CHANGED
|
@@ -60,6 +60,28 @@ export declare class FakeEmbedder implements Embedder {
|
|
|
60
60
|
constructor(dimensions?: number);
|
|
61
61
|
embed(text: string, _signal?: AbortSignal): Promise<EmbedResult>;
|
|
62
62
|
}
|
|
63
|
+
/** Lazy auto-detecting embedder — probes on first use, caches the result.
|
|
64
|
+
* Re-probes on failure after a TTL window so the system recovers if
|
|
65
|
+
* Ollama starts after MCP startup.
|
|
66
|
+
*
|
|
67
|
+
* Implements the same Embedder interface — the store never knows it's lazy.
|
|
68
|
+
* The probe uses the candidate's own timeout (5s for cold starts).
|
|
69
|
+
* The caller's signal is only forwarded to the actual embed call, not the probe. */
|
|
70
|
+
export declare class LazyEmbedder implements Embedder {
|
|
71
|
+
readonly dimensions: number;
|
|
72
|
+
private inner;
|
|
73
|
+
private lastProbeTime;
|
|
74
|
+
private hasLoggedUnavailable;
|
|
75
|
+
private readonly candidate;
|
|
76
|
+
private readonly reprobeIntervalMs;
|
|
77
|
+
private readonly now;
|
|
78
|
+
constructor(candidate: Embedder, opts?: {
|
|
79
|
+
readonly reprobeIntervalMs?: number;
|
|
80
|
+
/** Injectable clock for testing — default Date.now */
|
|
81
|
+
readonly now?: () => number;
|
|
82
|
+
});
|
|
83
|
+
embed(text: string, signal?: AbortSignal): Promise<EmbedResult>;
|
|
84
|
+
}
|
|
63
85
|
/** Batch embed texts sequentially. Pure composition over Embedder.embed().
|
|
64
86
|
* Sequential because local Ollama benefits from serialized requests (single GPU/CPU).
|
|
65
87
|
* Not on the interface — interface segregation. */
|
package/dist/embedder.js
CHANGED
|
@@ -130,6 +130,56 @@ function trigramHash(trigram, buckets) {
|
|
|
130
130
|
}
|
|
131
131
|
return ((hash % buckets) + buckets) % buckets;
|
|
132
132
|
}
|
|
133
|
+
// ─── LazyEmbedder ─────────────────────────────────────────────────────────
|
|
134
|
+
/** Default reprobe interval — how long to wait before retrying after a failed probe.
|
|
135
|
+
* 2 minutes balances responsiveness (recovery after Ollama starts) with
|
|
136
|
+
* avoiding excessive probes when Ollama isn't installed. */
|
|
137
|
+
const DEFAULT_REPROBE_INTERVAL_MS = 2 * 60 * 1000;
|
|
138
|
+
/** Lazy auto-detecting embedder — probes on first use, caches the result.
|
|
139
|
+
* Re-probes on failure after a TTL window so the system recovers if
|
|
140
|
+
* Ollama starts after MCP startup.
|
|
141
|
+
*
|
|
142
|
+
* Implements the same Embedder interface — the store never knows it's lazy.
|
|
143
|
+
* The probe uses the candidate's own timeout (5s for cold starts).
|
|
144
|
+
* The caller's signal is only forwarded to the actual embed call, not the probe. */
|
|
145
|
+
export class LazyEmbedder {
|
|
146
|
+
constructor(candidate, opts) {
|
|
147
|
+
this.inner = null;
|
|
148
|
+
this.lastProbeTime = -Infinity;
|
|
149
|
+
this.hasLoggedUnavailable = false;
|
|
150
|
+
this.candidate = candidate;
|
|
151
|
+
this.dimensions = candidate.dimensions;
|
|
152
|
+
this.reprobeIntervalMs = opts?.reprobeIntervalMs ?? DEFAULT_REPROBE_INTERVAL_MS;
|
|
153
|
+
this.now = opts?.now ?? Date.now;
|
|
154
|
+
}
|
|
155
|
+
async embed(text, signal) {
|
|
156
|
+
const now = this.now();
|
|
157
|
+
const shouldProbe = !this.inner && (now - this.lastProbeTime >= this.reprobeIntervalMs);
|
|
158
|
+
if (shouldProbe) {
|
|
159
|
+
this.lastProbeTime = now;
|
|
160
|
+
// Probe without caller's signal — use candidate's default timeout (5s)
|
|
161
|
+
// so cold model loads aren't aborted by a tight query-time timeout
|
|
162
|
+
const probe = await this.candidate.embed('probe');
|
|
163
|
+
if (probe.ok) {
|
|
164
|
+
this.inner = this.candidate;
|
|
165
|
+
if (this.hasLoggedUnavailable) {
|
|
166
|
+
// Recovery after previous failure — notify
|
|
167
|
+
process.stderr.write('[memory-mcp] Embedding provider recovered — semantic search active\n');
|
|
168
|
+
this.hasLoggedUnavailable = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else if (!this.hasLoggedUnavailable) {
|
|
172
|
+
// Only log first failure — avoid noisy repeated warnings
|
|
173
|
+
process.stderr.write(`[memory-mcp] Embedding provider not available — using keyword-only search (will retry in ${Math.round(this.reprobeIntervalMs / 1000)}s)\n`);
|
|
174
|
+
this.hasLoggedUnavailable = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!this.inner) {
|
|
178
|
+
return { ok: false, failure: { kind: 'provider-unavailable', reason: 'auto-detect: provider not available' } };
|
|
179
|
+
}
|
|
180
|
+
return this.inner.embed(text, signal);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
133
183
|
// ─── Batch utility ────────────────────────────────────────────────────────
|
|
134
184
|
/** Batch embed texts sequentially. Pure composition over Embedder.embed().
|
|
135
185
|
* Sequential because local Ollama benefits from serialized requests (single GPU/CPU).
|
package/dist/formatters.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type { MemoryStats, StaleEntry, ConflictPair, BehaviorConfig } from './types.js';
|
|
1
|
+
import type { MemoryStats, StaleEntry, ConflictPair, BehaviorConfig, RelatedEntry } from './types.js';
|
|
2
2
|
import { type FilterGroup } from './text-analyzer.js';
|
|
3
3
|
import type { MarkdownMemoryStore } from './store.js';
|
|
4
|
+
/** Format the search mode indicator for context/recall responses.
|
|
5
|
+
* Pure function — no I/O, no state.
|
|
6
|
+
*
|
|
7
|
+
* Shows whether semantic search is active and vector coverage. */
|
|
8
|
+
export declare function formatSearchMode(embedderAvailable: boolean, vectorCount: number, totalCount: number): string;
|
|
9
|
+
/** Format the loot-drop section — related entries shown after a storage operation.
|
|
10
|
+
* Transforms storage from "chore for the future" into "immediate value exchange."
|
|
11
|
+
* Pure function. */
|
|
12
|
+
export declare function formatLootDrop(related: readonly RelatedEntry[]): string;
|
|
4
13
|
/** Format the stale entries section for briefing/context responses */
|
|
5
14
|
export declare function formatStaleSection(staleDetails: readonly StaleEntry[]): string;
|
|
6
15
|
/** Format the conflict detection warning for query/context responses */
|
package/dist/formatters.js
CHANGED
|
@@ -4,6 +4,31 @@
|
|
|
4
4
|
// and returns a formatted string for the tool response.
|
|
5
5
|
import { DEFAULT_STALE_DAYS_STANDARD, DEFAULT_STALE_DAYS_PREFERENCES, DEFAULT_MAX_STALE_IN_BRIEFING, DEFAULT_MAX_DEDUP_SUGGESTIONS, DEFAULT_MAX_CONFLICT_PAIRS, MAX_FOOTER_TAGS, WARN_SEPARATOR, } from './thresholds.js';
|
|
6
6
|
import { analyzeFilterGroups } from './text-analyzer.js';
|
|
7
|
+
/** Format the search mode indicator for context/recall responses.
|
|
8
|
+
* Pure function — no I/O, no state.
|
|
9
|
+
*
|
|
10
|
+
* Shows whether semantic search is active and vector coverage. */
|
|
11
|
+
export function formatSearchMode(embedderAvailable, vectorCount, totalCount) {
|
|
12
|
+
if (!embedderAvailable) {
|
|
13
|
+
return '*Search: keyword-only (install Ollama for semantic search)*';
|
|
14
|
+
}
|
|
15
|
+
if (vectorCount === 0 && totalCount > 0) {
|
|
16
|
+
return `*Search: semantic + keyword (0/${totalCount} entries vectorized — run memory_reembed)*`;
|
|
17
|
+
}
|
|
18
|
+
if (totalCount === 0) {
|
|
19
|
+
return '*Search: semantic + keyword (no entries yet)*';
|
|
20
|
+
}
|
|
21
|
+
return `*Search: semantic + keyword (${vectorCount}/${totalCount} entries vectorized)*`;
|
|
22
|
+
}
|
|
23
|
+
/** Format the loot-drop section — related entries shown after a storage operation.
|
|
24
|
+
* Transforms storage from "chore for the future" into "immediate value exchange."
|
|
25
|
+
* Pure function. */
|
|
26
|
+
export function formatLootDrop(related) {
|
|
27
|
+
if (related.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
const lines = related.map(r => `- [${r.id}] "${r.title}" (confidence: ${r.confidence.toFixed(2)})`);
|
|
30
|
+
return `\n**Related knowledge:**\n${lines.join('\n')}`;
|
|
31
|
+
}
|
|
7
32
|
/** Format the stale entries section for briefing/context responses */
|
|
8
33
|
export function formatStaleSection(staleDetails) {
|
|
9
34
|
const lines = [
|
|
@@ -61,11 +86,12 @@ export function formatStats(lobe, result) {
|
|
|
61
86
|
.join('\n')
|
|
62
87
|
: ' (none)';
|
|
63
88
|
const corruptLine = result.corruptFiles > 0 ? `\n**Corrupt files:** ${result.corruptFiles}` : '';
|
|
89
|
+
const vectorLine = `\n**Vectors:** ${result.vectorCount}/${result.totalEntries} entries vectorized`;
|
|
64
90
|
return [
|
|
65
91
|
`## [${lobe}] Memory Stats`,
|
|
66
92
|
``,
|
|
67
93
|
`**Memory location:** ${result.memoryPath}`,
|
|
68
|
-
`**Total entries:** ${result.totalEntries}${corruptLine}`,
|
|
94
|
+
`**Total entries:** ${result.totalEntries}${corruptLine}${vectorLine}`,
|
|
69
95
|
`**Storage:** ${result.storageSize} / ${Math.round(result.storageBudgetBytes / 1024 / 1024)}MB budget`,
|
|
70
96
|
``,
|
|
71
97
|
`### By Topic`,
|