@nguyentamdat/mempalace 1.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.
- package/README.md +127 -0
- package/hooks/README.md +133 -0
- package/hooks/mempal_precompact_hook.sh +35 -0
- package/hooks/mempal_save_hook.sh +80 -0
- package/package.json +36 -0
- package/src/cli.ts +50 -0
- package/src/commands/compress.ts +161 -0
- package/src/commands/init.ts +40 -0
- package/src/commands/mine.ts +51 -0
- package/src/commands/search.ts +23 -0
- package/src/commands/split.ts +20 -0
- package/src/commands/status.ts +12 -0
- package/src/commands/wake-up.ts +20 -0
- package/src/config.ts +111 -0
- package/src/convo-miner.ts +373 -0
- package/src/dialect.ts +921 -0
- package/src/entity-detector.d.ts +25 -0
- package/src/entity-detector.ts +674 -0
- package/src/entity-registry.ts +806 -0
- package/src/general-extractor.ts +487 -0
- package/src/index.ts +5 -0
- package/src/knowledge-graph.ts +461 -0
- package/src/layers.ts +512 -0
- package/src/mcp-server.ts +1034 -0
- package/src/miner.ts +612 -0
- package/src/missing-modules.d.ts +43 -0
- package/src/normalize.ts +374 -0
- package/src/onboarding.ts +485 -0
- package/src/palace-graph.ts +310 -0
- package/src/room-detector-local.ts +415 -0
- package/src/room-detector.d.ts +1 -0
- package/src/room-detector.ts +6 -0
- package/src/searcher.ts +181 -0
- package/src/spellcheck.ts +200 -0
- package/src/split-mega-files.d.ts +8 -0
- package/src/split-mega-files.ts +297 -0
package/src/layers.ts
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
ChromaClient,
|
|
6
|
+
IncludeEnum,
|
|
7
|
+
type Collection,
|
|
8
|
+
type GetResponse,
|
|
9
|
+
type Metadata,
|
|
10
|
+
type QueryResponse,
|
|
11
|
+
type Where,
|
|
12
|
+
} from "chromadb";
|
|
13
|
+
|
|
14
|
+
import { MempalaceConfig } from "./config";
|
|
15
|
+
|
|
16
|
+
type DrawerMetadata = Metadata & {
|
|
17
|
+
source_file?: string;
|
|
18
|
+
wing?: string;
|
|
19
|
+
room?: string;
|
|
20
|
+
date?: string;
|
|
21
|
+
importance?: string | number;
|
|
22
|
+
emotional_weight?: string | number;
|
|
23
|
+
weight?: string | number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type SearchHit = {
|
|
27
|
+
text: string;
|
|
28
|
+
wing: string;
|
|
29
|
+
room: string;
|
|
30
|
+
sourceFile: string;
|
|
31
|
+
similarity: number;
|
|
32
|
+
metadata: DrawerMetadata;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type DialectLike = {
|
|
36
|
+
compress(text: string, metadata?: Record<string, unknown>): string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type QueryResponseLike = QueryResponse & {
|
|
40
|
+
documents?: Array<Array<string | null>> | null;
|
|
41
|
+
metadatas?: Array<Array<Metadata | null>> | null;
|
|
42
|
+
distances?: number[][] | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let dialectPromise: Promise<DialectLike> | null = null;
|
|
46
|
+
|
|
47
|
+
function defaultIdentityPath(): string {
|
|
48
|
+
return join(homedir(), ".mempalace", "identity.txt");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeText(value: string): string {
|
|
52
|
+
return value.trim().replace(/\n/g, " ");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function truncateText(value: string, maxLength: number): string {
|
|
56
|
+
if (value.length <= maxLength) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sourceFileName(metadata: DrawerMetadata): string {
|
|
64
|
+
return metadata.source_file ? basename(metadata.source_file) : "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildWhere(wing?: string, room?: string): Where | undefined {
|
|
68
|
+
if (wing && room) {
|
|
69
|
+
return { $and: [{ wing }, { room }] } as Where;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (wing) {
|
|
73
|
+
return { wing };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (room) {
|
|
77
|
+
return { room };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getMetadataValue(metadata: DrawerMetadata, key: "importance" | "emotional_weight" | "weight"): number | null {
|
|
84
|
+
const value = metadata[key];
|
|
85
|
+
if (value === undefined || value === null) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parsed = typeof value === "number" ? value : Number.parseFloat(String(value));
|
|
90
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeGetResponse(results: GetResponse): { documents: string[]; metadatas: DrawerMetadata[] } {
|
|
94
|
+
const documents: string[] = [];
|
|
95
|
+
const metadatas: DrawerMetadata[] = [];
|
|
96
|
+
|
|
97
|
+
for (const [index, document] of (results.documents ?? []).entries()) {
|
|
98
|
+
if (typeof document !== "string") {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
documents.push(document);
|
|
103
|
+
metadatas.push(((results.metadatas ?? [])[index] ?? {}) as DrawerMetadata);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { documents, metadatas };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeQueryResponse(results: QueryResponseLike): { documents: string[]; metadatas: DrawerMetadata[]; distances: number[] } {
|
|
110
|
+
const documents: string[] = [];
|
|
111
|
+
const metadatas: DrawerMetadata[] = [];
|
|
112
|
+
const distances: number[] = [];
|
|
113
|
+
|
|
114
|
+
for (const [index, document] of (results.documents?.[0] ?? []).entries()) {
|
|
115
|
+
if (typeof document !== "string") {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
documents.push(document);
|
|
120
|
+
metadatas.push(((results.metadatas?.[0] ?? [])[index] ?? {}) as DrawerMetadata);
|
|
121
|
+
distances.push((results.distances?.[0] ?? [])[index] ?? 1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { documents, metadatas, distances };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function loadDialect(): Promise<DialectLike> {
|
|
128
|
+
if (!dialectPromise) {
|
|
129
|
+
dialectPromise = (async () => {
|
|
130
|
+
try {
|
|
131
|
+
const dialectModulePath = "./dialect";
|
|
132
|
+
const dialectModule = (await import(dialectModulePath)) as { Dialect?: new () => DialectLike };
|
|
133
|
+
if (dialectModule.Dialect) {
|
|
134
|
+
return new dialectModule.Dialect();
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
compress(text: string) {
|
|
140
|
+
return text;
|
|
141
|
+
},
|
|
142
|
+
} satisfies DialectLike;
|
|
143
|
+
})();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return dialectPromise;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getCollection(palacePath: string, collectionName: string): Promise<Collection> {
|
|
150
|
+
const client = new ChromaClient({ path: palacePath });
|
|
151
|
+
return await client.getCollection({ name: collectionName } as never);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class Layer0 {
|
|
155
|
+
path: string;
|
|
156
|
+
private text: string | null;
|
|
157
|
+
|
|
158
|
+
constructor(identityPath?: string) {
|
|
159
|
+
this.path = identityPath ?? defaultIdentityPath();
|
|
160
|
+
this.text = null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async render(): Promise<string> {
|
|
164
|
+
if (this.text !== null) {
|
|
165
|
+
return this.text;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
this.text = (await readFile(this.path, "utf-8")).trim();
|
|
170
|
+
} catch {
|
|
171
|
+
this.text = "## L0 — IDENTITY\nNo identity configured. Create ~/.mempalace/identity.txt";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return this.text;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async tokenEstimate(): Promise<number> {
|
|
178
|
+
return Math.floor((await this.render()).length / 4);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export class Layer1 {
|
|
183
|
+
static readonly MAX_DRAWERS = 15;
|
|
184
|
+
static readonly MAX_CHARS = 3200;
|
|
185
|
+
|
|
186
|
+
palacePath: string;
|
|
187
|
+
collectionName: string;
|
|
188
|
+
wing?: string;
|
|
189
|
+
|
|
190
|
+
constructor(palacePath?: string, wing?: string) {
|
|
191
|
+
const config = new MempalaceConfig();
|
|
192
|
+
this.palacePath = palacePath ?? config.palacePath;
|
|
193
|
+
this.collectionName = config.collectionName;
|
|
194
|
+
this.wing = wing;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async generate(): Promise<string> {
|
|
198
|
+
let collection: Collection;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
collection = await getCollection(this.palacePath, this.collectionName);
|
|
202
|
+
} catch {
|
|
203
|
+
return "## L1 — No palace found. Run: mempalace mine <dir>";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const where = this.wing ? ({ wing: this.wing } as Where) : undefined;
|
|
207
|
+
|
|
208
|
+
let results: GetResponse;
|
|
209
|
+
try {
|
|
210
|
+
results = await collection.get({
|
|
211
|
+
include: [IncludeEnum.Documents, IncludeEnum.Metadatas],
|
|
212
|
+
...(where ? { where } : {}),
|
|
213
|
+
});
|
|
214
|
+
} catch {
|
|
215
|
+
return "## L1 — No drawers found.";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { documents, metadatas } = normalizeGetResponse(results);
|
|
219
|
+
if (documents.length === 0) {
|
|
220
|
+
return "## L1 — No memories yet.";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const scored = documents.map((document, index) => {
|
|
224
|
+
const metadata = metadatas[index] ?? {};
|
|
225
|
+
let importance = 3;
|
|
226
|
+
|
|
227
|
+
for (const key of ["importance", "emotional_weight", "weight"] as const) {
|
|
228
|
+
const value = getMetadataValue(metadata, key);
|
|
229
|
+
if (value !== null) {
|
|
230
|
+
importance = value;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { importance, metadata, document };
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
scored.sort((left, right) => right.importance - left.importance);
|
|
239
|
+
const top = scored.slice(0, Layer1.MAX_DRAWERS);
|
|
240
|
+
const byRoom = new Map<string, Array<typeof top[number]>>();
|
|
241
|
+
|
|
242
|
+
for (const entry of top) {
|
|
243
|
+
const room = entry.metadata.room ?? "general";
|
|
244
|
+
const roomEntries = byRoom.get(room) ?? [];
|
|
245
|
+
roomEntries.push(entry);
|
|
246
|
+
byRoom.set(room, roomEntries);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const dialect = await loadDialect();
|
|
250
|
+
const lines = ["## L1 — ESSENTIAL STORY"];
|
|
251
|
+
let totalLength = 0;
|
|
252
|
+
|
|
253
|
+
for (const room of [...byRoom.keys()].sort()) {
|
|
254
|
+
const roomLine = `\n[${room}]`;
|
|
255
|
+
lines.push(roomLine);
|
|
256
|
+
totalLength += roomLine.length;
|
|
257
|
+
|
|
258
|
+
for (const entry of byRoom.get(room) ?? []) {
|
|
259
|
+
const source = sourceFileName(entry.metadata);
|
|
260
|
+
const snippetSource = normalizeText(entry.document);
|
|
261
|
+
const compressed = dialect.compress(entry.document, entry.metadata);
|
|
262
|
+
const snippet = truncateText(snippetSource || normalizeText(compressed), 200);
|
|
263
|
+
|
|
264
|
+
let entryLine = ` - ${snippet}`;
|
|
265
|
+
if (source) {
|
|
266
|
+
entryLine += ` (${source})`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (totalLength + entryLine.length > Layer1.MAX_CHARS) {
|
|
270
|
+
lines.push(" ... (more in L3 search)");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
lines.push(entryLine);
|
|
275
|
+
totalLength += entryLine.length;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return lines.join("\n");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export class Layer2 {
|
|
284
|
+
palacePath: string;
|
|
285
|
+
collectionName: string;
|
|
286
|
+
|
|
287
|
+
constructor(palacePath?: string) {
|
|
288
|
+
const config = new MempalaceConfig();
|
|
289
|
+
this.palacePath = palacePath ?? config.palacePath;
|
|
290
|
+
this.collectionName = config.collectionName;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async retrieve(wing?: string, room?: string, nResults = 10): Promise<string> {
|
|
294
|
+
let collection: Collection;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
collection = await getCollection(this.palacePath, this.collectionName);
|
|
298
|
+
} catch {
|
|
299
|
+
return "No palace found.";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const where = buildWhere(wing, room);
|
|
303
|
+
|
|
304
|
+
let results: GetResponse;
|
|
305
|
+
try {
|
|
306
|
+
results = await collection.get({
|
|
307
|
+
include: [IncludeEnum.Documents, IncludeEnum.Metadatas],
|
|
308
|
+
limit: nResults,
|
|
309
|
+
...(where ? { where } : {}),
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return `Retrieval error: ${error}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { documents, metadatas } = normalizeGetResponse(results);
|
|
316
|
+
if (documents.length === 0) {
|
|
317
|
+
let label = wing ? `wing=${wing}` : "";
|
|
318
|
+
if (room) {
|
|
319
|
+
label += label ? ` room=${room}` : `room=${room}`;
|
|
320
|
+
}
|
|
321
|
+
return `No drawers found for ${label}.`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const lines = [`## L2 — ON-DEMAND (${documents.length} drawers)`];
|
|
325
|
+
for (const [index, document] of documents.slice(0, nResults).entries()) {
|
|
326
|
+
const metadata = metadatas[index] ?? {};
|
|
327
|
+
const roomName = metadata.room ?? "?";
|
|
328
|
+
const source = sourceFileName(metadata);
|
|
329
|
+
const snippet = truncateText(normalizeText(document), 300);
|
|
330
|
+
|
|
331
|
+
let entry = ` [${roomName}] ${snippet}`;
|
|
332
|
+
if (source) {
|
|
333
|
+
entry += ` (${source})`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
lines.push(entry);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return lines.join("\n");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export class Layer3 {
|
|
344
|
+
palacePath: string;
|
|
345
|
+
collectionName: string;
|
|
346
|
+
|
|
347
|
+
constructor(palacePath?: string) {
|
|
348
|
+
const config = new MempalaceConfig();
|
|
349
|
+
this.palacePath = palacePath ?? config.palacePath;
|
|
350
|
+
this.collectionName = config.collectionName;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async search(query: string, wing?: string, room?: string, nResults = 5): Promise<string> {
|
|
354
|
+
let collection: Collection;
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
collection = await getCollection(this.palacePath, this.collectionName);
|
|
358
|
+
} catch {
|
|
359
|
+
return "No palace found.";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const where = buildWhere(wing, room);
|
|
363
|
+
|
|
364
|
+
let results: QueryResponseLike;
|
|
365
|
+
try {
|
|
366
|
+
results = await collection.query({
|
|
367
|
+
queryTexts: [query],
|
|
368
|
+
nResults,
|
|
369
|
+
include: [IncludeEnum.Documents, IncludeEnum.Metadatas, IncludeEnum.Distances],
|
|
370
|
+
...(where ? { where } : {}),
|
|
371
|
+
});
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return `Search error: ${error}`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const { documents, metadatas, distances } = normalizeQueryResponse(results);
|
|
377
|
+
if (documents.length === 0) {
|
|
378
|
+
return "No results found.";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const lines = [`## L3 — SEARCH RESULTS for "${query}"`];
|
|
382
|
+
for (const [index, document] of documents.entries()) {
|
|
383
|
+
const metadata = metadatas[index] ?? {};
|
|
384
|
+
const distance = distances[index] ?? 1;
|
|
385
|
+
const similarity = Math.round((1 - distance) * 1000) / 1000;
|
|
386
|
+
const wingName = metadata.wing ?? "?";
|
|
387
|
+
const roomName = metadata.room ?? "?";
|
|
388
|
+
const source = sourceFileName(metadata);
|
|
389
|
+
const snippet = truncateText(normalizeText(document), 300);
|
|
390
|
+
|
|
391
|
+
lines.push(` [${index + 1}] ${wingName}/${roomName} (sim=${similarity})`);
|
|
392
|
+
lines.push(` ${snippet}`);
|
|
393
|
+
if (source) {
|
|
394
|
+
lines.push(` src: ${source}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return lines.join("\n");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async searchRaw(query: string, wing?: string, room?: string, nResults = 5): Promise<SearchHit[]> {
|
|
402
|
+
let collection: Collection;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
collection = await getCollection(this.palacePath, this.collectionName);
|
|
406
|
+
} catch {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const where = buildWhere(wing, room);
|
|
411
|
+
|
|
412
|
+
let results: QueryResponseLike;
|
|
413
|
+
try {
|
|
414
|
+
results = await collection.query({
|
|
415
|
+
queryTexts: [query],
|
|
416
|
+
nResults,
|
|
417
|
+
include: [IncludeEnum.Documents, IncludeEnum.Metadatas, IncludeEnum.Distances],
|
|
418
|
+
...(where ? { where } : {}),
|
|
419
|
+
});
|
|
420
|
+
} catch {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const { documents, metadatas, distances } = normalizeQueryResponse(results);
|
|
425
|
+
return documents.map((document, index) => {
|
|
426
|
+
const metadata = metadatas[index] ?? {};
|
|
427
|
+
const distance = distances[index] ?? 1;
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
text: document,
|
|
431
|
+
wing: metadata.wing ?? "unknown",
|
|
432
|
+
room: metadata.room ?? "unknown",
|
|
433
|
+
sourceFile: metadata.source_file ? basename(metadata.source_file) : "?",
|
|
434
|
+
similarity: Math.round((1 - distance) * 1000) / 1000,
|
|
435
|
+
metadata,
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export class MemoryStack {
|
|
442
|
+
palacePath: string;
|
|
443
|
+
identityPath: string;
|
|
444
|
+
l0: Layer0;
|
|
445
|
+
l1: Layer1;
|
|
446
|
+
l2: Layer2;
|
|
447
|
+
l3: Layer3;
|
|
448
|
+
|
|
449
|
+
constructor(palacePath?: string, identityPath?: string) {
|
|
450
|
+
const config = new MempalaceConfig();
|
|
451
|
+
this.palacePath = palacePath ?? config.palacePath;
|
|
452
|
+
this.identityPath = identityPath ?? defaultIdentityPath();
|
|
453
|
+
|
|
454
|
+
this.l0 = new Layer0(this.identityPath);
|
|
455
|
+
this.l1 = new Layer1(this.palacePath);
|
|
456
|
+
this.l2 = new Layer2(this.palacePath);
|
|
457
|
+
this.l3 = new Layer3(this.palacePath);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async wakeUp(wing?: string): Promise<string> {
|
|
461
|
+
const parts: string[] = [];
|
|
462
|
+
|
|
463
|
+
parts.push(await this.l0.render());
|
|
464
|
+
parts.push("");
|
|
465
|
+
|
|
466
|
+
if (wing) {
|
|
467
|
+
this.l1.wing = wing;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
parts.push(await this.l1.generate());
|
|
471
|
+
return parts.join("\n");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async recall(wing?: string, room?: string, nResults = 10): Promise<string> {
|
|
475
|
+
return await this.l2.retrieve(wing, room, nResults);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async search(query: string, wing?: string, room?: string, nResults = 5): Promise<string> {
|
|
479
|
+
return await this.l3.search(query, wing, room, nResults);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async status(): Promise<Record<string, unknown>> {
|
|
483
|
+
const result: Record<string, unknown> = {
|
|
484
|
+
palacePath: this.palacePath,
|
|
485
|
+
l0Identity: {
|
|
486
|
+
path: this.identityPath,
|
|
487
|
+
exists: await Bun.file(this.identityPath).exists(),
|
|
488
|
+
tokens: await this.l0.tokenEstimate(),
|
|
489
|
+
},
|
|
490
|
+
l1Essential: {
|
|
491
|
+
description: "Auto-generated from top palace drawers",
|
|
492
|
+
},
|
|
493
|
+
l2OnDemand: {
|
|
494
|
+
description: "Wing/room filtered retrieval",
|
|
495
|
+
},
|
|
496
|
+
l3DeepSearch: {
|
|
497
|
+
description: "Full semantic search via ChromaDB",
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
const collection = await getCollection(this.palacePath, new MempalaceConfig().collectionName);
|
|
503
|
+
result.totalDrawers = await collection.count();
|
|
504
|
+
} catch {
|
|
505
|
+
result.totalDrawers = 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export default MemoryStack;
|