@plumpslabs/kuma 2.1.8 → 2.2.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.
@@ -0,0 +1,612 @@
1
+ // src/engine/sessionMemory.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var SessionMemory = class {
5
+ state;
6
+ initialized = false;
7
+ init(config) {
8
+ const kumaDir = path.join(config.projectRoot, ".kuma");
9
+ const sessionFile = path.join(kumaDir, "memory.json");
10
+ const oldFile = path.join(kumaDir, ".kuma-memory.json");
11
+ if (fs.existsSync(oldFile) && !fs.existsSync(sessionFile)) {
12
+ try {
13
+ fs.renameSync(oldFile, sessionFile);
14
+ console.error("[SessionMemory] Migrated .kuma-memory.json \u2192 memory.json");
15
+ } catch {
16
+ }
17
+ }
18
+ const oldSessionFile = path.join(kumaDir, "session.json");
19
+ if (fs.existsSync(oldSessionFile) && !fs.existsSync(sessionFile)) {
20
+ try {
21
+ fs.renameSync(oldSessionFile, sessionFile);
22
+ console.error("[SessionMemory] Migrated session.json \u2192 memory.json");
23
+ } catch {
24
+ }
25
+ }
26
+ if (fs.existsSync(sessionFile)) {
27
+ try {
28
+ const raw = fs.readFileSync(sessionFile, "utf-8");
29
+ const parsed = JSON.parse(raw);
30
+ this.state = {
31
+ projectRoot: parsed.projectRoot || config.projectRoot,
32
+ startTime: parsed.startTime || config.startTime,
33
+ currentGoal: parsed.currentGoal || "",
34
+ completedSteps: parsed.completedSteps || [],
35
+ modifiedFiles: new Map(parsed.modifiedFiles || []),
36
+ failedFiles: new Map(parsed.failedFiles || []),
37
+ searchResults: new Map(parsed.searchResults || []),
38
+ dependencyGraph: new Map(parsed.dependencyGraph || []),
39
+ toolCalls: parsed.toolCalls || [],
40
+ conventions: parsed.conventions
41
+ };
42
+ this.initialized = true;
43
+ console.error(`[SessionMemory] Loaded persistent session memory.json`);
44
+ return;
45
+ } catch (err) {
46
+ console.error(`[SessionMemory] Failed to load persistent session: ${err}. Re-initializing.`);
47
+ }
48
+ }
49
+ this.state = {
50
+ projectRoot: config.projectRoot,
51
+ startTime: config.startTime,
52
+ currentGoal: "",
53
+ completedSteps: [],
54
+ modifiedFiles: /* @__PURE__ */ new Map(),
55
+ failedFiles: /* @__PURE__ */ new Map(),
56
+ searchResults: /* @__PURE__ */ new Map(),
57
+ dependencyGraph: /* @__PURE__ */ new Map(),
58
+ toolCalls: []
59
+ };
60
+ this.initialized = true;
61
+ this.save();
62
+ this.ensureMemoriesDir();
63
+ console.error(`[SessionMemory] Initialized at ${new Date(config.startTime).toISOString()}`);
64
+ }
65
+ memoriesDir() {
66
+ return path.join(this.state.projectRoot, ".kuma", "memories");
67
+ }
68
+ ensureMemoriesDir() {
69
+ const dir = this.memoriesDir();
70
+ if (!fs.existsSync(dir)) {
71
+ fs.mkdirSync(dir, { recursive: true });
72
+ }
73
+ }
74
+ memoryFilePath(topic) {
75
+ return path.join(this.memoriesDir(), `${topic}.md`);
76
+ }
77
+ getMemoryContent(topic) {
78
+ this.ensureInit();
79
+ this.ensureMemoriesDir();
80
+ const filePath = this.memoryFilePath(topic);
81
+ if (fs.existsSync(filePath)) {
82
+ return fs.readFileSync(filePath, "utf-8");
83
+ }
84
+ return this.generateMemory(topic);
85
+ }
86
+ writeMemory(topic, content) {
87
+ this.ensureInit();
88
+ this.ensureMemoriesDir();
89
+ this.writeMemoryFile(topic, content);
90
+ }
91
+ writeMemoryFile(topic, content) {
92
+ const filePath = this.memoryFilePath(topic);
93
+ fs.writeFileSync(filePath, content, "utf-8");
94
+ }
95
+ generateMemory(topic) {
96
+ switch (topic) {
97
+ case "architecture":
98
+ return this.generateArchitectureMd();
99
+ case "conventions":
100
+ return this.generateConventionsMd();
101
+ case "known-issues":
102
+ return this.generateKnownIssuesMd();
103
+ default:
104
+ return `# ${topic}
105
+
106
+ (empty)`;
107
+ }
108
+ }
109
+ generateArchitectureMd() {
110
+ const conv = this.state.conventions;
111
+ if (!conv) return `# Architecture
112
+
113
+ Run project_conventions first to auto-generate.`;
114
+ const stackKeys = ["framework", "projectType", "monorepo", "buildTool", "packageManager"];
115
+ const lines = [
116
+ "# Architecture",
117
+ "",
118
+ `Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
119
+ "",
120
+ "## Stack",
121
+ ""
122
+ ];
123
+ for (const key of stackKeys) {
124
+ if (conv[key] !== void 0) {
125
+ lines.push(`- **${key}**: ${JSON.stringify(conv[key])}`);
126
+ }
127
+ }
128
+ lines.push("", "## Project Layout", "");
129
+ if (conv.workspaces) {
130
+ lines.push(`- Monorepo workspaces: ${JSON.stringify(conv.workspaces)}`);
131
+ }
132
+ lines.push(`- Source root: ${conv.srcDir || "src/"}`);
133
+ return lines.join("\n");
134
+ }
135
+ generateConventionsMd() {
136
+ const conv = this.state.conventions;
137
+ if (!conv) return `# Conventions
138
+
139
+ Run project_conventions first to auto-generate.`;
140
+ const styleKeys = ["testRunner", "styling", "importAlias", "lintConfig", "codeStyle"];
141
+ const lines = [
142
+ "# Conventions",
143
+ "",
144
+ `Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
145
+ "",
146
+ "## Code Style",
147
+ ""
148
+ ];
149
+ for (const key of styleKeys) {
150
+ if (conv[key] !== void 0) {
151
+ lines.push(`- **${key}**: ${JSON.stringify(conv[key])}`);
152
+ }
153
+ }
154
+ if (conv.testRunner) {
155
+ lines.push("", `## Testing
156
+
157
+ - Test runner: ${conv.testRunner}`);
158
+ }
159
+ if (conv.importAlias) {
160
+ lines.push("", `## Imports
161
+
162
+ - Alias: \`${conv.importAlias}\``);
163
+ }
164
+ return lines.join("\n");
165
+ }
166
+ generateKnownIssuesMd() {
167
+ const allFailures = this.getFailedFiles();
168
+ const unresolved = allFailures.filter((f) => f.failures.some((ff) => !ff.resolved));
169
+ if (unresolved.length === 0) return `# Known Issues
170
+
171
+ No unresolved issues.`;
172
+ const lines = [
173
+ "# Known Issues",
174
+ "",
175
+ `Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`,
176
+ ""
177
+ ];
178
+ for (const f of unresolved) {
179
+ lines.push(`## ${f.task}`);
180
+ for (const ff of f.failures) {
181
+ if (!ff.resolved) {
182
+ lines.push(`- ${new Date(ff.timestamp).toISOString()}: ${ff.error}`);
183
+ }
184
+ }
185
+ lines.push("");
186
+ }
187
+ return lines.join("\n");
188
+ }
189
+ save() {
190
+ if (!this.initialized || !this.state) return;
191
+ try {
192
+ const kumaDir = path.join(this.state.projectRoot, ".kuma");
193
+ if (!fs.existsSync(kumaDir)) {
194
+ fs.mkdirSync(kumaDir, { recursive: true });
195
+ }
196
+ const serialized = {
197
+ projectRoot: this.state.projectRoot,
198
+ startTime: this.state.startTime,
199
+ currentGoal: this.state.currentGoal,
200
+ completedSteps: this.state.completedSteps,
201
+ modifiedFiles: Array.from(this.state.modifiedFiles.entries()),
202
+ failedFiles: Array.from(this.state.failedFiles.entries()),
203
+ searchResults: Array.from(this.state.searchResults.entries()),
204
+ dependencyGraph: Array.from(this.state.dependencyGraph.entries()),
205
+ toolCalls: this.state.toolCalls,
206
+ conventions: this.state.conventions
207
+ };
208
+ fs.writeFileSync(path.join(kumaDir, "memory.json"), JSON.stringify(serialized, null, 2), "utf-8");
209
+ } catch (err) {
210
+ console.error(`[SessionMemory] Failed to save session: ${err}`);
211
+ }
212
+ }
213
+ setGoal(goal) {
214
+ this.ensureInit();
215
+ this.state.currentGoal = goal;
216
+ this.save();
217
+ }
218
+ addCompletedStep(step) {
219
+ this.ensureInit();
220
+ if (!this.state.completedSteps.includes(step)) {
221
+ this.state.completedSteps.push(step);
222
+ this.save();
223
+ }
224
+ }
225
+ addModifiedFile(filePath) {
226
+ this.ensureInit();
227
+ const existing = this.state.modifiedFiles.get(filePath);
228
+ if (existing) {
229
+ existing.modifiedAt = Date.now();
230
+ existing.status = "modified";
231
+ } else {
232
+ this.state.modifiedFiles.set(filePath, {
233
+ filePath,
234
+ modifiedAt: Date.now(),
235
+ status: "modified"
236
+ });
237
+ }
238
+ this.save();
239
+ }
240
+ addCreatedFile(filePath) {
241
+ this.ensureInit();
242
+ this.state.modifiedFiles.set(filePath, {
243
+ filePath,
244
+ modifiedAt: Date.now(),
245
+ status: "created"
246
+ });
247
+ this.save();
248
+ }
249
+ addFailedFile(task, error) {
250
+ this.ensureInit();
251
+ const trimmedError = error?.trim() ?? "";
252
+ if (!trimmedError) return;
253
+ const truncatedError = trimmedError.substring(0, 500);
254
+ const failures = this.state.failedFiles.get(task) ?? [];
255
+ const lastUnresolved = [...failures].reverse().find((f) => !f.resolved);
256
+ if (lastUnresolved && lastUnresolved.error === truncatedError) return;
257
+ failures.push({
258
+ task,
259
+ error: truncatedError,
260
+ timestamp: Date.now(),
261
+ resolved: false
262
+ });
263
+ this.state.failedFiles.set(task, failures);
264
+ this.save();
265
+ this.ensureMemoriesDir();
266
+ this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
267
+ }
268
+ markFailureResolved(task) {
269
+ this.ensureInit();
270
+ const failures = this.state.failedFiles.get(task);
271
+ if (failures) {
272
+ let changed = false;
273
+ for (const f of failures) {
274
+ if (!f.resolved) {
275
+ f.resolved = true;
276
+ changed = true;
277
+ }
278
+ }
279
+ if (changed) {
280
+ this.save();
281
+ this.ensureMemoriesDir();
282
+ this.writeMemoryFile("known-issues", this.generateKnownIssuesMd());
283
+ }
284
+ }
285
+ }
286
+ addSearchResult(query, files) {
287
+ this.ensureInit();
288
+ this.state.searchResults.set(query, files);
289
+ this.save();
290
+ }
291
+ addDependency(file, dependsOn) {
292
+ this.ensureInit();
293
+ const deps = this.state.dependencyGraph.get(file) ?? [];
294
+ if (!deps.includes(dependsOn)) {
295
+ deps.push(dependsOn);
296
+ this.state.dependencyGraph.set(file, deps);
297
+ this.save();
298
+ }
299
+ }
300
+ recordToolCall(toolName, params) {
301
+ this.ensureInit();
302
+ this.state.toolCalls.push({
303
+ toolName,
304
+ params,
305
+ timestamp: Date.now()
306
+ });
307
+ if (this.state.toolCalls.length > 100) {
308
+ this.state.toolCalls = this.state.toolCalls.slice(-100);
309
+ }
310
+ this.save();
311
+ }
312
+ setConventions(conventions) {
313
+ this.ensureInit();
314
+ this.state.conventions = conventions;
315
+ this.save();
316
+ this.ensureMemoriesDir();
317
+ this.writeMemoryFile("architecture", this.generateArchitectureMd());
318
+ this.writeMemoryFile("conventions", this.generateConventionsMd());
319
+ }
320
+ // ============================================================
321
+ // KEYWORD SEARCH — Search session memory content
322
+ // ============================================================
323
+ /**
324
+ * Load previous session data into current session.
325
+ * Called by kuma_init() to hydrate conventions, failures, and modified files
326
+ * from a prior session stored in memory.json.
327
+ */
328
+ loadSession() {
329
+ this.ensureInit();
330
+ const kumaDir = path.join(this.state.projectRoot, ".kuma");
331
+ const sessionFile = path.join(kumaDir, "memory.json");
332
+ if (!fs.existsSync(sessionFile)) {
333
+ return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
334
+ }
335
+ try {
336
+ const raw = fs.readFileSync(sessionFile, "utf-8");
337
+ const parsed = JSON.parse(raw);
338
+ if (!this.state.conventions && parsed.conventions) {
339
+ this.state.conventions = parsed.conventions;
340
+ }
341
+ const prevFailed = parsed.failedFiles;
342
+ if (prevFailed) {
343
+ for (const [task, failures] of prevFailed) {
344
+ for (const f of failures) {
345
+ if (!f.resolved) {
346
+ const existing = this.state.failedFiles.get(task);
347
+ if (!existing?.some((ef) => ef.error === f.error)) {
348
+ this.state.failedFiles.set(task, [...existing || [], f]);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ const prevModified = parsed.modifiedFiles;
355
+ if (prevModified) {
356
+ for (const [filePath, mod] of prevModified) {
357
+ if (!this.state.modifiedFiles.has(filePath)) {
358
+ this.state.modifiedFiles.set(filePath, {
359
+ filePath,
360
+ modifiedAt: mod.modifiedAt,
361
+ status: mod.status
362
+ });
363
+ }
364
+ }
365
+ }
366
+ const prevSearch = parsed.searchResults;
367
+ if (prevSearch) {
368
+ for (const [query, files] of prevSearch) {
369
+ if (!this.state.searchResults.has(query)) {
370
+ this.state.searchResults.set(query, files);
371
+ }
372
+ }
373
+ }
374
+ const toolCallCount = parsed.toolCalls?.length ?? 0;
375
+ this.save();
376
+ return {
377
+ hasPrevSession: true,
378
+ toolCallCount,
379
+ hasConventions: !!this.state.conventions
380
+ };
381
+ } catch {
382
+ return { hasPrevSession: false, toolCallCount: 0, hasConventions: !!this.state.conventions };
383
+ }
384
+ }
385
+ /**
386
+ * Search through tool call history, memory files, search results,
387
+ * errors, and file modifications for a keyword.
388
+ */
389
+ searchMemory(query, limit = 20) {
390
+ this.ensureInit();
391
+ const results = [];
392
+ const q = query.toLowerCase();
393
+ for (const call of this.state.toolCalls) {
394
+ const paramStr = JSON.stringify(call.params);
395
+ if (call.toolName.toLowerCase().includes(q) || paramStr.toLowerCase().includes(q)) {
396
+ results.push({
397
+ type: `tool:${call.toolName}`,
398
+ content: `${call.toolName}(${JSON.stringify(call.params).substring(0, 200)})`,
399
+ timestamp: call.timestamp
400
+ });
401
+ }
402
+ }
403
+ for (const [queryStr, files] of this.state.searchResults) {
404
+ if (queryStr.toLowerCase().includes(q) || files.some((f) => f.toLowerCase().includes(q))) {
405
+ results.push({
406
+ type: "search",
407
+ content: `Query: "${queryStr}" \u2192 ${files.slice(0, 5).join(", ")}${files.length > 5 ? ` (+${files.length - 5} more)` : ""}`
408
+ });
409
+ }
410
+ }
411
+ for (const [, mod] of this.state.modifiedFiles) {
412
+ if (mod.filePath.toLowerCase().includes(q)) {
413
+ results.push({
414
+ type: `file:${mod.status}`,
415
+ content: `${mod.status.toUpperCase()}: ${mod.filePath}`,
416
+ timestamp: mod.modifiedAt
417
+ });
418
+ }
419
+ }
420
+ for (const [task, failures] of this.state.failedFiles) {
421
+ for (const f of failures) {
422
+ if (task.toLowerCase().includes(q) || f.error.toLowerCase().includes(q)) {
423
+ results.push({
424
+ type: `failure:${task}`,
425
+ content: `[${f.resolved ? "RESOLVED" : "UNRESOLVED"}] ${task}: ${f.error.substring(0, 200)}`,
426
+ timestamp: f.timestamp
427
+ });
428
+ }
429
+ }
430
+ }
431
+ for (const [file, deps] of this.state.dependencyGraph) {
432
+ if (file.toLowerCase().includes(q)) {
433
+ results.push({
434
+ type: "dependency",
435
+ content: `${file} depends on: ${deps.join(", ")}`
436
+ });
437
+ }
438
+ }
439
+ const memoriesDir = this.memoriesDir();
440
+ if (fs.existsSync(memoriesDir)) {
441
+ try {
442
+ const files = fs.readdirSync(memoriesDir);
443
+ for (const file of files) {
444
+ if (!file.endsWith(".md")) continue;
445
+ const filePath = path.join(memoriesDir, file);
446
+ try {
447
+ const content = fs.readFileSync(filePath, "utf-8");
448
+ const lines = content.split("\n");
449
+ for (let i = 0; i < lines.length; i++) {
450
+ if (lines[i].toLowerCase().includes(q)) {
451
+ const topicName = file.replace(/\.md$/, "");
452
+ results.push({
453
+ type: `memory:${topicName}`,
454
+ content: `[${topicName}] L${i + 1}: ${lines[i].trim().substring(0, 150)}`
455
+ });
456
+ if (results.filter((r) => r.type.startsWith("memory")).length >= 5) break;
457
+ }
458
+ }
459
+ } catch {
460
+ }
461
+ }
462
+ } catch {
463
+ }
464
+ }
465
+ results.sort((a, b) => {
466
+ if (a.timestamp && b.timestamp) return b.timestamp - a.timestamp;
467
+ if (a.timestamp) return -1;
468
+ if (b.timestamp) return 1;
469
+ return 0;
470
+ });
471
+ return results.slice(0, limit);
472
+ }
473
+ getConventions() {
474
+ this.ensureInit();
475
+ return this.state.conventions;
476
+ }
477
+ pruneMemory() {
478
+ this.ensureInit();
479
+ this.state.toolCalls = this.state.toolCalls.slice(-3);
480
+ this.state.searchResults.clear();
481
+ this.state.completedSteps = [];
482
+ this.save();
483
+ }
484
+ getSummary(topic) {
485
+ this.ensureInit();
486
+ if (topic) {
487
+ const content = this.getMemoryContent(topic);
488
+ return { topic, content };
489
+ }
490
+ const unresolvedFailures = [];
491
+ const seen = /* @__PURE__ */ new Set();
492
+ for (const [, failures] of this.state.failedFiles) {
493
+ for (const f of failures) {
494
+ if (f.resolved) continue;
495
+ const error = f.error.substring(0, 200);
496
+ if (!error.trim()) continue;
497
+ const key = `${f.task}::${error}`;
498
+ if (seen.has(key)) continue;
499
+ seen.add(key);
500
+ unresolvedFailures.push({ task: f.task, error });
501
+ }
502
+ }
503
+ const hasMemories = fs.existsSync(this.memoriesDir());
504
+ let availableMemories = [];
505
+ if (hasMemories) {
506
+ try {
507
+ availableMemories = fs.readdirSync(this.memoriesDir()).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
508
+ } catch {
509
+ }
510
+ }
511
+ return {
512
+ projectRoot: this.state.projectRoot,
513
+ sessionDuration: Math.round((Date.now() - this.state.startTime) / 1e3) + "s",
514
+ currentGoal: this.state.currentGoal,
515
+ completedSteps: this.state.completedSteps,
516
+ modifiedFiles: Array.from(this.state.modifiedFiles.values()),
517
+ unresolvedFailures,
518
+ toolCallCount: this.state.toolCalls.length,
519
+ recentSearches: Array.from(this.state.searchResults.keys()).slice(-5),
520
+ hasConventions: !!this.state.conventions,
521
+ ...availableMemories.length > 0 ? { availableMemories } : {},
522
+ ...availableMemories.length > 0 ? { hint: `Use get_session_memory({ topic: "<name>" }) to load a specific memory. Topics: ${availableMemories.join(", ")}` } : {}
523
+ };
524
+ }
525
+ getModifiedFiles() {
526
+ this.ensureInit();
527
+ return Array.from(this.state.modifiedFiles.values());
528
+ }
529
+ getFailedFiles() {
530
+ this.ensureInit();
531
+ const result = [];
532
+ for (const [task, failures] of this.state.failedFiles) {
533
+ result.push({ task, failures });
534
+ }
535
+ return result;
536
+ }
537
+ getToolCallHistory(limit = 10) {
538
+ this.ensureInit();
539
+ return this.state.toolCalls.slice(-limit);
540
+ }
541
+ detectLoop() {
542
+ this.ensureInit();
543
+ const recentCalls = this.state.toolCalls.slice(-10);
544
+ if (recentCalls.length < 6) return { isLooping: false };
545
+ const toolCounts = /* @__PURE__ */ new Map();
546
+ for (const call of recentCalls) {
547
+ toolCounts.set(call.toolName, (toolCounts.get(call.toolName) ?? 0) + 1);
548
+ }
549
+ for (const [toolName, count] of toolCounts) {
550
+ if (count >= 4) {
551
+ return {
552
+ isLooping: true,
553
+ toolName,
554
+ message: `Detected potential loop: "${toolName}" called ${count} times in last ${recentCalls.length} tool calls. Consider a different approach.`
555
+ };
556
+ }
557
+ }
558
+ return { isLooping: false };
559
+ }
560
+ ensureInit() {
561
+ if (!this.initialized) {
562
+ this.init({
563
+ projectRoot: process.cwd(),
564
+ startTime: Date.now()
565
+ });
566
+ }
567
+ }
568
+ };
569
+ var sessionMemory = new SessionMemory();
570
+ function getSessionMemory(topic) {
571
+ return sessionMemory.getSummary(topic);
572
+ }
573
+ function searchSessionMemory(params) {
574
+ const { query, limit = 20 } = params;
575
+ sessionMemory.recordToolCall("search_session_memory", { query, limit });
576
+ const results = sessionMemory.searchMemory(query, limit);
577
+ if (results.length === 0) {
578
+ return `\u{1F50D} **Search Memory** \u2014 No results for "${query}".`;
579
+ }
580
+ const lines = [
581
+ `\u{1F50D} **Search Memory** \u2014 ${results.length} results for "${query}"`,
582
+ ""
583
+ ];
584
+ for (const r of results) {
585
+ const icon = r.type.startsWith("tool:") ? "\u{1F6E0}\uFE0F" : r.type.startsWith("file:") ? r.type.includes("created") ? "\u2728" : "\u{1F4DD}" : r.type.startsWith("failure") ? "\u274C" : r.type.startsWith("memory") ? "\u{1F9E0}" : r.type === "search" ? "\u{1F50E}" : r.type === "dependency" ? "\u{1F517}" : "\u{1F4C4}";
586
+ const timeStr = r.timestamp ? new Date(r.timestamp).toLocaleTimeString() : "";
587
+ lines.push(`${icon} ${timeStr ? `[${timeStr}] ` : ""}${r.content}`);
588
+ }
589
+ lines.push("", `\u{1F4A1} Use get_session_memory({topic: "..."}) to load a specific memory topic.`);
590
+ return lines.join("\n");
591
+ }
592
+ function handleWriteMemory(params) {
593
+ const { topic, content, mode = "append" } = params;
594
+ const existing = sessionMemory.getMemoryContent(topic);
595
+ let finalContent = content;
596
+ if (mode === "prepend") {
597
+ finalContent = content + "\n\n" + existing;
598
+ } else if (mode === "append") {
599
+ finalContent = existing + "\n\n" + content;
600
+ }
601
+ sessionMemory.writeMemory(topic, finalContent);
602
+ sessionMemory.recordToolCall("write_memory", { topic, mode });
603
+ return `\u2705 Memory "${topic}" updated (mode: ${mode}).`;
604
+ }
605
+
606
+ export {
607
+ SessionMemory,
608
+ sessionMemory,
609
+ getSessionMemory,
610
+ searchSessionMemory,
611
+ handleWriteMemory
612
+ };