@crewx/memory 0.1.23-rc.163 → 0.1.23-rc.164

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.
@@ -1,1252 +1,99 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CATEGORIES = exports.MemoryEngine = exports.SUMMARIZE_LOCK_STALE_MS = exports.SUMMARIZER_FAILURE_THRESHOLD = void 0;
7
- exports.getMemoryConfig = getMemoryConfig;
8
- exports.createMemoryEngine = createMemoryEngine;
9
- const fs_1 = __importDefault(require("fs"));
10
- const path_1 = __importDefault(require("path"));
11
- const child_process_1 = require("child_process");
12
- const nanoid_1 = require("nanoid");
13
- const js_yaml_1 = __importDefault(require("js-yaml"));
14
- const parser_1 = require("./parser");
15
- const knowledge_core_1 = require("@crewx/knowledge-core");
16
- const sdk_1 = require("@crewx/sdk");
17
- exports.SUMMARIZER_FAILURE_THRESHOLD = 3;
18
- const SUMMARIZER_HEALTH_FILE = '.summarizer-health.json';
19
- const FIND_RESULT_LIMIT = 50;
20
- function resolveCrewxInvocation() {
21
- const resolution = (0, sdk_1.resolveCrewxExecutable)({
22
- env: process.env,
23
- cwd: process.cwd(),
24
- });
25
- if (!resolution.ok) {
26
- const error = new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
27
- Object.assign(error, { code: 'CLI_UNRESOLVABLE' });
28
- throw error;
29
- }
30
- const childInvocation = (0, sdk_1.createCrewxChildInvocation)(resolution, { baseEnv: process.env });
31
- const program = (0, sdk_1.resolveWindowsSpawnProgram)({
32
- command: childInvocation.command,
33
- env: childInvocation.env,
34
- cwd: process.cwd(),
35
- packageName: 'crewx',
36
- allowShellFallback: true,
37
- });
38
- const invocation = (0, sdk_1.materializeWindowsSpawnProgram)(program, childInvocation.args);
39
- return {
40
- command: invocation.command,
41
- args: invocation.argv,
42
- env: childInvocation.env,
43
- shell: invocation.shell,
44
- windowsHide: invocation.windowsHide,
45
- };
46
- }
47
- function execCrewx(args, options) {
48
- const invocation = resolveCrewxInvocation();
49
- return (0, child_process_1.execFileSync)(invocation.command, [...invocation.args, ...args], {
50
- ...options,
51
- env: invocation.env,
52
- ...(invocation.shell === undefined ? {} : { shell: invocation.shell }),
53
- ...(invocation.windowsHide === undefined ? {} : { windowsHide: invocation.windowsHide }),
54
- });
55
- }
56
- exports.SUMMARIZE_LOCK_STALE_MS = 10 * 60 * 1000;
57
- function parseCrewxResponse(raw) {
58
- const responseMatch = raw.match(/šŸ“„ Response:\s*─+\s*([\s\S]*?)(?=šŸ“ Working Directory|$)/);
59
- if (responseMatch?.[1]) {
60
- const captured = responseMatch[1].trim();
61
- const capturedFirstLine = captured.split('\n')[0]?.trim() ?? '';
62
- if (!capturedFirstLine.startsWith('{')) {
63
- return captured;
64
- }
65
- return parseJsonlContent(captured);
66
- }
67
- return parseJsonlContent(raw);
68
- }
69
- function parseJsonlContent(raw) {
70
- const lines = raw.split('\n').filter((l) => l.trim().startsWith('{'));
71
- let assistantText = '';
72
- let hasAssistantMessage = false;
73
- for (const line of lines) {
74
- try {
75
- const obj = JSON.parse(line);
76
- if (obj.type === 'message' && obj.role === 'assistant') {
77
- hasAssistantMessage = true;
78
- if (obj.content)
79
- assistantText += obj.content;
80
- }
81
- }
82
- catch (_e) {
83
- }
84
- }
85
- if (hasAssistantMessage)
86
- return assistantText.trim() || null;
87
- const trimmed = raw.trim();
88
- if (trimmed.length === 0)
89
- return null;
90
- const firstLine = trimmed.split('\n')[0]?.trim() ?? '';
91
- if (firstLine.startsWith('{'))
92
- return null;
93
- return trimmed;
94
- }
95
- function getProjectRoot() {
96
- try {
97
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
98
- }
99
- catch {
100
- return process.cwd();
101
- }
102
- }
103
- function getMemoryConfig() {
104
- try {
105
- const configPath = process.env.CREWX_CONFIG ?? path_1.default.join(getProjectRoot(), 'crewx.yaml');
106
- const raw = fs_1.default.readFileSync(configPath, 'utf8');
107
- const config = js_yaml_1.default.load(raw);
108
- const settings = config?.settings;
109
- return settings?.memory ?? {};
110
- }
111
- catch {
112
- return {};
113
- }
114
- }
115
- class MemoryEngine {
116
- constructor(config = {}) {
117
- this.searchIndexCache = new Map();
118
- const yamlConfig = getMemoryConfig();
119
- this.dataDir =
120
- config.dataDir ??
121
- process.env['MEMORY_DATA_DIR'] ??
122
- path_1.default.resolve(process.cwd(), 'memory');
123
- this.summaryDays =
124
- config.summaryDays ??
125
- (yamlConfig.summary_days != null ? Number(yamlConfig.summary_days) : undefined) ??
126
- (parseInt(process.env['MEMORY_SUMMARY_DAYS'] ?? '', 10) || 7);
127
- this.recentDays =
128
- config.recentDays ??
129
- (yamlConfig.recent_days != null ? Number(yamlConfig.recent_days) : undefined) ??
130
- (parseInt(process.env['MEMORY_RECENT_DAYS'] ?? '', 10) || 30);
131
- this.shortTermHours =
132
- config.shortTermHours ??
133
- (yamlConfig.short_term_hours != null ? Number(yamlConfig.short_term_hours) : undefined) ??
134
- 24;
135
- this.summarizerAgent =
136
- config.summarizerAgent ??
137
- yamlConfig.summarizer_agent ??
138
- '@memory_summarizer';
139
- this.searcherAgent =
140
- config.searcherAgent ??
141
- yamlConfig.searcher_agent ??
142
- '@memory_searcher';
143
- this.summarizerTimeout =
144
- config.summarizerTimeout ??
145
- (yamlConfig.summarizer_timeout != null ? Number(yamlConfig.summarizer_timeout) : undefined) ??
146
- 480000;
147
- this.searcherTimeout =
148
- config.searcherTimeout ??
149
- (yamlConfig.searcher_timeout != null ? Number(yamlConfig.searcher_timeout) : undefined) ??
150
- 300000;
151
- }
152
- getAgentDir(agentId) {
153
- return path_1.default.join(this.dataDir, agentId);
154
- }
155
- getEntriesDir(agentId) {
156
- return path_1.default.join(this.getAgentDir(agentId), 'entries');
157
- }
158
- getEntryFilesFingerprint(agentId) {
159
- const entriesDir = this.getEntriesDir(agentId);
160
- if (!fs_1.default.existsSync(entriesDir))
161
- return { files: [], fingerprint: '' };
162
- const files = fs_1.default.readdirSync(entriesDir)
163
- .filter((file) => file.endsWith('.md'))
164
- .sort();
165
- const fingerprint = files.map((file) => {
166
- const stat = fs_1.default.statSync(path_1.default.join(entriesDir, file));
167
- return `${file}\u0000${stat.size}\u0000${stat.mtimeMs}`;
168
- }).join('\u0001');
169
- return { files, fingerprint };
170
- }
171
- buildSearchIndex(agentId, files) {
172
- const entriesDir = this.getEntriesDir(agentId);
173
- const entries = files.map((file) => {
174
- const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
175
- const { data, content: body } = (0, parser_1.parseFrontmatter)(raw);
176
- return { ...data, file, body };
177
- });
178
- return (0, knowledge_core_1.createSearchIndex)(entries, { summary: 3, tags: 2, body: 1 });
179
- }
180
- getSearchIndex(agentId) {
181
- const snapshot = this.getEntryFilesFingerprint(agentId);
182
- const cached = this.searchIndexCache.get(agentId);
183
- if (cached?.fingerprint === snapshot.fingerprint) {
184
- return { files: snapshot.files, index: cached.index };
185
- }
186
- const index = this.buildSearchIndex(agentId, snapshot.files);
187
- this.searchIndexCache.set(agentId, { fingerprint: snapshot.fingerprint, index });
188
- return { files: snapshot.files, index };
189
- }
190
- invalidateSearchIndex(agentId) {
191
- this.searchIndexCache.delete(agentId);
192
- }
193
- createFindActionContext(agentId, keyword, results) {
194
- return {
195
- commandId: 'memory.find',
196
- params: { agentId, query: keyword },
197
- resultTags: [...new Set(results.flatMap((entry) => entry.tags ?? []))].slice(0, 3),
198
- resultIds: results.slice(0, 3).map((entry) => entry.id),
199
- resultCount: results.length,
200
- };
201
- }
202
- getSummaryPath(agentId) {
203
- return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
204
- }
205
- getDirtySummaryPath(agentId) {
206
- return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
207
- }
208
- getSummarizeLockPath() {
209
- return path_1.default.join(this.dataDir, '.summarize.lock');
210
- }
211
- ensureDir(dir) {
212
- if (!fs_1.default.existsSync(dir)) {
213
- fs_1.default.mkdirSync(dir, { recursive: true });
214
- }
215
- }
216
- generateId() {
217
- return (0, nanoid_1.nanoid)(6);
218
- }
219
- getToday() {
220
- return new Date().toISOString().slice(0, 10);
221
- }
222
- getLocalISO8601() {
223
- const now = new Date();
224
- const offset = -now.getTimezoneOffset();
225
- const sign = offset >= 0 ? '+' : '-';
226
- const offsetHours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
227
- const offsetMins = String(Math.abs(offset) % 60).padStart(2, '0');
228
- const y = now.getFullYear();
229
- const m = String(now.getMonth() + 1).padStart(2, '0');
230
- const d = String(now.getDate()).padStart(2, '0');
231
- const h = String(now.getHours()).padStart(2, '0');
232
- const min = String(now.getMinutes()).padStart(2, '0');
233
- const s = String(now.getSeconds()).padStart(2, '0');
234
- return `${y}-${m}-${d}T${h}:${min}:${s}${sign}${offsetHours}:${offsetMins}`;
235
- }
236
- getPrimaryTag(entry) {
237
- return (entry.tags?.[0]) ?? 'general';
238
- }
239
- toEntryTimestamp(entry) {
240
- if (entry['created_at'])
241
- return String(entry['created_at']);
242
- const d = entry['date'];
243
- const dateStr = d && typeof d === 'object' && typeof d.toISOString === 'function' ? d.toISOString().slice(0, 10) : String(d ?? '');
244
- if (dateStr.includes('T'))
245
- return dateStr;
246
- return dateStr + 'T09:00:00+09:00';
247
- }
248
- markSummaryDirty(agentId) {
249
- this.ensureDir(this.getAgentDir(agentId));
250
- fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
251
- }
252
- getSummarizerHealthPath() {
253
- return path_1.default.join(this.dataDir, SUMMARIZER_HEALTH_FILE);
254
- }
255
- readSummarizerHealth() {
256
- try {
257
- const raw = fs_1.default.readFileSync(this.getSummarizerHealthPath(), 'utf-8');
258
- const parsed = JSON.parse(raw);
259
- return {
260
- consecutiveFailures: typeof parsed.consecutiveFailures === 'number' ? parsed.consecutiveFailures : 0,
261
- notified: Boolean(parsed.notified),
262
- };
263
- }
264
- catch {
265
- return { consecutiveFailures: 0, notified: false };
266
- }
267
- }
268
- writeSummarizerHealth(state) {
269
- this.ensureDir(this.dataDir);
270
- fs_1.default.writeFileSync(this.getSummarizerHealthPath(), JSON.stringify(state), 'utf-8');
271
- }
272
- recordSummarizerFailure() {
273
- const state = this.readSummarizerHealth();
274
- state.consecutiveFailures += 1;
275
- if (state.consecutiveFailures >= exports.SUMMARIZER_FAILURE_THRESHOLD && !state.notified) {
276
- this.notifySummarizerDegraded(state.consecutiveFailures);
277
- state.notified = true;
278
- }
279
- this.writeSummarizerHealth(state);
280
- }
281
- recordSummarizerSuccess() {
282
- const state = this.readSummarizerHealth();
283
- if (state.consecutiveFailures === 0 && !state.notified)
284
- return;
285
- this.writeSummarizerHealth({ consecutiveFailures: 0, notified: false });
286
- }
287
- notifySummarizerDegraded(streakCount) {
288
- try {
289
- execCrewx([
290
- 'notify',
291
- 'AI memory summarization keeps failing — falling back to mechanical summaries',
292
- '--level=warning',
293
- '--body=callSummarizer(agent=' + this.summarizerAgent +
294
- ') failed ' + streakCount +
295
- ' times in a row, so summarization switched to the fallback (mechanical) path.',
296
- ], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15000 });
297
- }
298
- catch (e) {
299
- const err = e;
300
- console.error(`[memory] failed to send summarizer-health notify: ${err.message}`);
301
- }
302
- }
303
- loadAllEntries(agentId) {
304
- const entriesDir = this.getEntriesDir(agentId);
305
- if (!fs_1.default.existsSync(entriesDir))
306
- return [];
307
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
308
- const entries = [];
309
- for (const file of files) {
310
- const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
311
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
312
- const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
313
- const hasBody = Boolean(bodyText) && bodyText !== '(Add details here)';
314
- const entry = data;
315
- entries.push({ ...entry, file, hasBody });
316
- }
317
- entries.sort((a, b) => this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a)));
318
- return entries;
319
- }
320
- findEntryById(agentId, memoryId) {
321
- const entriesDir = this.getEntriesDir(agentId);
322
- if (!fs_1.default.existsSync(entriesDir))
323
- return null;
324
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
325
- for (const file of files) {
326
- const filePath = path_1.default.join(entriesDir, file);
327
- const raw = fs_1.default.readFileSync(filePath, 'utf-8');
328
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
329
- if (data.id === memoryId) {
330
- return {
331
- file,
332
- filePath,
333
- data: data,
334
- content,
335
- };
336
- }
337
- }
338
- return null;
339
- }
340
- save(agentId, summary, category = 'general', options = {}) {
341
- this.ensureDir(this.getEntriesDir(agentId));
342
- const id = this.generateId();
343
- const dateTime = this.getLocalISO8601();
344
- const tags = Array.isArray(options.tags)
345
- ? options.tags
346
- : options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
347
- const frontmatter = (0, parser_1.stringifyFrontmatter)({
348
- id,
349
- type: 'memory',
350
- date: dateTime,
351
- category,
352
- tags,
353
- summary,
354
- ...(options.important ? { important: true } : {}),
355
- });
356
- const filename = `${id}.md`;
357
- const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
358
- const body = options.body ?? '(Add details here)';
359
- const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
360
- fs_1.default.writeFileSync(filepath, content, 'utf-8');
361
- this.invalidateSearchIndex(agentId);
362
- console.log(`āœ… Saved: data/${agentId}/entries/${filename}`);
363
- console.log(` ID: ${id}`);
364
- console.log(` Tags: ${tags.join(', ') || '(none)'}`);
365
- console.log(` Category: ${category}`);
366
- try {
367
- this.markSummaryDirty(agentId);
368
- console.log(` šŸ“ summary.md update scheduled`);
369
- }
370
- catch (_e) {
371
- }
372
- const saveCtx = {
373
- commandId: 'memory.save',
374
- params: { agentId, tags: tags.join(',') },
375
- resultTags: tags.slice(0, 3),
376
- resultCount: 1,
377
- };
378
- const saveActions = (0, knowledge_core_1.generateNextActions)(saveCtx);
379
- const saveBlock = (0, knowledge_core_1.formatActions)(saveActions);
380
- if (saveBlock)
381
- console.log(saveBlock);
382
- }
383
- index(agentId, options = {}) {
384
- const summaryPath = this.getSummaryPath(agentId);
385
- const entries = this.loadAllEntries(agentId);
386
- const topics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))];
387
- if (options.recent) {
388
- const days = parseInt(String(options.recent), 10) || this.recentDays;
389
- this.recent(agentId, days);
390
- return;
391
- }
392
- const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
393
- const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
394
- console.log(`# ${agentId} memory summary\n`);
395
- const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
396
- ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
397
- : '-';
398
- console.log(`> ${entries.length} items | topics ${topics.length} items | updated: ${summaryLastUpdated}`);
399
- if (options.full) {
400
- const byCat = {};
401
- for (const entry of entries) {
402
- const cat = entry.category ?? 'general';
403
- byCat[cat] = (byCat[cat] ?? 0) + 1;
404
- }
405
- const catSummary = Object.entries(byCat)
406
- .sort((a, b) => b[1] - a[1])
407
- .map(([cat, count]) => `${cat}:${count}`)
408
- .join(' ');
409
- console.log(`> šŸ“Š ${catSummary}`);
410
- }
411
- console.log('');
412
- if (shortTermEntries.length > 0) {
413
- const sortedShortTerm = [...shortTermEntries].sort((a, b) => {
414
- const aImp = a.important ? 1 : 0;
415
- const bImp = b.important ? 1 : 0;
416
- if (aImp !== bImp)
417
- return bImp - aImp;
418
- return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
419
- });
420
- console.log(`## šŸ”„ last Task (24h)\n`);
421
- for (const e of sortedShortTerm) {
422
- const star = e.important ? '⭐ ' : '';
423
- console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
424
- }
425
- console.log('');
426
- }
427
- if (options.full) {
428
- if (fs_1.default.existsSync(summaryPath)) {
429
- const raw = fs_1.default.readFileSync(summaryPath, 'utf-8');
430
- const { content: summaryBody } = (0, parser_1.parseFrontmatter)(raw);
431
- if (entries.length > 0) {
432
- const newestDate = entries[0]?.date;
433
- const oldestDate = entries[entries.length - 1]?.date;
434
- const newest = newestDate ? new Date(`${newestDate}T00:00:00Z`) : null;
435
- const oldest = oldestDate ? new Date(`${oldestDate}T00:00:00Z`) : null;
436
- if (newest &&
437
- oldest &&
438
- !Number.isNaN(newest.getTime()) &&
439
- !Number.isNaN(oldest.getTime())) {
440
- const msPerDay = 24 * 60 * 60 * 1000;
441
- const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
442
- const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
443
- console.log('---');
444
- console.log(`šŸ“‹ Topic summary (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays} days)\n`);
445
- }
446
- }
447
- console.log(summaryBody.trim());
448
- }
449
- else {
450
- for (const tag of topics.sort()) {
451
- const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
452
- console.log(`- **${tag}** (${count} items)`);
453
- }
454
- }
455
- }
456
- const commandId = options.full ? 'memory.index-full' : 'memory.index';
457
- const ctx = {
458
- commandId,
459
- params: { agentId },
460
- resultTags: topics.slice(0, 3),
461
- resultIds: entries.slice(0, 3).map(e => e.id),
462
- resultCount: entries.length,
463
- };
464
- const actions = (0, knowledge_core_1.generateNextActions)(ctx);
465
- const block = (0, knowledge_core_1.formatActions)(actions);
466
- if (block)
467
- console.log(block);
468
- }
469
- generateIndexContent(agentId) {
470
- const entries = this.loadAllEntries(agentId);
471
- const today = this.getToday();
472
- const cutoff = new Date(Date.now() - this.recentDays * 24 * 60 * 60 * 1000)
473
- .toISOString()
474
- .slice(0, 10);
475
- const byCategory = {};
476
- const recent = [];
477
- for (const entry of entries) {
478
- if (entry.date >= cutoff)
479
- recent.push(entry);
480
- const category = entry.category ?? 'general';
481
- if (!byCategory[category])
482
- byCategory[category] = [];
483
- byCategory[category].push(entry);
484
- }
485
- let md = `# ${agentId} memory\n\n`;
486
- md += `> Last Updated: ${today}\n`;
487
- md += `> Total: ${entries.length} memories\n\n`;
488
- md += `## šŸ”„ Last ${this.recentDays} days\n\n`;
489
- if (recent.length === 0) {
490
- md += `(none)\n\n`;
491
- }
492
- else {
493
- md += `| ID | Date | Category | Summary | Details |\n`;
494
- md += `|----|------|----------|------|------|\n`;
495
- for (const entry of recent) {
496
- const detailIcon = entry.hasBody ? 'šŸ“„' : '-';
497
- md += `| ${entry.id} | ${entry.date} | ${entry.category ?? '-'} | [${entry.summary}](entries/${entry.file}) | ${detailIcon} |\n`;
498
- }
499
- md += `\n`;
500
- }
501
- md += `## šŸ·ļø By category\n\n`;
502
- for (const [category, items] of Object.entries(byCategory).sort()) {
503
- md += `- **${category}**: ${items.length} items\n`;
504
- }
505
- const olderCount = entries.length - recent.length;
506
- const oldestDate = entries[entries.length - 1]?.date ?? '-';
507
- const newestDate = entries[0]?.date ?? '-';
508
- md += `\n## šŸ“Š Stats\n\n`;
509
- md += `- Total: ${entries.length} items (${oldestDate} ~ ${newestDate})\n`;
510
- md += `- Last ${this.recentDays} days: ${recent.length} items\n`;
511
- if (olderCount > 0)
512
- md += `- Older than ${this.recentDays} days: ${olderCount} items\n`;
513
- return md;
514
- }
515
- topic(agentId, topicName) {
516
- const entries = this.loadAllEntries(agentId);
517
- const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
518
- if (filtered.length === 0) {
519
- console.log(`No memories found for topic '${topicName}'.`);
520
- return;
521
- }
522
- console.log(`## šŸ“‚ Topic: ${topicName} (${filtered.length} items)\n`);
523
- for (const entry of filtered) {
524
- console.log(`### [${entry.date}] ${entry.summary}`);
525
- console.log(`- File: data/${agentId}/entries/${entry.file}`);
526
- console.log(`- Category: ${entry.category ?? '-'}`);
527
- console.log(`- Tags: ${(entry.tags ?? []).join(', ') || '-'}`);
528
- console.log('');
529
- }
530
- }
531
- recent(agentId, days = 30) {
532
- const filtered = this.getRecentEntries(agentId, days);
533
- if (filtered.length === 0) {
534
- console.log(`No memories found in the last ${days} days.`);
535
- return;
536
- }
537
- console.log(`## šŸ”„ Last ${days} days (${filtered.length} items)\n`);
538
- for (const entry of filtered) {
539
- const detailIcon = entry.hasBody ? ' šŸ“„' : '';
540
- const timestamp = String(entry.date).includes('T')
541
- ? String(entry.date).slice(0, 16).replace('T', ' ')
542
- : entry.date;
543
- console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
544
- }
545
- }
546
- getRecentEntries(agentId, days = 30, opts = {}) {
547
- const entries = this.loadAllEntries(agentId);
548
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
549
- .toISOString()
550
- .slice(0, 10);
551
- const filtered = entries.filter((e) => this.resolveRecentDate(agentId, e).slice(0, 10) >= cutoff);
552
- if (!opts.includeBody)
553
- return filtered;
554
- return filtered.map((e) => ({
555
- ...e,
556
- body: this.readEntryBody(agentId, e.file),
557
- }));
558
- }
559
- resolveRecentDate(agentId, entry) {
560
- const updated = this.toDateString(entry.updated);
561
- if (updated)
562
- return updated;
563
- const createdAt = this.toDateString(entry['created_at']);
564
- if (createdAt)
565
- return createdAt;
566
- const date = this.toDateString(entry.date);
567
- if (date)
568
- return date;
569
- try {
570
- const filePath = path_1.default.join(this.getEntriesDir(agentId), entry.file);
571
- return fs_1.default.statSync(filePath).mtime.toISOString();
572
- }
573
- catch {
574
- return '';
575
- }
576
- }
577
- toDateString(value) {
578
- if (value &&
579
- typeof value === 'object' &&
580
- typeof value.toISOString === 'function') {
581
- return value.toISOString();
582
- }
583
- return typeof value === 'string' ? value : '';
584
- }
585
- readEntryBody(agentId, file) {
586
- try {
587
- const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), file), 'utf-8');
588
- const { content } = (0, parser_1.parseFrontmatter)(raw);
589
- return content.replace(/^#[^\n]*\n*/m, '').trim();
590
- }
591
- catch {
592
- return '';
593
- }
594
- }
595
- find(agentId, keyword) {
596
- const { files, index } = this.getSearchIndex(agentId);
597
- if (files.length === 0) {
598
- console.log('No memories found.');
599
- return;
600
- }
601
- const indexedResults = index.search(keyword, FIND_RESULT_LIMIT + 1);
602
- const truncated = indexedResults.length > FIND_RESULT_LIMIT;
603
- const results = indexedResults.slice(0, FIND_RESULT_LIMIT);
604
- if (results.length === 0) {
605
- console.log(`No related memories found for '${keyword}'.`);
606
- return;
607
- }
608
- const limitNote = truncated ? `, top ${FIND_RESULT_LIMIT}` : '';
609
- console.log(`## šŸ” Search results: "${keyword}" (${results.length} items${limitNote}, sorted by BM25 score)\n`);
610
- for (const entry of results) {
611
- const score = entry._score != null ? ` [${entry._score}pts]` : '';
612
- console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
613
- }
614
- const findCtx = this.createFindActionContext(agentId, keyword, results);
615
- const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
616
- const findBlock = (0, knowledge_core_1.formatActions)(findActions);
617
- if (findBlock)
618
- console.log(findBlock);
619
- }
620
- list(agentId, options = {}) {
621
- const entries = this.loadAllEntries(agentId);
622
- let filtered = entries;
623
- if (options.category) {
624
- filtered = filtered.filter(e => e.category === options.category);
625
- }
626
- if (options.tag) {
627
- const requiredTags = options.tag.split(',').map(t => t.trim());
628
- filtered = filtered.filter(e => requiredTags.every(t => (e.tags ?? []).includes(t)));
629
- }
630
- if (options.json) {
631
- const entriesDir = this.getEntriesDir(agentId);
632
- const result = filtered.map(e => {
633
- let content = '';
634
- if (e.file) {
635
- try {
636
- const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, e.file), 'utf-8');
637
- const { content: rawContent } = (0, parser_1.parseFrontmatter)(raw);
638
- const body = rawContent.replace(/^#[^\n]*\n*/m, '').trim();
639
- if (body !== '(Add details here)')
640
- content = body;
641
- }
642
- catch (_err) {
643
- }
644
- }
645
- return {
646
- id: e.id,
647
- title: e.summary ?? '',
648
- category: e.category ?? 'general',
649
- tags: e.tags ?? [],
650
- created_at: e.date ?? '',
651
- content,
652
- };
653
- });
654
- console.log(JSON.stringify(result, null, 2));
655
- return;
656
- }
657
- if (filtered.length === 0) {
658
- const filters = [];
659
- if (options.category)
660
- filters.push(`category=${options.category}`);
661
- if (options.tag)
662
- filters.push(`tag=${options.tag}`);
663
- console.log(`No memories match the filters. (${filters.join(', ') || 'all'})`);
664
- return;
665
- }
666
- console.log(`## šŸ“‹ List: ${agentId} (${filtered.length} items)\n`);
667
- for (const entry of filtered) {
668
- console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
669
- }
670
- const resultTags = [...new Set(filtered.flatMap(e => e.tags ?? []))].slice(0, 3);
671
- const resultIds = filtered.slice(0, 3).map(e => e.id);
672
- const listCtx = {
673
- commandId: 'memory.list',
674
- params: { agentId, ...(options.category ? { category: options.category } : {}) },
675
- resultTags,
676
- resultIds,
677
- resultCount: filtered.length,
678
- };
679
- const listActions = (0, knowledge_core_1.generateNextActions)(listCtx);
680
- const listBlock = (0, knowledge_core_1.formatActions)(listActions);
681
- if (listBlock)
682
- console.log(listBlock);
683
- }
684
- stats(agentId) {
685
- const entries = this.loadAllEntries(agentId);
686
- if (entries.length === 0) {
687
- console.log('No memories found.');
688
- return;
689
- }
690
- const byCategory = {};
691
- for (const entry of entries) {
692
- const cat = entry.category ?? 'general';
693
- byCategory[cat] = (byCategory[cat] ?? 0) + 1;
694
- }
695
- const byTag = {};
696
- for (const entry of entries) {
697
- for (const tag of entry.tags ?? []) {
698
- byTag[tag] = (byTag[tag] ?? 0) + 1;
699
- }
700
- }
701
- console.log(`# ${agentId} Stats (${entries.length} items)\n`);
702
- console.log(`## By category`);
703
- for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
704
- console.log(` ${cat}: ${count} items`);
705
- }
706
- console.log(`\n## Top 20 Tags`);
707
- const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
708
- for (const [tag, count] of sortedTags) {
709
- console.log(` ${tag}: ${count} items`);
710
- }
711
- const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
712
- const statsCtx = {
713
- commandId: 'memory.stats',
714
- params: { agentId },
715
- resultTags: topCategories.slice(0, 3),
716
- resultCount: entries.length,
717
- };
718
- const statsActions = (0, knowledge_core_1.generateNextActions)(statsCtx);
719
- const statsBlock = (0, knowledge_core_1.formatActions)(statsActions);
720
- if (statsBlock)
721
- console.log(statsBlock);
722
- }
723
- tags(agentId) {
724
- const entries = this.loadAllEntries(agentId);
725
- if (entries.length === 0) {
726
- console.log('No memories found.');
727
- return;
728
- }
729
- const byTag = {};
730
- for (const entry of entries) {
731
- for (const tag of entry.tags ?? []) {
732
- byTag[tag] = (byTag[tag] ?? 0) + 1;
733
- }
734
- }
735
- const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
736
- if (sortedTags.length === 0) {
737
- console.log('No tags found.');
738
- return;
739
- }
740
- console.log(`## šŸ·ļø All tags (${sortedTags.length} items)\n`);
741
- for (const [tag, count] of sortedTags) {
742
- console.log(` ${tag}: ${count} items`);
743
- }
744
- }
745
- get(agentId, memoryId) {
746
- const entriesDir = this.getEntriesDir(agentId);
747
- if (!fs_1.default.existsSync(entriesDir)) {
748
- console.log('No memories found.');
749
- return;
750
- }
751
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
752
- for (const file of files) {
753
- const filePath = path_1.default.join(entriesDir, file);
754
- const raw = fs_1.default.readFileSync(filePath, 'utf-8');
755
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
756
- if (data.id === memoryId) {
757
- const d = data;
758
- console.log(`## šŸ“„ ${d.summary}\n`);
759
- console.log(`- ID: ${d.id}`);
760
- console.log(`- Date: ${d.date}`);
761
- console.log(`- Category: ${d.category ?? '-'}`);
762
- console.log(`- Tags: ${(d.tags ?? []).join(', ') || '-'}`);
763
- console.log(`- File: data/${agentId}/entries/${file}`);
764
- console.log(`\n---\n`);
765
- console.log(content.trim());
766
- const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
767
- if (fs_1.default.existsSync(graphPath)) {
768
- try {
769
- const graph = JSON.parse(fs_1.default.readFileSync(graphPath, 'utf-8'));
770
- const relatedEdges = graph.edges
771
- .filter((e) => e.from === memoryId || e.to === memoryId)
772
- .sort((a, b) => b.weight - a.weight)
773
- .slice(0, 5);
774
- if (relatedEdges.length > 0) {
775
- const nodeMap = {};
776
- graph.nodes.forEach((n) => {
777
- nodeMap[n.id] = n;
778
- });
779
- console.log(`\n---\n`);
780
- console.log(`## šŸ”— Related memories (${relatedEdges.length} items)\n`);
781
- for (const edge of relatedEdges) {
782
- const otherId = edge.from === memoryId ? edge.to : edge.from;
783
- const other = nodeMap[otherId];
784
- if (other) {
785
- console.log(`[${edge.weight}] [${otherId}] ${other.summary.slice(0, 50)}...`);
786
- }
787
- }
788
- }
789
- }
790
- catch (_e) {
791
- }
792
- }
793
- const getCtx = {
794
- commandId: 'memory.get',
795
- params: { agentId, id: memoryId },
796
- resultTags: (d.tags ?? []).slice(0, 3),
797
- resultIds: [memoryId],
798
- resultCount: 1,
799
- };
800
- const getActions = (0, knowledge_core_1.generateNextActions)(getCtx);
801
- const getBlock = (0, knowledge_core_1.formatActions)(getActions);
802
- if (getBlock)
803
- console.log(getBlock);
804
- return;
805
- }
806
- }
807
- console.log(`ID '${memoryId}' not found.`);
808
- }
809
- update(agentId, memoryId, options = {}) {
810
- const entry = this.findEntryById(agentId, memoryId);
811
- if (!entry) {
812
- console.log(`āŒ ID '${memoryId}' not found.`);
813
- process.exit(1);
814
- }
815
- const { filePath, data, content } = entry;
816
- const existingBody = content.replace(/^#[^\n]*\n*/m, '').trim();
817
- const tags = Array.isArray(options.tags)
818
- ? options.tags
819
- : options.tags ? options.tags.split(',').map((t) => t.trim()) : (data.tags ?? []);
820
- const newData = {
821
- ...data,
822
- summary: options.summary ?? data.summary,
823
- category: options.category ?? data.category,
824
- tags,
825
- updated: this.getToday(),
826
- };
827
- if (options.important) {
828
- newData['important'] = true;
829
- }
830
- else if (options['no-important']) {
831
- delete newData['important'];
832
- }
833
- const newBody = options.body ?? existingBody ?? '(Add details here)';
834
- const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
835
- const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
836
- fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
837
- this.invalidateSearchIndex(agentId);
838
- console.log(`āœ… Updated: ${entry.file}`);
839
- console.log(` ID: ${memoryId}`);
840
- if (options.summary)
841
- console.log(` Summary: ${String(newData['summary'])}`);
842
- if (options.category)
843
- console.log(` Category: ${String(newData['category'])}`);
844
- if (options.tags)
845
- console.log(` Tags: ${newData['tags'].join(', ')}`);
846
- if (options.body)
847
- console.log(` Body: (updated)`);
848
- }
849
- delete(agentId, memoryId, options = {}) {
850
- const entry = this.findEntryById(agentId, memoryId);
851
- if (!entry) {
852
- console.log(`āŒ ID '${memoryId}' not found.`);
853
- process.exit(1);
854
- }
855
- if (!options.force) {
856
- console.log(`āš ļø Delete target: [${entry.data.id}] ${entry.data.summary}`);
857
- console.log(` File: ${entry.file}`);
858
- console.log(`\nAdd --force to delete it.`);
859
- console.log(`Example: memory delete ${agentId} ${memoryId} --force`);
860
- return;
861
- }
862
- fs_1.default.unlinkSync(entry.filePath);
863
- this.invalidateSearchIndex(agentId);
864
- console.log(`šŸ—‘ļø Deleted: ${entry.file}`);
865
- console.log(` ID: ${memoryId}`);
866
- console.log(` Summary: ${entry.data.summary}`);
867
- try {
868
- this.markSummaryDirty(agentId);
869
- console.log(` šŸ“ summary.md update scheduled`);
870
- }
871
- catch (_e) {
872
- }
873
- }
874
- merge(agentId, memoryId1, memoryId2, options = {}) {
875
- const entry1 = this.findEntryById(agentId, memoryId1);
876
- const entry2 = this.findEntryById(agentId, memoryId2);
877
- if (!entry1) {
878
- console.log(`āŒ ID '${memoryId1}' not found.`);
879
- process.exit(1);
880
- }
881
- if (!entry2) {
882
- console.log(`āŒ ID '${memoryId2}' not found.`);
883
- process.exit(1);
884
- }
885
- const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
886
- const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
887
- const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
888
- const mergedBody = `## Merged memory 1 (${entry1.data.id}, ${entry1.data.date})
889
- ${entry1.data.summary}
1
+ "use strict";var v=exports&&exports.__importDefault||function($){return $&&$.__esModule?$:{"default":$}};Object.defineProperty(exports,"__esModule",{value:true});exports.CATEGORIES=exports.MemoryEngine=exports.SUMMARIZE_LOCK_STALE_MS=exports.SUMMARIZER_FAILURE_THRESHOLD=void 0;exports.getMemoryConfig=I;exports.createMemoryEngine=W;const l=v(require("fs"));const D=v(require("path"));const N=require("child_process");const R=require("nanoid");const B=v(require("js-yaml"));const T=require("./parser");const _=require("@crewx/knowledge-core");const M=require("@crewx/sdk");exports.SUMMARIZER_FAILURE_THRESHOLD=3;const H=".summarizer-health.json";const z=50;function U(){const $=(0,M.resolveCrewxExecutable)({env:process.env,cwd:process.cwd()});if(!$.ok){const n=new Error((0,M.formatCrewxExecutableFailure)($));Object.assign(n,{code:"CLI_UNRESOLVABLE"});throw n}const e=(0,M.createCrewxChildInvocation)($,{baseEnv:process.env});const t=(0,M.resolveWindowsSpawnProgram)({command:e.command,env:e.env,cwd:process.cwd(),packageName:"crewx",allowShellFallback:true});const r=(0,M.materializeWindowsSpawnProgram)(t,e.args);return{command:r.command,args:r.argv,env:e.env,shell:r.shell,windowsHide:r.windowsHide}}function k($,e){const t=U();return(0,N.execFileSync)(t.command,[...t.args,...$],{...e,env:t.env,...t.shell===void 0?{}:{shell:t.shell},...t.windowsHide===void 0?{}:{windowsHide:t.windowsHide}})}exports.SUMMARIZE_LOCK_STALE_MS=10*60*1e3;function j($){const e=$.match(/šŸ“„ Response:\s*─+\s*([\s\S]*?)(?=šŸ“ Working Directory|$)/);if(e?.[1]){const t=e[1].trim();const r=t.split("\n")[0]?.trim()??"";if(!r.startsWith("{")){return t}return O(t)}return O($)}function O($){const e=$.split("\n").filter(o=>o.trim().startsWith("{"));let t="";let r=false;for(const o of e){try{const i=JSON.parse(o);if(i.type==="message"&&i.role==="assistant"){r=true;if(i.content)t+=i.content}}catch(i){}}if(r)return t.trim()||null;const n=$.trim();if(n.length===0)return null;const s=n.split("\n")[0]?.trim()??"";if(s.startsWith("{"))return null;return n}function q(){try{return(0,N.execSync)("git rev-parse --show-toplevel",{encoding:"utf8"}).trim()}catch{return process.cwd()}}function I(){try{const $=process.env.CREWX_CONFIG??D.default.join(q(),"crewx.yaml");const e=l.default.readFileSync($,"utf8");const t=B.default.load(e);const r=t?.settings;return r?.memory??{}}catch{return{}}}class P{constructor(e={}){this.searchIndexCache=new Map;const t=I();this.dataDir=e.dataDir??process.env["MEMORY_DATA_DIR"]??D.default.resolve(process.cwd(),"memory");this.summaryDays=e.summaryDays??(t.summary_days!=null?Number(t.summary_days):void 0)??(parseInt(process.env["MEMORY_SUMMARY_DAYS"]??"",10)||7);this.recentDays=e.recentDays??(t.recent_days!=null?Number(t.recent_days):void 0)??(parseInt(process.env["MEMORY_RECENT_DAYS"]??"",10)||30);this.shortTermHours=e.shortTermHours??(t.short_term_hours!=null?Number(t.short_term_hours):void 0)??24;this.summarizerAgent=e.summarizerAgent??t.summarizer_agent??"@memory_summarizer";this.searcherAgent=e.searcherAgent??t.searcher_agent??"@memory_searcher";this.summarizerTimeout=e.summarizerTimeout??(t.summarizer_timeout!=null?Number(t.summarizer_timeout):void 0)??48e4;this.searcherTimeout=e.searcherTimeout??(t.searcher_timeout!=null?Number(t.searcher_timeout):void 0)??3e5}getAgentDir(e){return D.default.join(this.dataDir,e)}getEntriesDir(e){return D.default.join(this.getAgentDir(e),"entries")}getEntryFilesFingerprint(e){const t=this.getEntriesDir(e);if(!l.default.existsSync(t))return{files:[],fingerprint:""};const r=l.default.readdirSync(t).filter(s=>s.endsWith(".md")).sort();const n=r.map(s=>{const o=l.default.statSync(D.default.join(t,s));return`${s}\0${o.size}\0${o.mtimeMs}`}).join("");return{files:r,fingerprint:n}}buildSearchIndex(e,t){const r=this.getEntriesDir(e);const n=t.map(s=>{const o=l.default.readFileSync(D.default.join(r,s),"utf-8");const{data:i,content:d}=(0,T.parseFrontmatter)(o);return{...i,file:s,body:d}});return(0,_.createSearchIndex)(n,{summary:3,tags:2,body:1})}getSearchIndex(e){const t=this.getEntryFilesFingerprint(e);const r=this.searchIndexCache.get(e);if(r?.fingerprint===t.fingerprint){return{files:t.files,index:r.index}}const n=this.buildSearchIndex(e,t.files);this.searchIndexCache.set(e,{fingerprint:t.fingerprint,index:n});return{files:t.files,index:n}}invalidateSearchIndex(e){this.searchIndexCache.delete(e)}createFindActionContext(e,t,r){return{commandId:"memory.find",params:{agentId:e,query:t},resultTags:[...new Set(r.flatMap(n=>n.tags??[]))].slice(0,3),resultIds:r.slice(0,3).map(n=>n.id),resultCount:r.length}}getSummaryPath(e){return D.default.join(this.getAgentDir(e),"summary.md")}getDirtySummaryPath(e){return D.default.join(this.getAgentDir(e),".dirty-summary")}getSummarizeLockPath(){return D.default.join(this.dataDir,".summarize.lock")}ensureDir(e){if(!l.default.existsSync(e)){l.default.mkdirSync(e,{recursive:true})}}generateId(){return(0,R.nanoid)(6)}getToday(){return new Date().toISOString().slice(0,10)}getLocalISO8601(){const e=new Date;const t=-e.getTimezoneOffset();const r=t>=0?"+":"-";const n=String(Math.floor(Math.abs(t)/60)).padStart(2,"0");const s=String(Math.abs(t)%60).padStart(2,"0");const o=e.getFullYear();const i=String(e.getMonth()+1).padStart(2,"0");const d=String(e.getDate()).padStart(2,"0");const f=String(e.getHours()).padStart(2,"0");const a=String(e.getMinutes()).padStart(2,"0");const c=String(e.getSeconds()).padStart(2,"0");return`${o}-${i}-${d}T${f}:${a}:${c}${r}${n}:${s}`}getPrimaryTag(e){return e.tags?.[0]??"general"}toEntryTimestamp(e){if(e["created_at"])return String(e["created_at"]);const t=e["date"];const r=t&&typeof t==="object"&&typeof t.toISOString==="function"?t.toISOString().slice(0,10):String(t??"");if(r.includes("T"))return r;return r+"T09:00:00+09:00"}markSummaryDirty(e){this.ensureDir(this.getAgentDir(e));l.default.writeFileSync(this.getDirtySummaryPath(e),String(Date.now()),"utf-8")}getSummarizerHealthPath(){return D.default.join(this.dataDir,H)}readSummarizerHealth(){try{const e=l.default.readFileSync(this.getSummarizerHealthPath(),"utf-8");const t=JSON.parse(e);return{consecutiveFailures:typeof t.consecutiveFailures==="number"?t.consecutiveFailures:0,notified:Boolean(t.notified)}}catch{return{consecutiveFailures:0,notified:false}}}writeSummarizerHealth(e){this.ensureDir(this.dataDir);l.default.writeFileSync(this.getSummarizerHealthPath(),JSON.stringify(e),"utf-8")}recordSummarizerFailure(){const e=this.readSummarizerHealth();e.consecutiveFailures+=1;if(e.consecutiveFailures>=exports.SUMMARIZER_FAILURE_THRESHOLD&&!e.notified){this.notifySummarizerDegraded(e.consecutiveFailures);e.notified=true}this.writeSummarizerHealth(e)}recordSummarizerSuccess(){const e=this.readSummarizerHealth();if(e.consecutiveFailures===0&&!e.notified)return;this.writeSummarizerHealth({consecutiveFailures:0,notified:false})}notifySummarizerDegraded(e){try{k(["notify","AI memory summarization keeps failing \u2014 falling back to mechanical summaries","--level=warning","--body=callSummarizer(agent="+this.summarizerAgent+") failed "+e+" times in a row, so summarization switched to the fallback (mechanical) path."],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:15e3})}catch(t){const r=t;console.error(`[memory] failed to send summarizer-health notify: ${r.message}`)}}loadAllEntries(e){const t=this.getEntriesDir(e);if(!l.default.existsSync(t))return[];const r=l.default.readdirSync(t).filter(s=>s.endsWith(".md"));const n=[];for(const s of r){const o=l.default.readFileSync(D.default.join(t,s),"utf-8");const{data:i,content:d}=(0,T.parseFrontmatter)(o);const f=d.replace(/^#[^\n]*\n*/m,"").trim();const a=Boolean(f)&&f!=="(Add details here)";const c=i;n.push({...c,file:s,hasBody:a})}n.sort((s,o)=>this.toEntryTimestamp(o).localeCompare(this.toEntryTimestamp(s)));return n}findEntryById(e,t){const r=this.getEntriesDir(e);if(!l.default.existsSync(r))return null;const n=l.default.readdirSync(r).filter(s=>s.endsWith(".md"));for(const s of n){const o=D.default.join(r,s);const i=l.default.readFileSync(o,"utf-8");const{data:d,content:f}=(0,T.parseFrontmatter)(i);if(d.id===t){return{file:s,filePath:o,data:d,content:f}}}return null}save(e,t,r="general",n={}){this.ensureDir(this.getEntriesDir(e));const s=this.generateId();const o=this.getLocalISO8601();const i=Array.isArray(n.tags)?n.tags:n.tags?n.tags.split(",").map(h=>h.trim()):[];const d=(0,T.stringifyFrontmatter)({id:s,type:"memory",date:o,category:r,tags:i,summary:t,...n.important?{important:true}:{}});const f=`${s}.md`;const a=D.default.join(this.getEntriesDir(e),f);const c=n.body??"(Add details here)";const m=`${d}
890
2
 
891
- ${body1 !== '(Add details here)' ? body1 : '(No details)'}
3
+ # ${t}
892
4
 
5
+ ${c}
6
+ `;l.default.writeFileSync(a,m,"utf-8");this.invalidateSearchIndex(e);console.log(`\u2705 Saved: data/${e}/entries/${f}`);console.log(` ID: ${s}`);console.log(` Tags: ${i.join(", ")||"(none)"}`);console.log(` Category: ${r}`);try{this.markSummaryDirty(e);console.log(` \u{1F4DD} summary.md update scheduled`)}catch(h){}const u={commandId:"memory.save",params:{agentId:e,tags:i.join(",")},resultTags:i.slice(0,3),resultCount:1};const y=(0,_.generateNextActions)(u);const g=(0,_.formatActions)(y);if(g)console.log(g)}index(e,t={}){const r=this.getSummaryPath(e);const n=this.loadAllEntries(e);const s=[...new Set(n.map(u=>this.getPrimaryTag(u)))];if(t.recent){const u=parseInt(String(t.recent),10)||this.recentDays;this.recent(e,u);return}const o=new Date(Date.now()-this.shortTermHours*60*60*1e3).toISOString();const i=n.filter(u=>this.toEntryTimestamp(u)>=o);console.log(`# ${e} memory summary
7
+ `);const d=l.default.existsSync(r)?(0,T.parseFrontmatter)(l.default.readFileSync(r,"utf-8")).data.last_updated??"-":"-";console.log(`> ${n.length} items | topics ${s.length} items | updated: ${d}`);if(t.full){const u={};for(const g of n){const h=g.category??"general";u[h]=(u[h]??0)+1}const y=Object.entries(u).sort((g,h)=>h[1]-g[1]).map(([g,h])=>`${g}:${h}`).join(" ");console.log(`> \u{1F4CA} ${y}`)}console.log("");if(i.length>0){const u=[...i].sort((y,g)=>{const h=y.important?1:0;const w=g.important?1:0;if(h!==w)return w-h;return this.toEntryTimestamp(g).localeCompare(this.toEntryTimestamp(y))});console.log(`## \u{1F525} last Task (24h)
8
+ `);for(const y of u){const g=y.important?"\u2B50 ":"";console.log(`- ${g}[${y.id}] ${y.summary} (${this.getPrimaryTag(y)})`)}console.log("")}if(t.full){if(l.default.existsSync(r)){const u=l.default.readFileSync(r,"utf-8");const{content:y}=(0,T.parseFrontmatter)(u);if(n.length>0){const g=n[0]?.date;const h=n[n.length-1]?.date;const w=g?new Date(`${g}T00:00:00Z`):null;const E=h?new Date(`${h}T00:00:00Z`):null;if(w&&E&&!Number.isNaN(w.getTime())&&!Number.isNaN(E.getTime())){const F=24*60*60*1e3;const S=Math.max(1,Math.floor((w.getTime()-E.getTime())/F)+1);const A=x=>`${String(x.getMonth()+1).padStart(2,"0")}/${String(x.getDate()).padStart(2,"0")}`;console.log("---");console.log(`\u{1F4CB} Topic summary (${A(E)} ~ ${A(w)}, ${S} days)
9
+ `)}}console.log(y.trim())}else{for(const u of s.sort()){const y=n.filter(g=>this.getPrimaryTag(g)===u).length;console.log(`- **${u}** (${y} items)`)}}}const f=t.full?"memory.index-full":"memory.index";const a={commandId:f,params:{agentId:e},resultTags:s.slice(0,3),resultIds:n.slice(0,3).map(u=>u.id),resultCount:n.length};const c=(0,_.generateNextActions)(a);const m=(0,_.formatActions)(c);if(m)console.log(m)}generateIndexContent(e){const t=this.loadAllEntries(e);const r=this.getToday();const n=new Date(Date.now()-this.recentDays*24*60*60*1e3).toISOString().slice(0,10);const s={};const o=[];for(const c of t){if(c.date>=n)o.push(c);const m=c.category??"general";if(!s[m])s[m]=[];s[m].push(c)}let i=`# ${e} memory
10
+
11
+ `;i+=`> Last Updated: ${r}
12
+ `;i+=`> Total: ${t.length} memories
13
+
14
+ `;i+=`## \u{1F525} Last ${this.recentDays} days
15
+
16
+ `;if(o.length===0){i+=`(none)
17
+
18
+ `}else{i+=`| ID | Date | Category | Summary | Details |
19
+ `;i+=`|----|------|----------|------|------|
20
+ `;for(const c of o){const m=c.hasBody?"\u{1F4C4}":"-";i+=`| ${c.id} | ${c.date} | ${c.category??"-"} | [${c.summary}](entries/${c.file}) | ${m} |
21
+ `}i+=`
22
+ `}i+=`## \u{1F3F7}\uFE0F By category
23
+
24
+ `;for(const[c,m]of Object.entries(s).sort()){i+=`- **${c}**: ${m.length} items
25
+ `}const d=t.length-o.length;const f=t[t.length-1]?.date??"-";const a=t[0]?.date??"-";i+=`
26
+ ## \u{1F4CA} Stats
27
+
28
+ `;i+=`- Total: ${t.length} items (${f} ~ ${a})
29
+ `;i+=`- Last ${this.recentDays} days: ${o.length} items
30
+ `;if(d>0)i+=`- Older than ${this.recentDays} days: ${d} items
31
+ `;return i}topic(e,t){const r=this.loadAllEntries(e);const n=r.filter(s=>this.getPrimaryTag(s)===t);if(n.length===0){console.log(`No memories found for topic '${t}'.`);return}console.log(`## \u{1F4C2} Topic: ${t} (${n.length} items)
32
+ `);for(const s of n){console.log(`### [${s.date}] ${s.summary}`);console.log(`- File: data/${e}/entries/${s.file}`);console.log(`- Category: ${s.category??"-"}`);console.log(`- Tags: ${(s.tags??[]).join(", ")||"-"}`);console.log("")}}recent(e,t=30){const r=this.getRecentEntries(e,t);if(r.length===0){console.log(`No memories found in the last ${t} days.`);return}console.log(`## \u{1F525} Last ${t} days (${r.length} items)
33
+ `);for(const n of r){const s=n.hasBody?" \u{1F4C4}":"";const o=String(n.date).includes("T")?String(n.date).slice(0,16).replace("T"," "):n.date;console.log(`[${n.id}] [${o}] [${n.category??"-"}] ${n.summary}${s}`)}}getRecentEntries(e,t=30,r={}){const n=this.loadAllEntries(e);const s=new Date(Date.now()-t*24*60*60*1e3).toISOString().slice(0,10);const o=n.filter(i=>this.resolveRecentDate(e,i).slice(0,10)>=s);if(!r.includeBody)return o;return o.map(i=>({...i,body:this.readEntryBody(e,i.file)}))}resolveRecentDate(e,t){const r=this.toDateString(t.updated);if(r)return r;const n=this.toDateString(t["created_at"]);if(n)return n;const s=this.toDateString(t.date);if(s)return s;try{const o=D.default.join(this.getEntriesDir(e),t.file);return l.default.statSync(o).mtime.toISOString()}catch{return""}}toDateString(e){if(e&&typeof e==="object"&&typeof e.toISOString==="function"){return e.toISOString()}return typeof e==="string"?e:""}readEntryBody(e,t){try{const r=l.default.readFileSync(D.default.join(this.getEntriesDir(e),t),"utf-8");const{content:n}=(0,T.parseFrontmatter)(r);return n.replace(/^#[^\n]*\n*/m,"").trim()}catch{return""}}find(e,t){const{files:r,index:n}=this.getSearchIndex(e);if(r.length===0){console.log("No memories found.");return}const s=n.search(t,z+1);const o=s.length>z;const i=s.slice(0,z);if(i.length===0){console.log(`No related memories found for '${t}'.`);return}const d=o?`, top ${z}`:"";console.log(`## \u{1F50D} Search results: "${t}" (${i.length} items${d}, sorted by BM25 score)
34
+ `);for(const m of i){const u=m._score!=null?` [${m._score}pts]`:"";console.log(`[${m.id}] [${m.date}] [${this.getPrimaryTag(m)}]${u} ${m.summary}`)}const f=this.createFindActionContext(e,t,i);const a=(0,_.generateNextActions)(f);const c=(0,_.formatActions)(a);if(c)console.log(c)}list(e,t={}){const r=this.loadAllEntries(e);let n=r;if(t.category){n=n.filter(a=>a.category===t.category)}if(t.tag){const a=t.tag.split(",").map(c=>c.trim());n=n.filter(c=>a.every(m=>(c.tags??[]).includes(m)))}if(t.json){const a=this.getEntriesDir(e);const c=n.map(m=>{let u="";if(m.file){try{const y=l.default.readFileSync(D.default.join(a,m.file),"utf-8");const{content:g}=(0,T.parseFrontmatter)(y);const h=g.replace(/^#[^\n]*\n*/m,"").trim();if(h!=="(Add details here)")u=h}catch(y){}}return{id:m.id,title:m.summary??"",category:m.category??"general",tags:m.tags??[],created_at:m.date??"",content:u}});console.log(JSON.stringify(c,null,2));return}if(n.length===0){const a=[];if(t.category)a.push(`category=${t.category}`);if(t.tag)a.push(`tag=${t.tag}`);console.log(`No memories match the filters. (${a.join(", ")||"all"})`);return}console.log(`## \u{1F4CB} List: ${e} (${n.length} items)
35
+ `);for(const a of n){console.log(`[${a.id}] [${a.date}] [${this.getPrimaryTag(a)}] ${a.summary}`)}const s=[...new Set(n.flatMap(a=>a.tags??[]))].slice(0,3);const o=n.slice(0,3).map(a=>a.id);const i={commandId:"memory.list",params:{agentId:e,...t.category?{category:t.category}:{}},resultTags:s,resultIds:o,resultCount:n.length};const d=(0,_.generateNextActions)(i);const f=(0,_.formatActions)(d);if(f)console.log(f)}stats(e){const t=this.loadAllEntries(e);if(t.length===0){console.log("No memories found.");return}const r={};for(const a of t){const c=a.category??"general";r[c]=(r[c]??0)+1}const n={};for(const a of t){for(const c of a.tags??[]){n[c]=(n[c]??0)+1}}console.log(`# ${e} Stats (${t.length} items)
36
+ `);console.log(`## By category`);for(const[a,c]of Object.entries(r).sort((m,u)=>u[1]-m[1])){console.log(` ${a}: ${c} items`)}console.log(`
37
+ ## Top 20 Tags`);const s=Object.entries(n).sort((a,c)=>c[1]-a[1]).slice(0,20);for(const[a,c]of s){console.log(` ${a}: ${c} items`)}const o=Object.entries(r).sort((a,c)=>c[1]-a[1]).map(a=>a[0]);const i={commandId:"memory.stats",params:{agentId:e},resultTags:o.slice(0,3),resultCount:t.length};const d=(0,_.generateNextActions)(i);const f=(0,_.formatActions)(d);if(f)console.log(f)}tags(e){const t=this.loadAllEntries(e);if(t.length===0){console.log("No memories found.");return}const r={};for(const s of t){for(const o of s.tags??[]){r[o]=(r[o]??0)+1}}const n=Object.entries(r).sort((s,o)=>o[1]-s[1]);if(n.length===0){console.log("No tags found.");return}console.log(`## \u{1F3F7}\uFE0F All tags (${n.length} items)
38
+ `);for(const[s,o]of n){console.log(` ${s}: ${o} items`)}}get(e,t){const r=this.getEntriesDir(e);if(!l.default.existsSync(r)){console.log("No memories found.");return}const n=l.default.readdirSync(r).filter(s=>s.endsWith(".md"));for(const s of n){const o=D.default.join(r,s);const i=l.default.readFileSync(o,"utf-8");const{data:d,content:f}=(0,T.parseFrontmatter)(i);if(d.id===t){const a=d;console.log(`## \u{1F4C4} ${a.summary}
39
+ `);console.log(`- ID: ${a.id}`);console.log(`- Date: ${a.date}`);console.log(`- Category: ${a.category??"-"}`);console.log(`- Tags: ${(a.tags??[]).join(", ")||"-"}`);console.log(`- File: data/${e}/entries/${s}`);console.log(`
40
+ ---
41
+ `);console.log(f.trim());const c=D.default.join(this.getAgentDir(e),"graph.json");if(l.default.existsSync(c)){try{const g=JSON.parse(l.default.readFileSync(c,"utf-8"));const h=g.edges.filter(w=>w.from===t||w.to===t).sort((w,E)=>E.weight-w.weight).slice(0,5);if(h.length>0){const w={};g.nodes.forEach(E=>{w[E.id]=E});console.log(`
893
42
  ---
43
+ `);console.log(`## \u{1F517} Related memories (${h.length} items)
44
+ `);for(const E of h){const F=E.from===t?E.to:E.from;const S=w[F];if(S){console.log(`[${E.weight}] [${F}] ${S.summary.slice(0,50)}...`)}}}}catch(g){}}const m={commandId:"memory.get",params:{agentId:e,id:t},resultTags:(a.tags??[]).slice(0,3),resultIds:[t],resultCount:1};const u=(0,_.generateNextActions)(m);const y=(0,_.formatActions)(u);if(y)console.log(y);return}}console.log(`ID '${t}' not found.`)}update(e,t,r={}){const n=this.findEntryById(e,t);if(!n){console.log(`\u274C ID '${t}' not found.`);process.exit(1)}const{filePath:s,data:o,content:i}=n;const d=i.replace(/^#[^\n]*\n*/m,"").trim();const f=Array.isArray(r.tags)?r.tags:r.tags?r.tags.split(",").map(y=>y.trim()):o.tags??[];const a={...o,summary:r.summary??o.summary,category:r.category??o.category,tags:f,updated:this.getToday()};if(r.important){a["important"]=true}else if(r["no-important"]){delete a["important"]}const c=r.body??d??"(Add details here)";const m=(0,T.stringifyFrontmatter)(a);const u=`${m}
45
+
46
+ # ${String(a["summary"])}
47
+
48
+ ${c}
49
+ `;l.default.writeFileSync(s,u,"utf-8");this.invalidateSearchIndex(e);console.log(`\u2705 Updated: ${n.file}`);console.log(` ID: ${t}`);if(r.summary)console.log(` Summary: ${String(a["summary"])}`);if(r.category)console.log(` Category: ${String(a["category"])}`);if(r.tags)console.log(` Tags: ${a["tags"].join(", ")}`);if(r.body)console.log(` Body: (updated)`)}delete(e,t,r={}){const n=this.findEntryById(e,t);if(!n){console.log(`\u274C ID '${t}' not found.`);process.exit(1)}if(!r.force){console.log(`\u26A0\uFE0F Delete target: [${n.data.id}] ${n.data.summary}`);console.log(` File: ${n.file}`);console.log(`
50
+ Add --force to delete it.`);console.log(`Example: memory delete ${e} ${t} --force`);return}l.default.unlinkSync(n.filePath);this.invalidateSearchIndex(e);console.log(`\u{1F5D1}\uFE0F Deleted: ${n.file}`);console.log(` ID: ${t}`);console.log(` Summary: ${n.data.summary}`);try{this.markSummaryDirty(e);console.log(` \u{1F4DD} summary.md update scheduled`)}catch(s){}}merge(e,t,r,n={}){const s=this.findEntryById(e,t);const o=this.findEntryById(e,r);if(!s){console.log(`\u274C ID '${t}' not found.`);process.exit(1)}if(!o){console.log(`\u274C ID '${r}' not found.`);process.exit(1)}const i=s.content.replace(/^#[^\n]*\n*/m,"").trim();const d=o.content.replace(/^#[^\n]*\n*/m,"").trim();const f=n.summary??`${s.data.summary} + ${o.data.summary}`;const a=`## Merged memory 1 (${s.data.id}, ${s.data.date})
51
+ ${s.data.summary}
52
+
53
+ ${i!=="(Add details here)"?i:"(No details)"}
54
+
55
+ ---
56
+
57
+ ## Merged memory 2 (${o.data.id}, ${o.data.date})
58
+ ${o.data.summary}
59
+
60
+ ${d!=="(Add details here)"?d:"(No details)"}`;const c=[...new Set([...s.data.tags??[],...o.data.tags??[]])];const m=s.data.date>o.data.date?s.data.date:o.data.date;const u=this.generateId();const y={id:u,type:"memory",date:m,category:s.data.category,tags:c,summary:f,merged_from:[t,r]};const g=`${u}.md`;const h=D.default.join(this.getEntriesDir(e),g);const w=(0,T.stringifyFrontmatter)(y);const E=`${w}
61
+
62
+ # ${f}
63
+
64
+ ${a}
65
+ `;l.default.writeFileSync(h,E,"utf-8");l.default.unlinkSync(s.filePath);l.default.unlinkSync(o.filePath);this.invalidateSearchIndex(e);console.log(`\u{1F500} Merge complete!`);console.log(` New ID: ${u}`);console.log(` New file: ${g}`);console.log(` Merged memory: [${t}] + [${r}]`);console.log(` Summary: ${f}`)}buildFallbackSummary(e){const t={};for(const s of e){const o=this.getPrimaryTag(s);if(!t[o])t[o]=[];t[o].push(s)}const r=s=>{if(!s)return"-";const o=String(s).includes("T")?s:`${s}T00:00:00Z`;const i=new Date(o);if(Number.isNaN(i.getTime()))return"-";return`${String(i.getMonth()+1).padStart(2,"0")}/${String(i.getDate()).padStart(2,"0")}`};let n="";for(const[s,o]of Object.entries(t).sort()){const i=o.map(c=>c.date).filter(Boolean).sort();const d=i[0];const f=i[i.length-1];const a=d&&f?`, ${r(d)}~${r(f)}`:"";n+=`## ${s} (${o.length} items${a})
66
+ `;for(const c of o)n+=`- [${c.date}] ${c.summary}
67
+ `;for(const c of o)n+=`\u2192 [entries/${c.file}](entries/${c.file})
68
+ `;n+=`
69
+ `}return n}callSummarizer(e){try{const t=k(["q",this.summarizerAgent],{encoding:"utf-8",timeout:this.summarizerTimeout,stdio:["pipe","pipe","pipe"],input:e});const r=j(t);this.recordSummarizerSuccess();return r}catch(t){const r=t;const n=r.code==="ETIMEDOUT"||r.killed?`timed out after ${this.summarizerTimeout}ms`:r.message;console.error(`[memory] callSummarizer failed (agent=${this.summarizerAgent}), falling back to raw entry list: ${n}`);this.recordSummarizerFailure();return null}}generateSummary(e,t={}){const r=this.loadAllEntries(e);if(r.length===0)return{status:"empty"};const n=new Date(Date.now()-this.summaryDays*24*60*60*1e3).toISOString().slice(0,10);const s=t.force?r:r.filter(S=>S.date>=n);if(s.length===0)return{status:"empty",entryCount:r.length};const o=this.getSummaryPath(e);const i=l.default.existsSync(o)?(0,T.parseFrontmatter)(l.default.readFileSync(o,"utf-8")):null;const d=i?.data?.last_entry_id??null;const f=i?.content.trim()??null;const a=(S,A)=>{if(!A)return S;const x=S.findIndex(p=>p.id===A);if(x===-1)return S;return S.slice(0,x)};const c=a(s,d);if(c.length===0&&!t.force){return{status:"up-to-date",entryCount:r.length}}const m=t.force?s:c;const u=[...m].reverse().map(S=>{if(S.file){try{const A=l.default.readFileSync(D.default.join(this.getEntriesDir(e),S.file),"utf-8");const{content:x}=(0,T.parseFrontmatter)(A);const p=x.replace(/^#[^\n]*\n*/m,"").trim();return{...S,body:p}}catch(A){}}return S});const y=[...new Set(s.map(S=>this.getPrimaryTag(S)))].sort();const g=(S,A,x)=>{let p=`You are a memory summarizer for an AI agent.
70
+
71
+ `;if(S){p+=`## Existing summary:
72
+ ${S}
73
+
74
+ ## New entries to incorporate:
75
+ `}else{p+=`## All entries:
76
+ `}for(const b of A){const C=b.important?" [important]":"";p+=`- [${b.id}] [${b.date}] [${this.getPrimaryTag(b)}]${C} ${b.summary}`;if(b.body&&b.body!=="(Add details here)"){p+=` \u2014 ${b.body.slice(0,200)}`}p+=` (file: entries/${b.file})
77
+ `}p+=`
78
+ ## Instructions:
79
+ `;p+=`- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4 items, 01/28~02/14)"
80
+ `;p+=`- Write concise Korean summary per topic (2-5 sentences)
81
+ `;p+=`- After each topic summary, list relevant entry file links as "\u2192 [entries/filename](entries/filename)"
82
+ `;p+=`- Highlight key decisions, insights, and action items
83
+ `;p+=`- If there's an existing summary, merge new information into it
84
+ `;p+=`- Topics to cover: ${x.join(", ")}
85
+ `;p+=`- Output ONLY the markdown body (topic sections), no frontmatter or top-level heading
86
+ `;return p};const h=g(t.force?null:f,u,y);const w=this.callSummarizer(h);const E=s[0]?.id;if(!w){const S=s.map(p=>{if(p.file){try{const b=l.default.readFileSync(D.default.join(this.getEntriesDir(e),p.file),"utf-8");const{content:C}=(0,T.parseFrontmatter)(b);const L=C.replace(/^#[^\n]*\n*/m,"").trim();return{...p,body:L}}catch(b){}}return p});const A=this.buildFallbackSummary(S);const x=(0,T.stringifyFrontmatter)({last_entry_id:E,last_updated:this.getToday(),entry_count:r.length,summary_days:this.summaryDays,summarizer:"fallback"});l.default.writeFileSync(o,`${x}
87
+
88
+ ${A}`,"utf-8");return{status:"fallback",entryCount:r.length,newEntries:m.length}}const F=(0,T.stringifyFrontmatter)({last_entry_id:E,last_updated:this.getToday(),entry_count:r.length,summary_days:this.summaryDays,summarizer:"memory_summarizer"});l.default.writeFileSync(o,`${F}
894
89
 
895
- ## Merged memory 2 (${entry2.data.id}, ${entry2.data.date})
896
- ${entry2.data.summary}
90
+ ${w}
91
+ `,"utf-8");return{status:"updated",entryCount:r.length,newEntries:m.length}}summarize(e,t={}){const r=this.loadAllEntries(e);if(r.length===0){console.log("No memories found.");return}console.log(`\u{1F4DD} Starting summary generation (${r.length} items entries)
92
+ `);const n=this.generateSummary(e,{force:t.force});if(n.status==="up-to-date"){console.log("\u2705 summary.md is up to date.")}else if(n.status==="fallback"){console.log(`\u{1F4CB} Fallback summary generated (AI call failed)`)}else if(n.status==="updated"){console.log(`\u{1F916} Summary complete (${n.newEntries} items applied)`)}console.log(` File: data/${e}/summary.md`);console.log(` entries: ${n.entryCount} items`)}isSummarizeLockStale(e){let t;try{t=l.default.readFileSync(e,"utf-8")}catch{return true}const r=t.match(/^pid=(\d+)/m);const n=r?Number(r[1]):NaN;if(Number.isFinite(n)){try{process.kill(n,0);return false}catch(s){const o=s;if(o.code==="ESRCH")return true}}try{const s=l.default.statSync(e);return Date.now()-s.mtimeMs>exports.SUMMARIZE_LOCK_STALE_MS}catch{return true}}acquireSummarizeLock(){const e=this.getSummarizeLockPath();const t=`pid=${process.pid}
93
+ mtime=${new Date().toISOString()}
94
+ `;try{const r=l.default.openSync(e,"wx");l.default.writeSync(r,t);l.default.closeSync(r);return true}catch(r){const n=r;if(n.code!=="EEXIST")throw n}if(!this.isSummarizeLockStale(e)){return false}l.default.writeFileSync(e,t,"utf-8");return true}releaseSummarizeLock(){try{l.default.unlinkSync(this.getSummarizeLockPath())}catch{}}summarizeDirty(){if(!l.default.existsSync(this.dataDir)){console.log(`\u{1F4A1} Register debounce cron:`);console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);return}if(!this.acquireSummarizeLock()){console.log(`\u23ED\uFE0F summarize-dirty is already running (lock held) \u2014 skipping this run.`);return}try{const e=l.default.readdirSync(this.dataDir,{withFileTypes:true}).filter(t=>t.isDirectory()).map(t=>t.name);for(const t of e){const r=this.getDirtySummaryPath(t);if(!l.default.existsSync(r))continue;try{const n=this.generateSummary(t);if(n.status==="updated"){console.log(`\u{1F916} ${t} summary.md updated (${n.newEntries} items applied)`)}else if(n.status==="fallback"){console.log(`\u{1F4CB} ${t} Fallback summary generated (AI call failed)`)}else if(n.status==="up-to-date"){console.log(`\u2705 ${t} summary.md is up to date.`)}else if(n.status==="empty"){console.log(`\u2139\uFE0F ${t} has no memories to summarize.`)}l.default.unlinkSync(r)}catch(n){console.error(`\u274C ${t} summary.md update failed: ${n.message}`)}}}finally{this.releaseSummarizeLock()}console.log(`\u{1F4A1} Register debounce cron:`);console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`)}search(e,t){const r=this.loadAllEntries(e);if(r.length===0){console.log("No memories found.");return}const n=r.map(o=>`[${o.id}] [${o.date}] [${this.getPrimaryTag(o)}] ${o.summary}`).join("\n");const s=`Memory list:
95
+ ${n}
897
96
 
898
- ${body2 !== '(Add details here)' ? body2 : '(No details)'}`;
899
- const mergedTags = [
900
- ...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
901
- ];
902
- const newerDate = entry1.data.date > entry2.data.date ? entry1.data.date : entry2.data.date;
903
- const newId = this.generateId();
904
- const newData = {
905
- id: newId,
906
- type: 'memory',
907
- date: newerDate,
908
- category: entry1.data.category,
909
- tags: mergedTags,
910
- summary: mergedSummary,
911
- merged_from: [memoryId1, memoryId2],
912
- };
913
- const filename = `${newId}.md`;
914
- const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
915
- const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
916
- const newContent = `${newFrontmatter}\n\n# ${mergedSummary}\n\n${mergedBody}\n`;
917
- fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
918
- fs_1.default.unlinkSync(entry1.filePath);
919
- fs_1.default.unlinkSync(entry2.filePath);
920
- this.invalidateSearchIndex(agentId);
921
- console.log(`šŸ”€ Merge complete!`);
922
- console.log(` New ID: ${newId}`);
923
- console.log(` New file: ${filename}`);
924
- console.log(` Merged memory: [${memoryId1}] + [${memoryId2}]`);
925
- console.log(` Summary: ${mergedSummary}`);
926
- }
927
- buildFallbackSummary(entries) {
928
- const byTag = {};
929
- for (const entry of entries) {
930
- const tag = this.getPrimaryTag(entry);
931
- if (!byTag[tag])
932
- byTag[tag] = [];
933
- byTag[tag].push(entry);
934
- }
935
- const toMD = (dateStr) => {
936
- if (!dateStr)
937
- return '-';
938
- const normalized = String(dateStr).includes('T') ? dateStr : `${dateStr}T00:00:00Z`;
939
- const d = new Date(normalized);
940
- if (Number.isNaN(d.getTime()))
941
- return '-';
942
- return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
943
- };
944
- let body = '';
945
- for (const [topic, items] of Object.entries(byTag).sort()) {
946
- const dates = items.map((i) => i.date).filter(Boolean).sort();
947
- const oldest = dates[0];
948
- const newest = dates[dates.length - 1];
949
- const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
950
- body += `## ${topic} (${items.length} items${rangeLabel})\n`;
951
- for (const e of items)
952
- body += `- [${e.date}] ${e.summary}\n`;
953
- for (const e of items)
954
- body += `→ [entries/${e.file}](entries/${e.file})\n`;
955
- body += `\n`;
956
- }
957
- return body;
958
- }
959
- callSummarizer(prompt) {
960
- try {
961
- const result = execCrewx(['q', this.summarizerAgent], {
962
- encoding: 'utf-8',
963
- timeout: this.summarizerTimeout,
964
- stdio: ['pipe', 'pipe', 'pipe'],
965
- input: prompt,
966
- });
967
- const parsed = parseCrewxResponse(result);
968
- this.recordSummarizerSuccess();
969
- return parsed;
970
- }
971
- catch (e) {
972
- const err = e;
973
- const reason = err.code === 'ETIMEDOUT' || err.killed
974
- ? `timed out after ${this.summarizerTimeout}ms`
975
- : err.message;
976
- console.error(`[memory] callSummarizer failed (agent=${this.summarizerAgent}), falling back to raw entry list: ${reason}`);
977
- this.recordSummarizerFailure();
978
- return null;
979
- }
980
- }
981
- generateSummary(agentId, options = {}) {
982
- const allEntries = this.loadAllEntries(agentId);
983
- if (allEntries.length === 0)
984
- return { status: 'empty' };
985
- const cutoff = new Date(Date.now() - this.summaryDays * 24 * 60 * 60 * 1000)
986
- .toISOString()
987
- .slice(0, 10);
988
- const entries = options.force
989
- ? allEntries
990
- : allEntries.filter((e) => e.date >= cutoff);
991
- if (entries.length === 0)
992
- return { status: 'empty', entryCount: allEntries.length };
993
- const summaryPath = this.getSummaryPath(agentId);
994
- const existing = fs_1.default.existsSync(summaryPath)
995
- ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8'))
996
- : null;
997
- const lastEntryId = existing?.data?.last_entry_id ?? null;
998
- const existingBody = existing?.content.trim() ?? null;
999
- const getNewSince = (arr, lastId) => {
1000
- if (!lastId)
1001
- return arr;
1002
- const idx = arr.findIndex((e) => e.id === lastId);
1003
- if (idx === -1)
1004
- return arr;
1005
- return arr.slice(0, idx);
1006
- };
1007
- const newEntries = getNewSince(entries, lastEntryId);
1008
- if (newEntries.length === 0 && !options.force) {
1009
- return { status: 'up-to-date', entryCount: allEntries.length };
1010
- }
1011
- const entriesToProcess = options.force ? entries : newEntries;
1012
- const enriched = [...entriesToProcess].reverse().map((e) => {
1013
- if (e.file) {
1014
- try {
1015
- const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
1016
- const { content } = (0, parser_1.parseFrontmatter)(raw);
1017
- const body = content.replace(/^#[^\n]*\n*/m, '').trim();
1018
- return { ...e, body };
1019
- }
1020
- catch (_e) {
1021
- }
1022
- }
1023
- return e;
1024
- });
1025
- const allTopics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))].sort();
1026
- const buildPrompt = (existBody, newItems, topics) => {
1027
- let p = `You are a memory summarizer for an AI agent.\n\n`;
1028
- if (existBody) {
1029
- p += `## Existing summary:\n${existBody}\n\n## New entries to incorporate:\n`;
1030
- }
1031
- else {
1032
- p += `## All entries:\n`;
1033
- }
1034
- for (const entry of newItems) {
1035
- const importantTag = entry.important ? ' [important]' : '';
1036
- p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
1037
- if (entry.body && entry.body !== '(Add details here)') {
1038
- p += ` — ${entry.body.slice(0, 200)}`;
1039
- }
1040
- p += ` (file: entries/${entry.file})\n`;
1041
- }
1042
- p += `\n## Instructions:\n`;
1043
- p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4 items, 01/28~02/14)"\n`;
1044
- p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
1045
- p += `- After each topic summary, list relevant entry file links as "→ [entries/filename](entries/filename)"\n`;
1046
- p += `- Highlight key decisions, insights, and action items\n`;
1047
- p += `- If there's an existing summary, merge new information into it\n`;
1048
- p += `- Topics to cover: ${topics.join(', ')}\n`;
1049
- p += `- Output ONLY the markdown body (topic sections), no frontmatter or top-level heading\n`;
1050
- return p;
1051
- };
1052
- const prompt = buildPrompt(options.force ? null : existingBody, enriched, allTopics);
1053
- const summaryText = this.callSummarizer(prompt);
1054
- const newestEntryId = entries[0]?.id;
1055
- if (!summaryText) {
1056
- const allEnriched = entries.map((e) => {
1057
- if (e.file) {
1058
- try {
1059
- const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
1060
- const { content } = (0, parser_1.parseFrontmatter)(raw);
1061
- const body = content.replace(/^#[^\n]*\n*/m, '').trim();
1062
- return { ...e, body };
1063
- }
1064
- catch (_e) {
1065
- }
1066
- }
1067
- return e;
1068
- });
1069
- const fallbackBody = this.buildFallbackSummary(allEnriched);
1070
- const fm = (0, parser_1.stringifyFrontmatter)({
1071
- last_entry_id: newestEntryId,
1072
- last_updated: this.getToday(),
1073
- entry_count: allEntries.length,
1074
- summary_days: this.summaryDays,
1075
- summarizer: 'fallback',
1076
- });
1077
- fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${fallbackBody}`, 'utf-8');
1078
- return { status: 'fallback', entryCount: allEntries.length, newEntries: entriesToProcess.length };
1079
- }
1080
- const fm = (0, parser_1.stringifyFrontmatter)({
1081
- last_entry_id: newestEntryId,
1082
- last_updated: this.getToday(),
1083
- entry_count: allEntries.length,
1084
- summary_days: this.summaryDays,
1085
- summarizer: 'memory_summarizer',
1086
- });
1087
- fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${summaryText}\n`, 'utf-8');
1088
- return { status: 'updated', entryCount: allEntries.length, newEntries: entriesToProcess.length };
1089
- }
1090
- summarize(agentId, options = {}) {
1091
- const entries = this.loadAllEntries(agentId);
1092
- if (entries.length === 0) {
1093
- console.log('No memories found.');
1094
- return;
1095
- }
1096
- console.log(`šŸ“ Starting summary generation (${entries.length} items entries)\n`);
1097
- const result = this.generateSummary(agentId, { force: options.force });
1098
- if (result.status === 'up-to-date') {
1099
- console.log('āœ… summary.md is up to date.');
1100
- }
1101
- else if (result.status === 'fallback') {
1102
- console.log(`šŸ“‹ Fallback summary generated (AI call failed)`);
1103
- }
1104
- else if (result.status === 'updated') {
1105
- console.log(`šŸ¤– Summary complete (${result.newEntries} items applied)`);
1106
- }
1107
- console.log(` File: data/${agentId}/summary.md`);
1108
- console.log(` entries: ${result.entryCount} items`);
1109
- }
1110
- isSummarizeLockStale(lockPath) {
1111
- let content;
1112
- try {
1113
- content = fs_1.default.readFileSync(lockPath, 'utf-8');
1114
- }
1115
- catch {
1116
- return true;
1117
- }
1118
- const pidMatch = content.match(/^pid=(\d+)/m);
1119
- const pid = pidMatch ? Number(pidMatch[1]) : NaN;
1120
- if (Number.isFinite(pid)) {
1121
- try {
1122
- process.kill(pid, 0);
1123
- return false;
1124
- }
1125
- catch (e) {
1126
- const err = e;
1127
- if (err.code === 'ESRCH')
1128
- return true;
1129
- }
1130
- }
1131
- try {
1132
- const stat = fs_1.default.statSync(lockPath);
1133
- return Date.now() - stat.mtimeMs > exports.SUMMARIZE_LOCK_STALE_MS;
1134
- }
1135
- catch {
1136
- return true;
1137
- }
1138
- }
1139
- acquireSummarizeLock() {
1140
- const lockPath = this.getSummarizeLockPath();
1141
- const content = `pid=${process.pid}\nmtime=${new Date().toISOString()}\n`;
1142
- try {
1143
- const fd = fs_1.default.openSync(lockPath, 'wx');
1144
- fs_1.default.writeSync(fd, content);
1145
- fs_1.default.closeSync(fd);
1146
- return true;
1147
- }
1148
- catch (e) {
1149
- const err = e;
1150
- if (err.code !== 'EEXIST')
1151
- throw err;
1152
- }
1153
- if (!this.isSummarizeLockStale(lockPath)) {
1154
- return false;
1155
- }
1156
- fs_1.default.writeFileSync(lockPath, content, 'utf-8');
1157
- return true;
1158
- }
1159
- releaseSummarizeLock() {
1160
- try {
1161
- fs_1.default.unlinkSync(this.getSummarizeLockPath());
1162
- }
1163
- catch {
1164
- }
1165
- }
1166
- summarizeDirty() {
1167
- if (!fs_1.default.existsSync(this.dataDir)) {
1168
- console.log(`šŸ’” Register debounce cron:`);
1169
- console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
1170
- return;
1171
- }
1172
- if (!this.acquireSummarizeLock()) {
1173
- console.log(`ā­ļø summarize-dirty is already running (lock held) — skipping this run.`);
1174
- return;
1175
- }
1176
- try {
1177
- const agentDirs = fs_1.default
1178
- .readdirSync(this.dataDir, { withFileTypes: true })
1179
- .filter((entry) => entry.isDirectory())
1180
- .map((entry) => entry.name);
1181
- for (const agentId of agentDirs) {
1182
- const dirtyPath = this.getDirtySummaryPath(agentId);
1183
- if (!fs_1.default.existsSync(dirtyPath))
1184
- continue;
1185
- try {
1186
- const result = this.generateSummary(agentId);
1187
- if (result.status === 'updated') {
1188
- console.log(`šŸ¤– ${agentId} summary.md updated (${result.newEntries} items applied)`);
1189
- }
1190
- else if (result.status === 'fallback') {
1191
- console.log(`šŸ“‹ ${agentId} Fallback summary generated (AI call failed)`);
1192
- }
1193
- else if (result.status === 'up-to-date') {
1194
- console.log(`āœ… ${agentId} summary.md is up to date.`);
1195
- }
1196
- else if (result.status === 'empty') {
1197
- console.log(`ā„¹ļø ${agentId} has no memories to summarize.`);
1198
- }
1199
- fs_1.default.unlinkSync(dirtyPath);
1200
- }
1201
- catch (e) {
1202
- console.error(`āŒ ${agentId} summary.md update failed: ${e.message}`);
1203
- }
1204
- }
1205
- }
1206
- finally {
1207
- this.releaseSummarizeLock();
1208
- }
1209
- console.log(`šŸ’” Register debounce cron:`);
1210
- console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
1211
- }
1212
- search(agentId, query) {
1213
- const entries = this.loadAllEntries(agentId);
1214
- if (entries.length === 0) {
1215
- console.log('No memories found.');
1216
- return;
1217
- }
1218
- const memoryList = entries
1219
- .map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
1220
- .join('\n');
1221
- const task = `Memory list:\n${memoryList}\n\nQuestion: "${query}"`;
1222
- try {
1223
- console.log(`šŸ” "${query}" searching... (${this.searcherAgent})\n`);
1224
- const result = execCrewx(['q', `${this.searcherAgent} ${task}`], { encoding: 'utf-8', timeout: this.searcherTimeout, stdio: ['pipe', 'pipe', 'pipe'] });
1225
- const parsed = parseCrewxResponse(result);
1226
- if (parsed) {
1227
- console.log(`## 🧠 Semantic search results\n`);
1228
- console.log(parsed);
1229
- }
1230
- else {
1231
- console.log(result.trim());
1232
- }
1233
- }
1234
- catch (error) {
1235
- const err = error;
1236
- if (err.code === 'ETIMEDOUT' || err.killed) {
1237
- console.log('Search failed: timed out (max 5 minutes)');
1238
- }
1239
- else {
1240
- console.log('Search failed:', err.message);
1241
- }
1242
- console.log('\nšŸ’” Tip: try keyword search with the find command.');
1243
- }
1244
- }
1245
- }
1246
- exports.MemoryEngine = MemoryEngine;
1247
- function createMemoryEngine(config) {
1248
- return new MemoryEngine(config);
1249
- }
1250
- var types_1 = require("./types");
1251
- Object.defineProperty(exports, "CATEGORIES", { enumerable: true, get: function () { return types_1.CATEGORIES; } });
1252
- //# sourceMappingURL=engine.js.map
97
+ Question: "${t}"`;try{console.log(`\u{1F50D} "${t}" searching... (${this.searcherAgent})
98
+ `);const o=k(["q",`${this.searcherAgent} ${s}`],{encoding:"utf-8",timeout:this.searcherTimeout,stdio:["pipe","pipe","pipe"]});const i=j(o);if(i){console.log(`## \u{1F9E0} Semantic search results
99
+ `);console.log(i)}else{console.log(o.trim())}}catch(o){const i=o;if(i.code==="ETIMEDOUT"||i.killed){console.log("Search failed: timed out (max 5 minutes)")}else{console.log("Search failed:",i.message)}console.log("\n\u{1F4A1} Tip: try keyword search with the find command.")}}}exports.MemoryEngine=P;function W($){return new P($)}var Z=require("./types");Object.defineProperty(exports,"CATEGORIES",{enumerable:true,get:function(){return Z.CATEGORIES}});