tsunami-memory 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +501 -0
  3. package/README.zh-CN.md +485 -0
  4. package/package.json +46 -0
  5. package/server/api.ts +125 -0
  6. package/server/mcp.ts +221 -0
  7. package/src/bun_memory_store.ts +340 -0
  8. package/src/classifier_keywords.ts +115 -0
  9. package/src/core/project_state.ts +88 -0
  10. package/src/index.ts +54 -0
  11. package/src/legacy_compat/tsunami_compat.ts +22 -0
  12. package/src/legacy_compat/tsunami_legacy_identity.ts +13 -0
  13. package/src/legacy_compat/tsunami_legacy_taxonomy.ts +197 -0
  14. package/src/memory_audit.ts +32 -0
  15. package/src/memory_conflict_resolver.ts +14 -0
  16. package/src/memory_fabric.ts +31 -0
  17. package/src/memory_manager.ts +10 -0
  18. package/src/memory_promotion.ts +22 -0
  19. package/src/memory_recovery.ts +14 -0
  20. package/src/memory_runtime.ts +7 -0
  21. package/src/migration.ts +163 -0
  22. package/src/provider.ts +68 -0
  23. package/src/runtime/checkpoints/durable_recovery.ts +24 -0
  24. package/src/runtime/paths.ts +11 -0
  25. package/src/storm/basins.ts +57 -0
  26. package/src/storm/boundary.ts +52 -0
  27. package/src/storm/budget.ts +42 -0
  28. package/src/storm/center.ts +396 -0
  29. package/src/storm/confidence.ts +88 -0
  30. package/src/storm/coverage.ts +44 -0
  31. package/src/storm/directive.ts +94 -0
  32. package/src/storm/gate.ts +43 -0
  33. package/src/storm/helpers.ts +172 -0
  34. package/src/storm/horizon.ts +52 -0
  35. package/src/storm/intake.ts +80 -0
  36. package/src/storm/mode.ts +21 -0
  37. package/src/storm/pressure.ts +56 -0
  38. package/src/storm/readiness.ts +29 -0
  39. package/src/storm/saturation.ts +45 -0
  40. package/src/storm/selection.ts +49 -0
  41. package/src/storm/signals.ts +105 -0
  42. package/src/storm/types.ts +216 -0
  43. package/src/tsunami_bun_backend.ts +705 -0
  44. package/src/tsunami_chinese_dialect.ts +19 -0
  45. package/src/tsunami_classifier.ts +137 -0
  46. package/src/tsunami_client.ts +710 -0
  47. package/src/tsunami_execution_gate.ts +232 -0
  48. package/src/tsunami_graph_runtime.ts +359 -0
  49. package/src/tsunami_identity.ts +35 -0
  50. package/src/tsunami_routing.ts +169 -0
  51. package/src/tsunami_runtime_graph_sync.ts +17 -0
  52. package/src/tsunami_schema.ts +403 -0
  53. package/src/tsunami_storage_paths.ts +8 -0
  54. package/src/tsunami_storm_center.ts +53 -0
