@crewx/memory 0.1.23-rc.17 β†’ 0.1.23-rc.171

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,1014 +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 = 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 os_1 = __importDefault(require("os"));
12
- const child_process_1 = require("child_process");
13
- const nanoid_1 = require("nanoid");
14
- const js_yaml_1 = __importDefault(require("js-yaml"));
15
- const parser_1 = require("./parser");
16
- const knowledge_core_1 = require("@crewx/knowledge-core");
17
- const CREWX_CLI = process.env.CREWX_CLI || 'npx crewx';
18
- function parseCrewxResponse(raw) {
19
- const responseMatch = raw.match(/πŸ“„ Response:\s*─+\s*([\s\S]*?)(?=πŸ“ Working Directory|$)/);
20
- if (responseMatch?.[1]) {
21
- const captured = responseMatch[1].trim();
22
- const capturedFirstLine = captured.split('\n')[0]?.trim() ?? '';
23
- if (!capturedFirstLine.startsWith('{')) {
24
- return captured;
25
- }
26
- return parseJsonlContent(captured);
27
- }
28
- return parseJsonlContent(raw);
29
- }
30
- function parseJsonlContent(raw) {
31
- const lines = raw.split('\n').filter((l) => l.trim().startsWith('{'));
32
- let assistantText = '';
33
- let hasAssistantMessage = false;
34
- for (const line of lines) {
35
- try {
36
- const obj = JSON.parse(line);
37
- if (obj.type === 'message' && obj.role === 'assistant') {
38
- hasAssistantMessage = true;
39
- if (obj.content)
40
- assistantText += obj.content;
41
- }
42
- }
43
- catch (_e) {
44
- }
45
- }
46
- if (hasAssistantMessage)
47
- return assistantText.trim() || null;
48
- const trimmed = raw.trim();
49
- if (trimmed.length === 0)
50
- return null;
51
- const firstLine = trimmed.split('\n')[0]?.trim() ?? '';
52
- if (firstLine.startsWith('{'))
53
- return null;
54
- return trimmed;
55
- }
56
- function getProjectRoot() {
57
- try {
58
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
59
- }
60
- catch {
61
- return process.cwd();
62
- }
63
- }
64
- function getMemoryConfig() {
65
- try {
66
- const configPath = process.env.CREWX_CONFIG ?? path_1.default.join(getProjectRoot(), 'crewx.yaml');
67
- const raw = fs_1.default.readFileSync(configPath, 'utf8');
68
- const config = js_yaml_1.default.load(raw);
69
- const settings = config?.settings;
70
- return settings?.memory ?? {};
71
- }
72
- catch {
73
- return {};
74
- }
75
- }
76
- class MemoryEngine {
77
- constructor(config = {}) {
78
- const yamlConfig = getMemoryConfig();
79
- this.dataDir =
80
- config.dataDir ??
81
- process.env['MEMORY_DATA_DIR'] ??
82
- path_1.default.resolve(process.cwd(), 'memory');
83
- this.summaryDays =
84
- config.summaryDays ??
85
- (yamlConfig.summary_days != null ? Number(yamlConfig.summary_days) : undefined) ??
86
- (parseInt(process.env['MEMORY_SUMMARY_DAYS'] ?? '', 10) || 7);
87
- this.recentDays =
88
- config.recentDays ??
89
- (yamlConfig.recent_days != null ? Number(yamlConfig.recent_days) : undefined) ??
90
- (parseInt(process.env['MEMORY_RECENT_DAYS'] ?? '', 10) || 30);
91
- this.shortTermHours =
92
- config.shortTermHours ??
93
- (yamlConfig.short_term_hours != null ? Number(yamlConfig.short_term_hours) : undefined) ??
94
- 24;
95
- this.summarizerAgent =
96
- config.summarizerAgent ??
97
- yamlConfig.summarizer_agent ??
98
- '@memory_summarizer';
99
- this.searcherAgent =
100
- config.searcherAgent ??
101
- yamlConfig.searcher_agent ??
102
- '@memory_searcher';
103
- this.summarizerTimeout =
104
- config.summarizerTimeout ??
105
- (yamlConfig.summarizer_timeout != null ? Number(yamlConfig.summarizer_timeout) : undefined) ??
106
- 60000;
107
- this.searcherTimeout =
108
- config.searcherTimeout ??
109
- (yamlConfig.searcher_timeout != null ? Number(yamlConfig.searcher_timeout) : undefined) ??
110
- 300000;
111
- }
112
- getAgentDir(agentId) {
113
- return path_1.default.join(this.dataDir, agentId);
114
- }
115
- getEntriesDir(agentId) {
116
- return path_1.default.join(this.getAgentDir(agentId), 'entries');
117
- }
118
- getSummaryPath(agentId) {
119
- return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
120
- }
121
- getDirtySummaryPath(agentId) {
122
- return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
123
- }
124
- ensureDir(dir) {
125
- if (!fs_1.default.existsSync(dir)) {
126
- fs_1.default.mkdirSync(dir, { recursive: true });
127
- }
128
- }
129
- generateId() {
130
- return (0, nanoid_1.nanoid)(6);
131
- }
132
- getToday() {
133
- return new Date().toISOString().slice(0, 10);
134
- }
135
- getLocalISO8601() {
136
- const now = new Date();
137
- const offset = -now.getTimezoneOffset();
138
- const sign = offset >= 0 ? '+' : '-';
139
- const offsetHours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
140
- const offsetMins = String(Math.abs(offset) % 60).padStart(2, '0');
141
- const y = now.getFullYear();
142
- const m = String(now.getMonth() + 1).padStart(2, '0');
143
- const d = String(now.getDate()).padStart(2, '0');
144
- const h = String(now.getHours()).padStart(2, '0');
145
- const min = String(now.getMinutes()).padStart(2, '0');
146
- const s = String(now.getSeconds()).padStart(2, '0');
147
- return `${y}-${m}-${d}T${h}:${min}:${s}${sign}${offsetHours}:${offsetMins}`;
148
- }
149
- getPrimaryTag(entry) {
150
- return (entry.tags?.[0]) ?? 'general';
151
- }
152
- toEntryTimestamp(entry) {
153
- if (entry['created_at'])
154
- return String(entry['created_at']);
155
- const d = entry['date'];
156
- const dateStr = d && typeof d === 'object' && typeof d.toISOString === 'function' ? d.toISOString().slice(0, 10) : String(d ?? '');
157
- if (dateStr.includes('T'))
158
- return dateStr;
159
- return dateStr + 'T09:00:00+09:00';
160
- }
161
- markSummaryDirty(agentId) {
162
- this.ensureDir(this.getAgentDir(agentId));
163
- fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
164
- }
165
- loadAllEntries(agentId) {
166
- const entriesDir = this.getEntriesDir(agentId);
167
- if (!fs_1.default.existsSync(entriesDir))
168
- return [];
169
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
170
- const entries = [];
171
- for (const file of files) {
172
- const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
173
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
174
- const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
175
- const hasBody = Boolean(bodyText) && bodyText !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
176
- const entry = data;
177
- entries.push({ ...entry, file, hasBody });
178
- }
179
- entries.sort((a, b) => this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a)));
180
- return entries;
181
- }
182
- findEntryById(agentId, memoryId) {
183
- const entriesDir = this.getEntriesDir(agentId);
184
- if (!fs_1.default.existsSync(entriesDir))
185
- return null;
186
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
187
- for (const file of files) {
188
- const filePath = path_1.default.join(entriesDir, file);
189
- const raw = fs_1.default.readFileSync(filePath, 'utf-8');
190
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
191
- if (data.id === memoryId) {
192
- return {
193
- file,
194
- filePath,
195
- data: data,
196
- content,
197
- };
198
- }
199
- }
200
- return null;
201
- }
202
- save(agentId, summary, category = 'general', options = {}) {
203
- this.ensureDir(this.getEntriesDir(agentId));
204
- const id = this.generateId();
205
- const dateTime = this.getLocalISO8601();
206
- const tags = Array.isArray(options.tags)
207
- ? options.tags
208
- : options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
209
- const frontmatter = (0, parser_1.stringifyFrontmatter)({
210
- id,
211
- type: 'memory',
212
- date: dateTime,
213
- category,
214
- tags,
215
- summary,
216
- ...(options.important ? { important: true } : {}),
217
- });
218
- const filename = `${id}.md`;
219
- const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
220
- const body = options.body ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
221
- const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
222
- fs_1.default.writeFileSync(filepath, content, 'utf-8');
223
- console.log(`βœ… μ €μž₯ μ™„λ£Œ: data/${agentId}/entries/${filename}`);
224
- console.log(` ID: ${id}`);
225
- console.log(` Tags: ${tags.join(', ') || '(μ—†μŒ)'}`);
226
- console.log(` Category: ${category}`);
227
- try {
228
- this.markSummaryDirty(agentId);
229
- console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
230
- }
231
- catch (_e) {
232
- }
233
- const saveCtx = {
234
- commandId: 'memory.save',
235
- params: { agentId, tags: tags.join(',') },
236
- resultTags: tags.slice(0, 3),
237
- resultCount: 1,
238
- };
239
- const saveActions = (0, knowledge_core_1.generateNextActions)(saveCtx);
240
- const saveBlock = (0, knowledge_core_1.formatActions)(saveActions);
241
- if (saveBlock)
242
- console.log(saveBlock);
243
- }
244
- index(agentId, options = {}) {
245
- const summaryPath = this.getSummaryPath(agentId);
246
- const entries = this.loadAllEntries(agentId);
247
- const topics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))];
248
- if (options.recent) {
249
- const days = parseInt(String(options.recent), 10) || this.recentDays;
250
- this.recent(agentId, days);
251
- return;
252
- }
253
- const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
254
- const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
255
- console.log(`# ${agentId} κΈ°μ–΅ μš”μ•½\n`);
256
- const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
257
- ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
258
- : '-';
259
- console.log(`> ${entries.length}건 | ν† ν”½ ${topics.length}개 | κ°±μ‹ : ${summaryLastUpdated}`);
260
- if (options.full) {
261
- const byCat = {};
262
- for (const entry of entries) {
263
- const cat = entry.category ?? 'general';
264
- byCat[cat] = (byCat[cat] ?? 0) + 1;
265
- }
266
- const catSummary = Object.entries(byCat)
267
- .sort((a, b) => b[1] - a[1])
268
- .map(([cat, count]) => `${cat}:${count}`)
269
- .join(' ');
270
- console.log(`> πŸ“Š ${catSummary}`);
271
- }
272
- console.log('');
273
- if (shortTermEntries.length > 0) {
274
- const sortedShortTerm = [...shortTermEntries].sort((a, b) => {
275
- const aImp = a.important ? 1 : 0;
276
- const bImp = b.important ? 1 : 0;
277
- if (aImp !== bImp)
278
- return bImp - aImp;
279
- return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
280
- });
281
- console.log(`## πŸ”₯ 졜근 μž‘μ—… (24h)\n`);
282
- for (const e of sortedShortTerm) {
283
- const star = e.important ? '⭐ ' : '';
284
- console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
285
- }
286
- console.log('');
287
- }
288
- if (options.full) {
289
- if (fs_1.default.existsSync(summaryPath)) {
290
- const raw = fs_1.default.readFileSync(summaryPath, 'utf-8');
291
- const { content: summaryBody } = (0, parser_1.parseFrontmatter)(raw);
292
- if (entries.length > 0) {
293
- const newestDate = entries[0]?.date;
294
- const oldestDate = entries[entries.length - 1]?.date;
295
- const newest = newestDate ? new Date(`${newestDate}T00:00:00Z`) : null;
296
- const oldest = oldestDate ? new Date(`${oldestDate}T00:00:00Z`) : null;
297
- if (newest &&
298
- oldest &&
299
- !Number.isNaN(newest.getTime()) &&
300
- !Number.isNaN(oldest.getTime())) {
301
- const msPerDay = 24 * 60 * 60 * 1000;
302
- const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
303
- const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
304
- console.log('---');
305
- console.log(`πŸ“‹ ν† ν”½ μš”μ•½ (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays}일간)\n`);
306
- }
307
- }
308
- console.log(summaryBody.trim());
309
- }
310
- else {
311
- for (const tag of topics.sort()) {
312
- const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
313
- console.log(`- **${tag}** (${count}건)`);
314
- }
315
- }
316
- }
317
- const commandId = options.full ? 'memory.index-full' : 'memory.index';
318
- const ctx = {
319
- commandId,
320
- params: { agentId },
321
- resultTags: topics.slice(0, 3),
322
- resultIds: entries.slice(0, 3).map(e => e.id),
323
- resultCount: entries.length,
324
- };
325
- const actions = (0, knowledge_core_1.generateNextActions)(ctx);
326
- const block = (0, knowledge_core_1.formatActions)(actions);
327
- if (block)
328
- console.log(block);
329
- }
330
- generateIndexContent(agentId) {
331
- const entries = this.loadAllEntries(agentId);
332
- const today = this.getToday();
333
- const cutoff = new Date(Date.now() - this.recentDays * 24 * 60 * 60 * 1000)
334
- .toISOString()
335
- .slice(0, 10);
336
- const byCategory = {};
337
- const recent = [];
338
- for (const entry of entries) {
339
- if (entry.date >= cutoff)
340
- recent.push(entry);
341
- const category = entry.category ?? 'general';
342
- if (!byCategory[category])
343
- byCategory[category] = [];
344
- byCategory[category].push(entry);
345
- }
346
- let md = `# ${agentId} λ©”λͺ¨λ¦¬\n\n`;
347
- md += `> Last Updated: ${today}\n`;
348
- md += `> Total: ${entries.length} memories\n\n`;
349
- md += `## πŸ”₯ 졜근 ${this.recentDays}일\n\n`;
350
- if (recent.length === 0) {
351
- md += `(μ—†μŒ)\n\n`;
352
- }
353
- else {
354
- md += `| ID | λ‚ μ§œ | μΉ΄ν…Œκ³ λ¦¬ | μš”μ•½ | 상세 |\n`;
355
- md += `|----|------|----------|------|------|\n`;
356
- for (const entry of recent) {
357
- const detailIcon = entry.hasBody ? 'πŸ“„' : '-';
358
- md += `| ${entry.id} | ${entry.date} | ${entry.category ?? '-'} | [${entry.summary}](entries/${entry.file}) | ${detailIcon} |\n`;
359
- }
360
- md += `\n`;
361
- }
362
- md += `## 🏷️ μΉ΄ν…Œκ³ λ¦¬λ³„\n\n`;
363
- for (const [category, items] of Object.entries(byCategory).sort()) {
364
- md += `- **${category}**: ${items.length}건\n`;
365
- }
366
- const olderCount = entries.length - recent.length;
367
- const oldestDate = entries[entries.length - 1]?.date ?? '-';
368
- const newestDate = entries[0]?.date ?? '-';
369
- md += `\n## πŸ“Š 톡계\n\n`;
370
- md += `- 전체: ${entries.length}건 (${oldestDate} ~ ${newestDate})\n`;
371
- md += `- 졜근 ${this.recentDays}일: ${recent.length}건\n`;
372
- if (olderCount > 0)
373
- md += `- ${this.recentDays}일 이전: ${olderCount}건\n`;
374
- return md;
375
- }
376
- topic(agentId, topicName) {
377
- const entries = this.loadAllEntries(agentId);
378
- const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
379
- if (filtered.length === 0) {
380
- console.log(`ν† ν”½ '${topicName}'에 ν•΄λ‹Ήν•˜λŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
381
- return;
382
- }
383
- console.log(`## πŸ“‚ ν† ν”½: ${topicName} (${filtered.length}건)\n`);
384
- for (const entry of filtered) {
385
- console.log(`### [${entry.date}] ${entry.summary}`);
386
- console.log(`- 파일: data/${agentId}/entries/${entry.file}`);
387
- console.log(`- μΉ΄ν…Œκ³ λ¦¬: ${entry.category ?? '-'}`);
388
- console.log(`- νƒœκ·Έ: ${(entry.tags ?? []).join(', ') || '-'}`);
389
- console.log('');
390
- }
391
- }
392
- recent(agentId, days = 30) {
393
- const entries = this.loadAllEntries(agentId);
394
- const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
395
- .toISOString()
396
- .slice(0, 10);
397
- const filtered = entries.filter((e) => e.date >= cutoff);
398
- if (filtered.length === 0) {
399
- console.log(`졜근 ${days}일 λ‚΄ 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
400
- return;
401
- }
402
- console.log(`## πŸ”₯ 졜근 ${days}일 (${filtered.length}건)\n`);
403
- for (const entry of filtered) {
404
- const detailIcon = entry.hasBody ? ' πŸ“„' : '';
405
- const timestamp = String(entry.date).includes('T')
406
- ? String(entry.date).slice(0, 16).replace('T', ' ')
407
- : entry.date;
408
- console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
409
- }
410
- }
411
- find(agentId, keyword) {
412
- const entriesDir = this.getEntriesDir(agentId);
413
- if (!fs_1.default.existsSync(entriesDir)) {
414
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
415
- return;
416
- }
417
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
418
- const entries = [];
419
- for (const file of files) {
420
- const content = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
421
- const { data, content: body } = (0, parser_1.parseFrontmatter)(content);
422
- const entry = data;
423
- entries.push({ ...entry, file, body });
424
- }
425
- if (entries.length === 0) {
426
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
427
- return;
428
- }
429
- const kcEntries = entries;
430
- const results = (0, knowledge_core_1.searchEntries)(kcEntries, keyword, { summary: 3, tags: 2, body: 1 });
431
- if (results.length === 0) {
432
- console.log(`'${keyword}' κ΄€λ ¨ 기얡을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
433
- return;
434
- }
435
- console.log(`## πŸ” 검색 κ²°κ³Ό: "${keyword}" (${results.length}건, BM25 점수순)\n`);
436
- for (const entry of results) {
437
- const score = entry._score != null ? ` [${entry._score}점]` : '';
438
- console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
439
- }
440
- const resultTags = [...new Set(results.flatMap(e => e.tags ?? []))].slice(0, 3);
441
- const resultIds = results.slice(0, 3).map(e => e.id);
442
- const findCtx = {
443
- commandId: 'memory.find',
444
- params: { agentId, query: keyword },
445
- resultTags,
446
- resultIds,
447
- resultCount: results.length,
448
- };
449
- const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
450
- const findBlock = (0, knowledge_core_1.formatActions)(findActions);
451
- if (findBlock)
452
- console.log(findBlock);
453
- }
454
- list(agentId, options = {}) {
455
- const entries = this.loadAllEntries(agentId);
456
- let filtered = entries;
457
- if (options.category) {
458
- filtered = filtered.filter(e => e.category === options.category);
459
- }
460
- if (options.tag) {
461
- const requiredTags = options.tag.split(',').map(t => t.trim());
462
- filtered = filtered.filter(e => requiredTags.every(t => (e.tags ?? []).includes(t)));
463
- }
464
- if (options.json) {
465
- const entriesDir = this.getEntriesDir(agentId);
466
- const result = filtered.map(e => {
467
- let content = '';
468
- if (e.file) {
469
- try {
470
- const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, e.file), 'utf-8');
471
- const { content: rawContent } = (0, parser_1.parseFrontmatter)(raw);
472
- const body = rawContent.replace(/^#[^\n]*\n*/m, '').trim();
473
- if (body !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)')
474
- content = body;
475
- }
476
- catch (_err) {
477
- }
478
- }
479
- return {
480
- id: e.id,
481
- title: e.summary ?? '',
482
- category: e.category ?? 'general',
483
- tags: e.tags ?? [],
484
- created_at: e.date ?? '',
485
- content,
486
- };
487
- });
488
- console.log(JSON.stringify(result, null, 2));
489
- return;
490
- }
491
- if (filtered.length === 0) {
492
- const filters = [];
493
- if (options.category)
494
- filters.push(`category=${options.category}`);
495
- if (options.tag)
496
- filters.push(`tag=${options.tag}`);
497
- console.log(`쑰건에 λ§žλŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€. (${filters.join(', ') || '전체'})`);
498
- return;
499
- }
500
- console.log(`## πŸ“‹ λͺ©λ‘: ${agentId} (${filtered.length}건)\n`);
501
- for (const entry of filtered) {
502
- console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
503
- }
504
- const resultTags = [...new Set(filtered.flatMap(e => e.tags ?? []))].slice(0, 3);
505
- const resultIds = filtered.slice(0, 3).map(e => e.id);
506
- const listCtx = {
507
- commandId: 'memory.list',
508
- params: { agentId, ...(options.category ? { category: options.category } : {}) },
509
- resultTags,
510
- resultIds,
511
- resultCount: filtered.length,
512
- };
513
- const listActions = (0, knowledge_core_1.generateNextActions)(listCtx);
514
- const listBlock = (0, knowledge_core_1.formatActions)(listActions);
515
- if (listBlock)
516
- console.log(listBlock);
517
- }
518
- stats(agentId) {
519
- const entries = this.loadAllEntries(agentId);
520
- if (entries.length === 0) {
521
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
522
- return;
523
- }
524
- const byCategory = {};
525
- for (const entry of entries) {
526
- const cat = entry.category ?? 'general';
527
- byCategory[cat] = (byCategory[cat] ?? 0) + 1;
528
- }
529
- const byTag = {};
530
- for (const entry of entries) {
531
- for (const tag of entry.tags ?? []) {
532
- byTag[tag] = (byTag[tag] ?? 0) + 1;
533
- }
534
- }
535
- console.log(`# ${agentId} 톡계 (${entries.length}건)\n`);
536
- console.log(`## μΉ΄ν…Œκ³ λ¦¬λ³„`);
537
- for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
538
- console.log(` ${cat}: ${count}건`);
539
- }
540
- console.log(`\n## νƒœκ·Έ Top 20`);
541
- const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
542
- for (const [tag, count] of sortedTags) {
543
- console.log(` ${tag}: ${count}건`);
544
- }
545
- const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
546
- const statsCtx = {
547
- commandId: 'memory.stats',
548
- params: { agentId },
549
- resultTags: topCategories.slice(0, 3),
550
- resultCount: entries.length,
551
- };
552
- const statsActions = (0, knowledge_core_1.generateNextActions)(statsCtx);
553
- const statsBlock = (0, knowledge_core_1.formatActions)(statsActions);
554
- if (statsBlock)
555
- console.log(statsBlock);
556
- }
557
- tags(agentId) {
558
- const entries = this.loadAllEntries(agentId);
559
- if (entries.length === 0) {
560
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
561
- return;
562
- }
563
- const byTag = {};
564
- for (const entry of entries) {
565
- for (const tag of entry.tags ?? []) {
566
- byTag[tag] = (byTag[tag] ?? 0) + 1;
567
- }
568
- }
569
- const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
570
- if (sortedTags.length === 0) {
571
- console.log('νƒœκ·Έκ°€ μ—†μŠ΅λ‹ˆλ‹€.');
572
- return;
573
- }
574
- console.log(`## 🏷️ 전체 νƒœκ·Έ (${sortedTags.length}개)\n`);
575
- for (const [tag, count] of sortedTags) {
576
- console.log(` ${tag}: ${count}건`);
577
- }
578
- }
579
- get(agentId, memoryId) {
580
- const entriesDir = this.getEntriesDir(agentId);
581
- if (!fs_1.default.existsSync(entriesDir)) {
582
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
583
- return;
584
- }
585
- const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
586
- for (const file of files) {
587
- const filePath = path_1.default.join(entriesDir, file);
588
- const raw = fs_1.default.readFileSync(filePath, 'utf-8');
589
- const { data, content } = (0, parser_1.parseFrontmatter)(raw);
590
- if (data.id === memoryId) {
591
- const d = data;
592
- console.log(`## πŸ“„ ${d.summary}\n`);
593
- console.log(`- ID: ${d.id}`);
594
- console.log(`- λ‚ μ§œ: ${d.date}`);
595
- console.log(`- μΉ΄ν…Œκ³ λ¦¬: ${d.category ?? '-'}`);
596
- console.log(`- νƒœκ·Έ: ${(d.tags ?? []).join(', ') || '-'}`);
597
- console.log(`- 파일: data/${agentId}/entries/${file}`);
598
- console.log(`\n---\n`);
599
- console.log(content.trim());
600
- const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
601
- if (fs_1.default.existsSync(graphPath)) {
602
- try {
603
- const graph = JSON.parse(fs_1.default.readFileSync(graphPath, 'utf-8'));
604
- const relatedEdges = graph.edges
605
- .filter((e) => e.from === memoryId || e.to === memoryId)
606
- .sort((a, b) => b.weight - a.weight)
607
- .slice(0, 5);
608
- if (relatedEdges.length > 0) {
609
- const nodeMap = {};
610
- graph.nodes.forEach((n) => {
611
- nodeMap[n.id] = n;
612
- });
613
- console.log(`\n---\n`);
614
- console.log(`## πŸ”— μ—°κ΄€ κΈ°μ–΅ (${relatedEdges.length}개)\n`);
615
- for (const edge of relatedEdges) {
616
- const otherId = edge.from === memoryId ? edge.to : edge.from;
617
- const other = nodeMap[otherId];
618
- if (other) {
619
- console.log(`[${edge.weight}] [${otherId}] ${other.summary.slice(0, 50)}...`);
620
- }
621
- }
622
- }
623
- }
624
- catch (_e) {
625
- }
626
- }
627
- const getCtx = {
628
- commandId: 'memory.get',
629
- params: { agentId, id: memoryId },
630
- resultTags: (d.tags ?? []).slice(0, 3),
631
- resultIds: [memoryId],
632
- resultCount: 1,
633
- };
634
- const getActions = (0, knowledge_core_1.generateNextActions)(getCtx);
635
- const getBlock = (0, knowledge_core_1.formatActions)(getActions);
636
- if (getBlock)
637
- console.log(getBlock);
638
- return;
639
- }
640
- }
641
- console.log(`ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
642
- }
643
- update(agentId, memoryId, options = {}) {
644
- const entry = this.findEntryById(agentId, memoryId);
645
- if (!entry) {
646
- console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
647
- process.exit(1);
648
- }
649
- const { filePath, data, content } = entry;
650
- const existingBody = content.replace(/^#[^\n]*\n*/m, '').trim();
651
- const tags = Array.isArray(options.tags)
652
- ? options.tags
653
- : options.tags ? options.tags.split(',').map((t) => t.trim()) : (data.tags ?? []);
654
- const newData = {
655
- ...data,
656
- summary: options.summary ?? data.summary,
657
- category: options.category ?? data.category,
658
- tags,
659
- updated: this.getToday(),
660
- };
661
- if (options.important) {
662
- newData['important'] = true;
663
- }
664
- else if (options['no-important']) {
665
- delete newData['important'];
666
- }
667
- const newBody = options.body ?? existingBody ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
668
- const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
669
- const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
670
- fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
671
- console.log(`βœ… μˆ˜μ • μ™„λ£Œ: ${entry.file}`);
672
- console.log(` ID: ${memoryId}`);
673
- if (options.summary)
674
- console.log(` Summary: ${String(newData['summary'])}`);
675
- if (options.category)
676
- console.log(` Category: ${String(newData['category'])}`);
677
- if (options.tags)
678
- console.log(` Tags: ${newData['tags'].join(', ')}`);
679
- if (options.body)
680
- console.log(` Body: (μ—…λ°μ΄νŠΈλ¨)`);
681
- }
682
- delete(agentId, memoryId, options = {}) {
683
- const entry = this.findEntryById(agentId, memoryId);
684
- if (!entry) {
685
- console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
686
- process.exit(1);
687
- }
688
- if (!options.force) {
689
- console.log(`⚠️ μ‚­μ œ λŒ€μƒ: [${entry.data.id}] ${entry.data.summary}`);
690
- console.log(` 파일: ${entry.file}`);
691
- console.log(`\n--force μ˜΅μ…˜μ„ μΆ”κ°€ν•˜λ©΄ μ‚­μ œλ©λ‹ˆλ‹€.`);
692
- console.log(`예: memory delete ${agentId} ${memoryId} --force`);
693
- return;
694
- }
695
- fs_1.default.unlinkSync(entry.filePath);
696
- console.log(`πŸ—‘οΈ μ‚­μ œ μ™„λ£Œ: ${entry.file}`);
697
- console.log(` ID: ${memoryId}`);
698
- console.log(` Summary: ${entry.data.summary}`);
699
- try {
700
- this.markSummaryDirty(agentId);
701
- console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
702
- }
703
- catch (_e) {
704
- }
705
- }
706
- merge(agentId, memoryId1, memoryId2, options = {}) {
707
- const entry1 = this.findEntryById(agentId, memoryId1);
708
- const entry2 = this.findEntryById(agentId, memoryId2);
709
- if (!entry1) {
710
- console.log(`❌ ID '${memoryId1}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
711
- process.exit(1);
712
- }
713
- if (!entry2) {
714
- console.log(`❌ ID '${memoryId2}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
715
- process.exit(1);
716
- }
717
- const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
718
- const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
719
- const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
720
- const mergedBody = `## λ³‘ν•©λœ κΈ°μ–΅ 1 (${entry1.data.id}, ${entry1.data.date})
721
- ${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}
722
2
 
723
- ${body1 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body1 : '(상세 μ—†μŒ)'}
3
+ # ${t}
724
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(`
725
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}
726
89
 
727
- ## λ³‘ν•©λœ κΈ°μ–΅ 2 (${entry2.data.id}, ${entry2.data.date})
728
- ${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}
729
96
 
730
- ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
731
- const mergedTags = [
732
- ...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
733
- ];
734
- const newerDate = entry1.data.date > entry2.data.date ? entry1.data.date : entry2.data.date;
735
- const newId = this.generateId();
736
- const newData = {
737
- id: newId,
738
- type: 'memory',
739
- date: newerDate,
740
- category: entry1.data.category,
741
- tags: mergedTags,
742
- summary: mergedSummary,
743
- merged_from: [memoryId1, memoryId2],
744
- };
745
- const filename = `${newId}.md`;
746
- const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
747
- const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
748
- const newContent = `${newFrontmatter}\n\n# ${mergedSummary}\n\n${mergedBody}\n`;
749
- fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
750
- fs_1.default.unlinkSync(entry1.filePath);
751
- fs_1.default.unlinkSync(entry2.filePath);
752
- console.log(`πŸ”€ 병합 μ™„λ£Œ!`);
753
- console.log(` μƒˆ ID: ${newId}`);
754
- console.log(` μƒˆ 파일: ${filename}`);
755
- console.log(` λ³‘ν•©λœ κΈ°μ–΅: [${memoryId1}] + [${memoryId2}]`);
756
- console.log(` Summary: ${mergedSummary}`);
757
- }
758
- buildFallbackSummary(entries) {
759
- const byTag = {};
760
- for (const entry of entries) {
761
- const tag = this.getPrimaryTag(entry);
762
- if (!byTag[tag])
763
- byTag[tag] = [];
764
- byTag[tag].push(entry);
765
- }
766
- const toMD = (dateStr) => {
767
- if (!dateStr)
768
- return '-';
769
- const normalized = String(dateStr).includes('T') ? dateStr : `${dateStr}T00:00:00Z`;
770
- const d = new Date(normalized);
771
- if (Number.isNaN(d.getTime()))
772
- return '-';
773
- return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
774
- };
775
- let body = '';
776
- for (const [topic, items] of Object.entries(byTag).sort()) {
777
- const dates = items.map((i) => i.date).filter(Boolean).sort();
778
- const oldest = dates[0];
779
- const newest = dates[dates.length - 1];
780
- const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
781
- body += `## ${topic} (${items.length}건${rangeLabel})\n`;
782
- for (const e of items)
783
- body += `- [${e.date}] ${e.summary}\n`;
784
- for (const e of items)
785
- body += `β†’ [entries/${e.file}](entries/${e.file})\n`;
786
- body += `\n`;
787
- }
788
- return body;
789
- }
790
- callSummarizer(prompt) {
791
- const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
792
- try {
793
- fs_1.default.writeFileSync(tmpFile, prompt, 'utf-8');
794
- const result = (0, child_process_1.execSync)(`${CREWX_CLI} q "${this.summarizerAgent} $(cat '${tmpFile}')"`, { encoding: 'utf-8', timeout: this.summarizerTimeout, stdio: ['pipe', 'pipe', 'pipe'] });
795
- return parseCrewxResponse(result);
796
- }
797
- catch (_e) {
798
- return null;
799
- }
800
- finally {
801
- try {
802
- fs_1.default.unlinkSync(tmpFile);
803
- }
804
- catch (_e) {
805
- }
806
- }
807
- }
808
- generateSummary(agentId, options = {}) {
809
- const allEntries = this.loadAllEntries(agentId);
810
- if (allEntries.length === 0)
811
- return { status: 'empty' };
812
- const cutoff = new Date(Date.now() - this.summaryDays * 24 * 60 * 60 * 1000)
813
- .toISOString()
814
- .slice(0, 10);
815
- const entries = options.force
816
- ? allEntries
817
- : allEntries.filter((e) => e.date >= cutoff);
818
- if (entries.length === 0)
819
- return { status: 'empty', entryCount: allEntries.length };
820
- const summaryPath = this.getSummaryPath(agentId);
821
- const existing = fs_1.default.existsSync(summaryPath)
822
- ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8'))
823
- : null;
824
- const lastEntryId = existing?.data?.last_entry_id ?? null;
825
- const existingBody = existing?.content.trim() ?? null;
826
- const getNewSince = (arr, lastId) => {
827
- if (!lastId)
828
- return arr;
829
- const idx = arr.findIndex((e) => e.id === lastId);
830
- if (idx === -1)
831
- return arr;
832
- return arr.slice(0, idx);
833
- };
834
- const newEntries = getNewSince(entries, lastEntryId);
835
- if (newEntries.length === 0 && !options.force) {
836
- return { status: 'up-to-date', entryCount: allEntries.length };
837
- }
838
- const entriesToProcess = options.force ? entries : newEntries;
839
- const enriched = [...entriesToProcess].reverse().map((e) => {
840
- if (e.file) {
841
- try {
842
- const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
843
- const { content } = (0, parser_1.parseFrontmatter)(raw);
844
- const body = content.replace(/^#[^\n]*\n*/m, '').trim();
845
- return { ...e, body };
846
- }
847
- catch (_e) {
848
- }
849
- }
850
- return e;
851
- });
852
- const allTopics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))].sort();
853
- const buildPrompt = (existBody, newItems, topics) => {
854
- let p = `You are a memory summarizer for an AI agent.\n\n`;
855
- if (existBody) {
856
- p += `## Existing summary:\n${existBody}\n\n## New entries to incorporate:\n`;
857
- }
858
- else {
859
- p += `## All entries:\n`;
860
- }
861
- for (const entry of newItems) {
862
- const importantTag = entry.important ? ' [important]' : '';
863
- p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
864
- if (entry.body && entry.body !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)') {
865
- p += ` β€” ${entry.body.slice(0, 200)}`;
866
- }
867
- p += ` (file: entries/${entry.file})\n`;
868
- }
869
- p += `\n## Instructions:\n`;
870
- p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4건, 01/28~02/14)"\n`;
871
- p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
872
- p += `- After each topic summary, list relevant entry file links as "β†’ [entries/filename](entries/filename)"\n`;
873
- p += `- Highlight key decisions, insights, and action items\n`;
874
- p += `- If there's an existing summary, merge new information into it\n`;
875
- p += `- Topics to cover: ${topics.join(', ')}\n`;
876
- p += `- Output ONLY the markdown body (topic sections), no frontmatter or top-level heading\n`;
877
- return p;
878
- };
879
- const prompt = buildPrompt(options.force ? null : existingBody, enriched, allTopics);
880
- const summaryText = this.callSummarizer(prompt);
881
- const newestEntryId = entries[0]?.id;
882
- if (!summaryText) {
883
- const allEnriched = entries.map((e) => {
884
- if (e.file) {
885
- try {
886
- const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
887
- const { content } = (0, parser_1.parseFrontmatter)(raw);
888
- const body = content.replace(/^#[^\n]*\n*/m, '').trim();
889
- return { ...e, body };
890
- }
891
- catch (_e) {
892
- }
893
- }
894
- return e;
895
- });
896
- const fallbackBody = this.buildFallbackSummary(allEnriched);
897
- const fm = (0, parser_1.stringifyFrontmatter)({
898
- last_entry_id: newestEntryId,
899
- last_updated: this.getToday(),
900
- entry_count: allEntries.length,
901
- summary_days: this.summaryDays,
902
- summarizer: 'fallback',
903
- });
904
- fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${fallbackBody}`, 'utf-8');
905
- return { status: 'fallback', entryCount: allEntries.length, newEntries: entriesToProcess.length };
906
- }
907
- const fm = (0, parser_1.stringifyFrontmatter)({
908
- last_entry_id: newestEntryId,
909
- last_updated: this.getToday(),
910
- entry_count: allEntries.length,
911
- summary_days: this.summaryDays,
912
- summarizer: 'memory_summarizer',
913
- });
914
- fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${summaryText}\n`, 'utf-8');
915
- return { status: 'updated', entryCount: allEntries.length, newEntries: entriesToProcess.length };
916
- }
917
- summarize(agentId, options = {}) {
918
- const entries = this.loadAllEntries(agentId);
919
- if (entries.length === 0) {
920
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
921
- return;
922
- }
923
- console.log(`πŸ“ μš”μ•½ 생성 μ‹œμž‘ (${entries.length}개 μ—”νŠΈλ¦¬)\n`);
924
- const result = this.generateSummary(agentId, { force: options.force });
925
- if (result.status === 'up-to-date') {
926
- console.log('βœ… summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.');
927
- }
928
- else if (result.status === 'fallback') {
929
- console.log(`πŸ“‹ 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
930
- }
931
- else if (result.status === 'updated') {
932
- console.log(`πŸ€– μš”μ•½ μ™„λ£Œ (${result.newEntries}건 반영)`);
933
- }
934
- console.log(` 파일: data/${agentId}/summary.md`);
935
- console.log(` μ—”νŠΈλ¦¬: ${result.entryCount}개`);
936
- }
937
- summarizeDirty() {
938
- if (!fs_1.default.existsSync(this.dataDir)) {
939
- console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
940
- console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
941
- return;
942
- }
943
- const agentDirs = fs_1.default
944
- .readdirSync(this.dataDir, { withFileTypes: true })
945
- .filter((entry) => entry.isDirectory())
946
- .map((entry) => entry.name);
947
- for (const agentId of agentDirs) {
948
- const dirtyPath = this.getDirtySummaryPath(agentId);
949
- if (!fs_1.default.existsSync(dirtyPath))
950
- continue;
951
- try {
952
- const result = this.generateSummary(agentId);
953
- if (result.status === 'updated') {
954
- console.log(`πŸ€– ${agentId} summary.md κ°±μ‹  (${result.newEntries}건 반영)`);
955
- }
956
- else if (result.status === 'fallback') {
957
- console.log(`πŸ“‹ ${agentId} 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
958
- }
959
- else if (result.status === 'up-to-date') {
960
- console.log(`βœ… ${agentId} summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.`);
961
- }
962
- else if (result.status === 'empty') {
963
- console.log(`ℹ️ ${agentId} μš”μ•½ν•  기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
964
- }
965
- fs_1.default.unlinkSync(dirtyPath);
966
- }
967
- catch (e) {
968
- console.error(`❌ ${agentId} summary.md κ°±μ‹  μ‹€νŒ¨: ${e.message}`);
969
- }
970
- }
971
- console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
972
- console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
973
- }
974
- search(agentId, query) {
975
- const entries = this.loadAllEntries(agentId);
976
- if (entries.length === 0) {
977
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
978
- return;
979
- }
980
- const memoryList = entries
981
- .map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
982
- .join('\n');
983
- const task = `κΈ°μ–΅ λͺ©λ‘:\n${memoryList}\n\n질문: "${query}"`;
984
- try {
985
- console.log(`πŸ” "${query}" 검색 쀑... (${this.searcherAgent})\n`);
986
- const result = (0, child_process_1.execSync)(`${CREWX_CLI} q "${this.searcherAgent} ${task.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf-8', timeout: this.searcherTimeout, stdio: ['pipe', 'pipe', 'pipe'] });
987
- const parsed = parseCrewxResponse(result);
988
- if (parsed) {
989
- console.log(`## 🧠 μ‹œλ§¨ν‹± 검색 κ²°κ³Ό\n`);
990
- console.log(parsed);
991
- }
992
- else {
993
- console.log(result.trim());
994
- }
995
- }
996
- catch (error) {
997
- const err = error;
998
- if (err.code === 'ETIMEDOUT' || err.killed) {
999
- console.log('검색 μ‹€νŒ¨: μ‹œκ°„ 초과 (μ΅œλŒ€ 5λΆ„)');
1000
- }
1001
- else {
1002
- console.log('검색 μ‹€νŒ¨:', err.message);
1003
- }
1004
- console.log('\nπŸ’‘ Tip: find λͺ…λ ΉμœΌλ‘œ ν‚€μ›Œλ“œ 검색을 μ‹œλ„ν•΄λ³΄μ„Έμš”.');
1005
- }
1006
- }
1007
- }
1008
- exports.MemoryEngine = MemoryEngine;
1009
- function createMemoryEngine(config) {
1010
- return new MemoryEngine(config);
1011
- }
1012
- var types_1 = require("./types");
1013
- Object.defineProperty(exports, "CATEGORIES", { enumerable: true, get: function () { return types_1.CATEGORIES; } });
1014
- //# 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}});