@@ -0,0 +1,705 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { dirname } from 'path';
3
+
4
+ import {
5
+ BUN_MEMORY_DB_PATH,
6
+ buildBunMemoryPreview,
7
+ checkBunMemoryDuplicate,
8
+ countBunMemoryEntries,
9
+ deleteBunMemoryEntry,
10
+ getBunMemoryStatus,
11
+ getBunMemoryTaxonomy,
12
+ insertBunMemoryEntry,
13
+ listBunMemoryRoomCounts,
14
+ listBunMemoryTimeline,
15
+ listBunMemoryWingCounts,
16
+ recallBunMemoryRows,
17
+ searchBunMemoryRows,
18
+ wakeBunMemoryRows,
19
+ } from './bun_memory_store';
20
+ import {
21
+ buildTsunamiStormCenter,
22
+ formatTsunamiStormCenterText,
23
+ } from './tsunami_storm_center';
24
+ import {
25
+ describeTsunamiGraphOntology,
26
+ describeTsunamiTaxonomy,
27
+ formatTsunamiGraphOntologyText,
28
+ formatTsunamiNormalizationText,
29
+ formatTsunamiTaxonomyText,
30
+ normalizeTsunamiTaxonomy,
31
+ } from './tsunami_schema';
32
+ import {
33
+ buildChineseIndexedContent,
34
+ getTsunamiAaakSpec,
35
+ isChineseHeavyText,
36
+ } from './tsunami_chinese_dialect';
37
+ import {
38
+ describeTsunamiRoutingMatrix,
39
+ } from './tsunami_routing';
40
+ import {
41
+ classifyTsunamiText,
42
+ classifyTsunamiTextMulti,
43
+ } from './tsunami_classifier';
44
+ import {
45
+ readTsunamiIdentity,
46
+ } from './tsunami_identity';
47
+ import {
48
+ tsunamiGraphAddTriple,
49
+ tsunamiGraphCompatStats,
50
+ tsunamiGraphFindTunnels,
51
+ tsunamiGraphInvalidateTriple,
52
+ tsunamiGraphQueryEntity,
53
+ tsunamiGraphTraverse,
54
+ tsunamiGraphStats,
55
+ tsunamiGraphTimeline,
56
+ } from './tsunami_graph_runtime';
57
+ import {
58
+ TSUNAMI_IDENTITY_FILE,
59
+ TSUNAMI_LEGACY_FALLBACK_FILE,
60
+ } from './tsunami_storage_paths';
61
+
62
+ function countIdentityTokens(identity: string): number {
63
+ return (identity.match(/[\u4e00-\u9fa5A-Za-z0-9_.-]+/g) ?? []).length;
64
+ }
65
+
66
+ function isBunDrawerId(id: string): boolean {
67
+ return id.startsWith('bunmem_');
68
+ }
69
+
70
+ type LegacyFallbackDrawer = {
71
+ id: string;
72
+ wing: string;
73
+ room: string;
74
+ content: string;
75
+ importance: number;
76
+ ts: number;
77
+ };
78
+
79
+ type LegacyFallbackStore = {
80
+ version: number;
81
+ updatedAt: number;
82
+ drawers: LegacyFallbackDrawer[];
83
+ };
84
+
85
+ function loadLegacyFallbackStore(): LegacyFallbackStore {
86
+ try {
87
+ if (!existsSync(TSUNAMI_LEGACY_FALLBACK_FILE)) {
88
+ return { version: 1, updatedAt: Date.now(), drawers: [] };
89
+ }
90
+ const raw = JSON.parse(readFileSync(TSUNAMI_LEGACY_FALLBACK_FILE, 'utf8'));
91
+ const drawers = Array.isArray(raw?.drawers) ? raw.drawers : [];
92
+ return {
93
+ version: 1,
94
+ updatedAt: Number(raw?.updatedAt ?? Date.now()),
95
+ drawers: drawers
96
+ .filter((item: any) => item && typeof item.content === 'string')
97
+ .map((item: any) => ({
98
+ id: String(item.id ?? ''),
99
+ wing: String(item.wing ?? 'ats'),
100
+ room: String(item.room ?? 'ats/general'),
101
+ content: String(item.content ?? ''),
102
+ importance: Number(item.importance ?? 3),
103
+ ts: Number(item.ts ?? Date.now()),
104
+ })),
105
+ };
106
+ } catch (err: unknown) {
107
+ const msg = err instanceof Error ? err.message : String(err);
108
+ console.warn(`[TSUNAMI] failed to load legacy fallback store: ${msg}`);
109
+ return { version: 1, updatedAt: Date.now(), drawers: [] };
110
+ }
111
+ }
112
+
113
+ function saveLegacyFallbackStore(store: LegacyFallbackStore): void {
114
+ const dir = dirname(TSUNAMI_LEGACY_FALLBACK_FILE);
115
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
116
+ store.updatedAt = Date.now();
117
+ writeFileSync(TSUNAMI_LEGACY_FALLBACK_FILE, JSON.stringify(store, null, 2), 'utf8');
118
+ }
119
+
120
+ function deleteLegacyFallbackDrawer(id: string): boolean {
121
+ const drawerId = String(id ?? '').trim();
122
+ if (!drawerId) return false;
123
+ const store = loadLegacyFallbackStore();
124
+ const nextDrawers = store.drawers.filter((drawer) => drawer.id !== drawerId);
125
+ if (nextDrawers.length === store.drawers.length) return false;
126
+ store.drawers = nextDrawers;
127
+ saveLegacyFallbackStore(store);
128
+ return true;
129
+ }
130
+
131
+ function buildIndexedMemoryContent(content: string, scope: {
132
+ wing?: string;
133
+ room?: string;
134
+ source?: string;
135
+ date?: string;
136
+ }): string {
137
+ const raw = String(content ?? '').trim();
138
+ if (!raw) return raw;
139
+ if (!isChineseHeavyText(raw)) return raw;
140
+ return buildChineseIndexedContent(raw, {
141
+ wing: scope.wing,
142
+ room: scope.room,
143
+ source_file: scope.source,
144
+ date: scope.date,
145
+ });
146
+ }
147
+
148
+ function resolveEntryScope(entry: Record<string, unknown>, fallback: {
149
+ wing: string;
150
+ room: string;
151
+ }): {
152
+ wing: string;
153
+ room: string;
154
+ basin: string;
155
+ current: string;
156
+ } {
157
+ const normalized = normalizeTsunamiTaxonomy({
158
+ wing: String(entry.wing ?? '').trim() || fallback.wing,
159
+ room: String(entry.room ?? '').trim() || fallback.room,
160
+ basin: String(entry.basin ?? '').trim() || undefined,
161
+ current: String(entry.current ?? '').trim() || undefined,
162
+ });
163
+ return normalized;
164
+ }
165
+
166
+ export function tryHandleTsunamiBunRequest(req: Record<string, unknown>): any | null {
167
+ const cmd = String(req.cmd ?? '').trim();
168
+ const normalizedScope = normalizeTsunamiTaxonomy({
169
+ wing: String(req.wing ?? '').trim() || undefined,
170
+ room: String(req.room ?? '').trim() || undefined,
171
+ basin: String(req.basin ?? '').trim() || undefined,
172
+ current: String(req.current ?? '').trim() || undefined,
173
+ });
174
+ const wing = normalizedScope.wing;
175
+ const room = normalizedScope.room;
176
+ const total = countBunMemoryEntries(wing || undefined);
177
+
178
+ if (cmd === 'tsunami_taxonomy') {
179
+ return {
180
+ ok: true,
181
+ data: describeTsunamiTaxonomy(),
182
+ text: formatTsunamiTaxonomyText(),
183
+ __backend: 'bun_native',
184
+ };
185
+ }
186
+
187
+ if (cmd === 'tsunami_ontology') {
188
+ return {
189
+ ok: true,
190
+ data: describeTsunamiGraphOntology(),
191
+ text: formatTsunamiGraphOntologyText(),
192
+ __backend: 'bun_native',
193
+ };
194
+ }
195
+
196
+ if (cmd === 'normalize_taxonomy') {
197
+ return {
198
+ ok: true,
199
+ data: normalizedScope,
200
+ text: formatTsunamiNormalizationText(req),
201
+ __backend: 'bun_native',
202
+ };
203
+ }
204
+
205
+ if (cmd === 'status') {
206
+ const identity = readTsunamiIdentity(TSUNAMI_IDENTITY_FILE);
207
+ const status = getBunMemoryStatus();
208
+ return {
209
+ ok: true,
210
+ data: {
211
+ ...status,
212
+ palace_path: BUN_MEMORY_DB_PATH,
213
+ total_drawers: Number(status.total ?? 0),
214
+ runtime_backend: 'bun_native',
215
+ compatibility_backend: 'python_wrapper_dormant',
216
+ compatibility_route: 'opt_in_only',
217
+ routing_summary: describeTsunamiRoutingMatrix(),
218
+ L0_identity: {
219
+ preview: identity.slice(0, 200),
220
+ tokens: countIdentityTokens(identity),
221
+ },
222
+ },
223
+ __backend: 'bun_native',
224
+ };
225
+ }
226
+
227
+ if (cmd === 'add') {
228
+ const rawContent = String(req.content ?? '').trim();
229
+ const indexedContent = buildIndexedMemoryContent(rawContent, {
230
+ wing: wing || 'ats',
231
+ room: room || 'ats/general',
232
+ source: String(req.source ?? 'tsunami_bun_direct').trim() || 'tsunami_bun_direct',
233
+ date: String(req.date ?? '').trim() || undefined,
234
+ });
235
+ const id = insertBunMemoryEntry({
236
+ wing: wing || 'ats',
237
+ room: room || 'ats/general',
238
+ content: indexedContent,
239
+ importance: Number(req.importance ?? 3),
240
+ source: String(req.source ?? 'tsunami_bun_direct').trim() || 'tsunami_bun_direct',
241
+ sessionId: String(req.session_id ?? '').trim() || undefined,
242
+ projectDir: String(req.project_dir ?? '').trim() || undefined,
243
+ fingerprint: String(req.fingerprint ?? '').trim() || undefined,
244
+ });
245
+ return {
246
+ ok: true,
247
+ id,
248
+ runtime_backend: 'bun_native',
249
+ aaak_indexed: indexedContent !== rawContent,
250
+ __backend: 'bun_native',
251
+ };
252
+ }
253
+
254
+ if (cmd === 'diary') {
255
+ const agent = String(req.agent ?? 'ats').trim() || 'ats';
256
+ const rawEntry = String(req.entry ?? '').trim();
257
+ const indexedEntry = buildIndexedMemoryContent(rawEntry, {
258
+ wing: wing || 'ats',
259
+ room: `diary-${agent}`,
260
+ source: `tsunami_bun_diary:${agent}`,
261
+ date: String(req.date ?? '').trim() || undefined,
262
+ });
263
+ const id = insertBunMemoryEntry({
264
+ wing: wing || 'ats',
265
+ room: `diary-${agent}`,
266
+ content: indexedEntry,
267
+ importance: Number(req.importance ?? 3),
268
+ source: 'tsunami_bun_diary',
269
+ });
270
+ return {
271
+ ok: true,
272
+ id,
273
+ runtime_backend: 'bun_native',
274
+ aaak_indexed: indexedEntry !== rawEntry,
275
+ __backend: 'bun_native',
276
+ };
277
+ }
278
+
279
+ if (cmd === 'mine') {
280
+ const entries = Array.isArray(req.entries) ? req.entries : [];
281
+ let stored = 0;
282
+ let indexed = 0;
283
+ let defaulted = 0;
284
+ const ids: string[] = [];
285
+ for (const rawEntry of entries) {
286
+ if (!rawEntry || typeof rawEntry !== 'object') continue;
287
+ const entry = rawEntry as Record<string, unknown>;
288
+ const text = String(entry.text ?? '').trim();
289
+ if (!text || text.length < 20) continue;
290
+ const scope = resolveEntryScope(entry, {
291
+ wing: wing || 'ats',
292
+ room: room || 'ats/general',
293
+ });
294
+ const source = String(entry.source ?? req.source ?? 'mine').trim() || 'mine';
295
+ const indexedContent = buildIndexedMemoryContent(text, {
296
+ wing: scope.wing,
297
+ room: scope.room,
298
+ source,
299
+ date: String(entry.date ?? req.date ?? '').trim() || undefined,
300
+ });
301
+ const id = insertBunMemoryEntry({
302
+ wing: scope.wing,
303
+ room: scope.room,
304
+ content: indexedContent,
305
+ importance: Number(entry.importance ?? req.importance ?? 3),
306
+ source,
307
+ sessionId: String(entry.session_id ?? req.session_id ?? '').trim() || undefined,
308
+ projectDir: String(entry.project_dir ?? req.project_dir ?? '').trim() || undefined,
309
+ });
310
+ ids.push(id);
311
+ stored += 1;
312
+ if (indexedContent !== text) indexed += 1;
313
+ if (!entry.wing && !entry.room && !entry.basin && !entry.current) defaulted += 1;
314
+ }
315
+ return {
316
+ ok: true,
317
+ stored,
318
+ indexed,
319
+ defaulted,
320
+ ids,
321
+ runtime_backend: 'bun_native',
322
+ __backend: 'bun_native',
323
+ };
324
+ }
325
+
326
+ if (cmd === 'kg_add') {
327
+ const subject = String(req.subject ?? '').trim();
328
+ const predicate = String(req.predicate ?? '').trim();
329
+ const object = String(req.object ?? '').trim();
330
+ if (!subject || !predicate || !object) {
331
+ return {
332
+ ok: false,
333
+ error: 'subject / predicate / object are required for kg_add',
334
+ __backend: 'bun_native',
335
+ };
336
+ }
337
+ const id = tsunamiGraphAddTriple({
338
+ subject,
339
+ subjectType: String(req.subject_type ?? '').trim() || undefined,
340
+ subjectProperties: req.subject_properties && typeof req.subject_properties === 'object'
341
+ ? (req.subject_properties as Record<string, unknown>)
342
+ : undefined,
343
+ predicate,
344
+ object,
345
+ objectType: String(req.object_type ?? '').trim() || undefined,
346
+ objectProperties: req.object_properties && typeof req.object_properties === 'object'
347
+ ? (req.object_properties as Record<string, unknown>)
348
+ : undefined,
349
+ validFrom: String(req.valid_from ?? '').trim() || undefined,
350
+ validTo: String(req.valid_to ?? '').trim() || undefined,
351
+ confidence: Number(req.confidence ?? 1.0),
352
+ sourceCloset: String(req.source_closet ?? '').trim() || undefined,
353
+ sourceFile: String(req.source_file ?? '').trim() || undefined,
354
+ });
355
+ return {
356
+ ok: true,
357
+ id,
358
+ runtime_backend: 'bun_native',
359
+ __backend: 'bun_native',
360
+ };
361
+ }
362
+
363
+ if (cmd === 'kg_query') {
364
+ const entity = String(req.entity ?? req.query ?? '').trim();
365
+ if (!entity) {
366
+ return {
367
+ ok: false,
368
+ error: 'entity is required for kg_query',
369
+ __backend: 'bun_native',
370
+ };
371
+ }
372
+ const rows = tsunamiGraphQueryEntity(
373
+ entity,
374
+ String(req.as_of ?? '').trim() || undefined,
375
+ (String(req.direction ?? '').trim() || 'outgoing') as 'outgoing' | 'incoming' | 'both',
376
+ );
377
+ return {
378
+ ok: true,
379
+ data: rows,
380
+ text: JSON.stringify(rows, null, 2),
381
+ runtime_backend: 'bun_native',
382
+ __backend: 'bun_native',
383
+ };
384
+ }
385
+
386
+ if (cmd === 'kg_invalidate') {
387
+ const subject = String(req.subject ?? '').trim();
388
+ const predicate = String(req.predicate ?? '').trim();
389
+ const object = String(req.object ?? '').trim();
390
+ if (!subject || !predicate || !object) {
391
+ return {
392
+ ok: false,
393
+ error: 'subject / predicate / object are required for kg_invalidate',
394
+ __backend: 'bun_native',
395
+ };
396
+ }
397
+ const changes = tsunamiGraphInvalidateTriple({
398
+ subject,
399
+ predicate,
400
+ object,
401
+ ended: String(req.ended ?? '').trim() || undefined,
402
+ });
403
+ return {
404
+ ok: true,
405
+ data: { changes },
406
+ runtime_backend: 'bun_native',
407
+ __backend: 'bun_native',
408
+ };
409
+ }
410
+
411
+ if (cmd === 'kg_stats') {
412
+ return {
413
+ ok: true,
414
+ data: {
415
+ ...tsunamiGraphStats(),
416
+ runtime_backend: 'bun_native',
417
+ },
418
+ __backend: 'bun_native',
419
+ };
420
+ }
421
+
422
+ if (cmd === 'traverse_graph') {
423
+ const startRoom = String(req.start_room ?? req.entity ?? req.query ?? '').trim();
424
+ if (!startRoom) {
425
+ return {
426
+ ok: false,
427
+ error: 'start_room is required for traverse_graph',
428
+ __backend: 'bun_native',
429
+ };
430
+ }
431
+ const data = tsunamiGraphTraverse(startRoom, Number(req.max_hops ?? 2));
432
+ return {
433
+ ok: !('error' in data),
434
+ data,
435
+ text: JSON.stringify(data, null, 2),
436
+ runtime_backend: 'bun_native',
437
+ __backend: 'bun_native',
438
+ ...(typeof data === 'object' && data && 'error' in data ? { error: String(data.error ?? 'traverse_graph failed') } : {}),
439
+ };
440
+ }
441
+
442
+ if (cmd === 'find_tunnels') {
443
+ const data = tsunamiGraphFindTunnels(
444
+ String(req.wing_a ?? req.basin_a ?? '').trim() || undefined,
445
+ String(req.wing_b ?? req.basin_b ?? '').trim() || undefined,
446
+ );
447
+ return {
448
+ ok: true,
449
+ data,
450
+ text: JSON.stringify(data, null, 2),
451
+ runtime_backend: 'bun_native',
452
+ __backend: 'bun_native',
453
+ };
454
+ }
455
+
456
+ if (cmd === 'graph_stats') {
457
+ return {
458
+ ok: true,
459
+ data: {
460
+ ...tsunamiGraphCompatStats(),
461
+ runtime_backend: 'bun_native',
462
+ },
463
+ runtime_backend: 'bun_native',
464
+ __backend: 'bun_native',
465
+ };
466
+ }
467
+
468
+ if (cmd === 'get_aaak_spec') {
469
+ return {
470
+ ok: true,
471
+ spec: getTsunamiAaakSpec(),
472
+ runtime_backend: 'bun_native',
473
+ __backend: 'bun_native',
474
+ };
475
+ }
476
+
477
+ if (cmd === 'classify' || cmd === 'classify_multi') {
478
+ const text = String(req.text ?? '').trim();
479
+ if (!text) {
480
+ return {
481
+ ok: false,
482
+ error: 'text is required for classify',
483
+ runtime_backend: 'bun_native',
484
+ __backend: 'bun_native',
485
+ };
486
+ }
487
+ const wantsMulti = cmd === 'classify_multi' || req.multi === true;
488
+ if (wantsMulti) {
489
+ return {
490
+ ok: true,
491
+ results: classifyTsunamiTextMulti(text, Number(req.top ?? 3)).map((entry) => ({
492
+ ...entry,
493
+ confidence: Number(entry.confidence.toFixed(2)),
494
+ })),
495
+ runtime_backend: 'bun_native',
496
+ __backend: 'bun_native',
497
+ };
498
+ }
499
+ const result = classifyTsunamiText(text);
500
+ return {
501
+ ok: true,
502
+ ...result,
503
+ confidence: Number(result.confidence.toFixed(2)),
504
+ runtime_backend: 'bun_native',
505
+ __backend: 'bun_native',
506
+ };
507
+ }
508
+
509
+ if (cmd === 'kg_timeline') {
510
+ const entity = String(req.entity ?? req.query ?? '').trim() || undefined;
511
+ const rows = tsunamiGraphTimeline(entity, Number(req.limit ?? 20)).map((row) => ({
512
+ ...row,
513
+ runtime_backend: 'bun_native',
514
+ }));
515
+ return {
516
+ ok: true,
517
+ data: rows,
518
+ runtime_backend: 'bun_native',
519
+ __backend: 'bun_native',
520
+ };
521
+ }
522
+
523
+ if (cmd === 'storm_center') {
524
+ const center = buildTsunamiStormCenter({
525
+ projectDir: String(req.project_dir ?? '').trim() || process.cwd(),
526
+ query: String(req.query ?? '').trim() || undefined,
527
+ sessionId: String(req.session_id ?? '').trim() || undefined,
528
+ refreshGraph: req.refresh_graph !== false,
529
+ evidenceLimit: Number(req.evidence_limit ?? 4),
530
+ signalLimit: Number(req.signal_limit ?? 4),
531
+ relationLimit: Number(req.relation_limit ?? 10),
532
+ });
533
+ return {
534
+ ok: true,
535
+ data: center,
536
+ text: formatTsunamiStormCenterText(center),
537
+ runtime_backend: 'bun_native',
538
+ __backend: 'bun_native',
539
+ };
540
+ }
541
+
542
+ if (cmd === 'wakeup') {
543
+ const scopeWing = wing || undefined;
544
+ const rows = wakeBunMemoryRows({ wing: scopeWing });
545
+ const identity = readTsunamiIdentity(TSUNAMI_IDENTITY_FILE);
546
+ const topWings = Object.entries(listBunMemoryWingCounts())
547
+ .sort((a, b) => b[1] - a[1])
548
+ .slice(0, 5)
549
+ .map(([name, count]) => `${name}:${count}`)
550
+ .join(' / ') || 'none';
551
+ const highlights = rows.map((row, index) => ` [${index + 1}] ${row.wing}/${row.room} ${buildBunMemoryPreview(row, 120)}`);
552
+ return {
553
+ ok: true,
554
+ data: [
555
+ '## L0 — IDENTITY',
556
+ identity,
557
+ '',
558
+ '## L1 — ESSENTIAL STORY',
559
+ 'backend=bun-native',
560
+ `total=${countBunMemoryEntries(scopeWing)}`,
561
+ `tsunami=${normalizedScope.basin}/${normalizedScope.current}`,
562
+ `top_wings=${topWings}`,
563
+ '',
564
+ ...highlights,
565
+ ].join('\n'),
566
+ __backend: 'bun_native',
567
+ };
568
+ }
569
+
570
+ if (cmd === 'search') {
571
+ const query = String(req.query ?? '').trim();
572
+ const rows = searchBunMemoryRows({
573
+ query,
574
+ wing: wing || undefined,
575
+ room: room || undefined,
576
+ limit: Number(req.limit ?? 5),
577
+ });
578
+ const lines = [`## L3 — SEARCH RESULTS for "${query}"`, 'backend=bun-native', `tsunami=${normalizedScope.basin}/${normalizedScope.current}`];
579
+ if (rows.length === 0) {
580
+ lines.push(' (no results)');
581
+ } else {
582
+ rows.forEach((row, index) => {
583
+ lines.push(` [${index + 1}] ${row.wing}/${row.room}`);
584
+ lines.push(` ${buildBunMemoryPreview(row, 200)}`);
585
+ });
586
+ }
587
+ return { ok: true, data: lines.join('\n'), __backend: 'bun_native' };
588
+ }
589
+
590
+ if (cmd === 'recall') {
591
+ const rows = recallBunMemoryRows({
592
+ wing: wing || undefined,
593
+ room: room || undefined,
594
+ limit: Number(req.limit ?? 10),
595
+ });
596
+ const lines = ['## L2 — RECALL', 'backend=bun-native', `tsunami=${normalizedScope.basin}/${normalizedScope.current}`];
597
+ if (rows.length === 0) {
598
+ lines.push(' (no memories)');
599
+ } else {
600
+ rows.forEach((row, index) => {
601
+ lines.push(` [${index + 1}] ${row.wing}/${row.room}`);
602
+ lines.push(` ${buildBunMemoryPreview(row, 220)}`);
603
+ });
604
+ }
605
+ return { ok: true, data: lines.join('\n'), __backend: 'bun_native' };
606
+ }
607
+
608
+ if (cmd === 'timeline') {
609
+ return {
610
+ ok: true,
611
+ data: listBunMemoryTimeline(Number(req.limit ?? 20)).map((row) => ({
612
+ ...row,
613
+ runtime_backend: 'bun_native',
614
+ })),
615
+ __backend: 'bun_native',
616
+ };
617
+ }
618
+
619
+ if (cmd === 'list_wings') {
620
+ return {
621
+ ok: true,
622
+ wings: listBunMemoryWingCounts(),
623
+ __backend: 'bun_native',
624
+ };
625
+ }
626
+
627
+ if (cmd === 'list_rooms') {
628
+ return {
629
+ ok: true,
630
+ wing: wing ?? 'all',
631
+ basin: normalizedScope.basin,
632
+ current: normalizedScope.current,
633
+ rooms: listBunMemoryRoomCounts(wing || undefined),
634
+ __backend: 'bun_native',
635
+ };
636
+ }
637
+
638
+ if (cmd === 'get_taxonomy') {
639
+ return {
640
+ ok: true,
641
+ taxonomy: getBunMemoryTaxonomy(),
642
+ tsunami: describeTsunamiTaxonomy(),
643
+ __backend: 'bun_native',
644
+ };
645
+ }
646
+
647
+ if (cmd === 'check_duplicate') {
648
+ return {
649
+ ok: true,
650
+ ...checkBunMemoryDuplicate(String(req.content ?? ''), Number(req.threshold ?? 0.9)),
651
+ __backend: 'bun_native',
652
+ };
653
+ }
654
+
655
+ if (cmd === 'delete_drawer') {
656
+ const drawerId = String(req.drawer_id ?? '').trim();
657
+ if (!drawerId) {
658
+ return {
659
+ ok: false,
660
+ error: 'drawer_id is required for delete_drawer',
661
+ __backend: 'bun_native',
662
+ };
663
+ }
664
+ if (deleteBunMemoryEntry(drawerId)) {
665
+ return {
666
+ ok: true,
667
+ deleted_id: drawerId,
668
+ deleted_from: isBunDrawerId(drawerId) ? 'bun_memory' : 'bun_memory_legacy_id',
669
+ __backend: 'bun_native',
670
+ };
671
+ }
672
+ if (deleteLegacyFallbackDrawer(drawerId)) {
673
+ return {
674
+ ok: true,
675
+ deleted_id: drawerId,
676
+ deleted_from: 'legacy_fallback_store',
677
+ __backend: 'bun_native',
678
+ };
679
+ }
680
+ if (!isBunDrawerId(drawerId)) {
681
+ return { ok: false, error: `Drawer not found: ${drawerId}`, __backend: 'bun_native' };
682
+ }
683
+ return { ok: false, error: `Drawer not found: ${drawerId}`, __backend: 'bun_native' };
684
+ }
685
+
686
+ if (cmd === 'diary_read') {
687
+ const agent = String(req.agent ?? 'ats').trim() || 'ats';
688
+ const rows = recallBunMemoryRows({
689
+ room: `diary-${agent}`,
690
+ limit: Number(req.last_n ?? 10),
691
+ });
692
+ return {
693
+ ok: true,
694
+ entries: rows.map((row) => ({
695
+ id: row.id,
696
+ date: new Date(row.created_at).toISOString(),
697
+ topic: row.room,
698
+ content: row.content,
699
+ })),
700
+ __backend: 'bun_native',
701
+ };
702
+ }
703
+
704
+ return null;
705
+ }