@crewx/memory 0.1.23-rc.5 β†’ 0.1.23-rc.51

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.
@@ -3,18 +3,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CATEGORIES = exports.MemoryEngine = void 0;
6
+ exports.CATEGORIES = exports.MemoryEngine = exports.SUMMARIZER_FAILURE_THRESHOLD = void 0;
7
7
  exports.getMemoryConfig = getMemoryConfig;
8
8
  exports.createMemoryEngine = createMemoryEngine;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
- const os_1 = __importDefault(require("os"));
12
11
  const child_process_1 = require("child_process");
13
12
  const nanoid_1 = require("nanoid");
14
13
  const js_yaml_1 = __importDefault(require("js-yaml"));
15
14
  const parser_1 = require("./parser");
16
15
  const knowledge_core_1 = require("@crewx/knowledge-core");
17
16
  const CREWX_CLI = process.env.CREWX_CLI || 'npx crewx';
17
+ exports.SUMMARIZER_FAILURE_THRESHOLD = 3;
18
+ const SUMMARIZER_HEALTH_FILE = '.summarizer-health.json';
18
19
  function parseCrewxResponse(raw) {
19
20
  const responseMatch = raw.match(/πŸ“„ Response:\s*─+\s*([\s\S]*?)(?=πŸ“ Working Directory|$)/);
20
21
  if (responseMatch?.[1]) {
@@ -162,6 +163,50 @@ class MemoryEngine {
162
163
  this.ensureDir(this.getAgentDir(agentId));
163
164
  fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
164
165
  }
166
+ getSummarizerHealthPath() {
167
+ return path_1.default.join(this.dataDir, SUMMARIZER_HEALTH_FILE);
168
+ }
169
+ readSummarizerHealth() {
170
+ try {
171
+ const raw = fs_1.default.readFileSync(this.getSummarizerHealthPath(), 'utf-8');
172
+ const parsed = JSON.parse(raw);
173
+ return {
174
+ consecutiveFailures: typeof parsed.consecutiveFailures === 'number' ? parsed.consecutiveFailures : 0,
175
+ notified: Boolean(parsed.notified),
176
+ };
177
+ }
178
+ catch {
179
+ return { consecutiveFailures: 0, notified: false };
180
+ }
181
+ }
182
+ writeSummarizerHealth(state) {
183
+ this.ensureDir(this.dataDir);
184
+ fs_1.default.writeFileSync(this.getSummarizerHealthPath(), JSON.stringify(state), 'utf-8');
185
+ }
186
+ recordSummarizerFailure() {
187
+ const state = this.readSummarizerHealth();
188
+ state.consecutiveFailures += 1;
189
+ if (state.consecutiveFailures >= exports.SUMMARIZER_FAILURE_THRESHOLD && !state.notified) {
190
+ this.notifySummarizerDegraded(state.consecutiveFailures);
191
+ state.notified = true;
192
+ }
193
+ this.writeSummarizerHealth(state);
194
+ }
195
+ recordSummarizerSuccess() {
196
+ const state = this.readSummarizerHealth();
197
+ if (state.consecutiveFailures === 0 && !state.notified)
198
+ return;
199
+ this.writeSummarizerHealth({ consecutiveFailures: 0, notified: false });
200
+ }
201
+ notifySummarizerDegraded(streakCount) {
202
+ try {
203
+ (0, child_process_1.execSync)(`${CREWX_CLI} notify "λ©”λͺ¨λ¦¬ AI μš”μ•½μ΄ 계속 μ‹€νŒ¨ν•΄ 기계 μš”μ•½μœΌλ‘œ λŒ€μ²΄λ˜κ³  μžˆμ–΄μš”" --level=warning --body="callSummarizer(agent=${this.summarizerAgent})κ°€ 연속 ${streakCount}회 μ‹€νŒ¨ν•˜μ—¬ fallback(기계) μš”μ•½μœΌλ‘œ μ „ν™˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€."`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15000 });
204
+ }
205
+ catch (e) {
206
+ const err = e;
207
+ console.error(`[memory] failed to send summarizer-health notify: ${err.message}`);
208
+ }
209
+ }
165
210
  loadAllEntries(agentId) {
166
211
  const entriesDir = this.getEntriesDir(agentId);
167
212
  if (!fs_1.default.existsSync(entriesDir))
@@ -172,7 +217,7 @@ class MemoryEngine {
172
217
  const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
173
218
  const { data, content } = (0, parser_1.parseFrontmatter)(raw);
174
219
  const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
175
- const hasBody = Boolean(bodyText) && bodyText !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
220
+ const hasBody = Boolean(bodyText) && bodyText !== '(Add details here)';
176
221
  const entry = data;
177
222
  entries.push({ ...entry, file, hasBody });
178
223
  }
@@ -217,16 +262,16 @@ class MemoryEngine {
217
262
  });
218
263
  const filename = `${id}.md`;
219
264
  const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
220
- const body = options.body ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
265
+ const body = options.body ?? '(Add details here)';
221
266
  const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
222
267
  fs_1.default.writeFileSync(filepath, content, 'utf-8');
223
- console.log(`βœ… μ €μž₯ μ™„λ£Œ: data/${agentId}/entries/${filename}`);
268
+ console.log(`βœ… Saved: data/${agentId}/entries/${filename}`);
224
269
  console.log(` ID: ${id}`);
225
- console.log(` Tags: ${tags.join(', ') || '(μ—†μŒ)'}`);
270
+ console.log(` Tags: ${tags.join(', ') || '(none)'}`);
226
271
  console.log(` Category: ${category}`);
227
272
  try {
228
273
  this.markSummaryDirty(agentId);
229
- console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
274
+ console.log(` πŸ“ summary.md update scheduled`);
230
275
  }
231
276
  catch (_e) {
232
277
  }
@@ -252,11 +297,11 @@ class MemoryEngine {
252
297
  }
253
298
  const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
254
299
  const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
255
- console.log(`# ${agentId} κΈ°μ–΅ μš”μ•½\n`);
300
+ console.log(`# ${agentId} memory summary\n`);
256
301
  const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
257
302
  ? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
258
303
  : '-';
259
- console.log(`> ${entries.length}건 | ν† ν”½ ${topics.length}개 | κ°±μ‹ : ${summaryLastUpdated}`);
304
+ console.log(`> ${entries.length} items | topics ${topics.length} items | updated: ${summaryLastUpdated}`);
260
305
  if (options.full) {
261
306
  const byCat = {};
262
307
  for (const entry of entries) {
@@ -278,7 +323,7 @@ class MemoryEngine {
278
323
  return bImp - aImp;
279
324
  return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
280
325
  });
281
- console.log(`## πŸ”₯ 졜근 μž‘μ—… (24h)\n`);
326
+ console.log(`## πŸ”₯ last Task (24h)\n`);
282
327
  for (const e of sortedShortTerm) {
283
328
  const star = e.important ? '⭐ ' : '';
284
329
  console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
@@ -302,7 +347,7 @@ class MemoryEngine {
302
347
  const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
303
348
  const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
304
349
  console.log('---');
305
- console.log(`πŸ“‹ ν† ν”½ μš”μ•½ (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays}일간)\n`);
350
+ console.log(`πŸ“‹ Topic summary (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays} days)\n`);
306
351
  }
307
352
  }
308
353
  console.log(summaryBody.trim());
@@ -310,7 +355,7 @@ class MemoryEngine {
310
355
  else {
311
356
  for (const tag of topics.sort()) {
312
357
  const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
313
- console.log(`- **${tag}** (${count}건)`);
358
+ console.log(`- **${tag}** (${count} items)`);
314
359
  }
315
360
  }
316
361
  }
@@ -343,15 +388,15 @@ class MemoryEngine {
343
388
  byCategory[category] = [];
344
389
  byCategory[category].push(entry);
345
390
  }
346
- let md = `# ${agentId} λ©”λͺ¨λ¦¬\n\n`;
391
+ let md = `# ${agentId} memory\n\n`;
347
392
  md += `> Last Updated: ${today}\n`;
348
393
  md += `> Total: ${entries.length} memories\n\n`;
349
- md += `## πŸ”₯ 졜근 ${this.recentDays}일\n\n`;
394
+ md += `## πŸ”₯ Last ${this.recentDays} days\n\n`;
350
395
  if (recent.length === 0) {
351
- md += `(μ—†μŒ)\n\n`;
396
+ md += `(none)\n\n`;
352
397
  }
353
398
  else {
354
- md += `| ID | λ‚ μ§œ | μΉ΄ν…Œκ³ λ¦¬ | μš”μ•½ | 상세 |\n`;
399
+ md += `| ID | Date | Category | Summary | Details |\n`;
355
400
  md += `|----|------|----------|------|------|\n`;
356
401
  for (const entry of recent) {
357
402
  const detailIcon = entry.hasBody ? 'πŸ“„' : '-';
@@ -359,47 +404,43 @@ class MemoryEngine {
359
404
  }
360
405
  md += `\n`;
361
406
  }
362
- md += `## 🏷️ μΉ΄ν…Œκ³ λ¦¬λ³„\n\n`;
407
+ md += `## 🏷️ By category\n\n`;
363
408
  for (const [category, items] of Object.entries(byCategory).sort()) {
364
- md += `- **${category}**: ${items.length}건\n`;
409
+ md += `- **${category}**: ${items.length} items\n`;
365
410
  }
366
411
  const olderCount = entries.length - recent.length;
367
412
  const oldestDate = entries[entries.length - 1]?.date ?? '-';
368
413
  const newestDate = entries[0]?.date ?? '-';
369
- md += `\n## πŸ“Š 톡계\n\n`;
370
- md += `- 전체: ${entries.length}건 (${oldestDate} ~ ${newestDate})\n`;
371
- md += `- 졜근 ${this.recentDays}일: ${recent.length}건\n`;
414
+ md += `\n## πŸ“Š Stats\n\n`;
415
+ md += `- Total: ${entries.length} items (${oldestDate} ~ ${newestDate})\n`;
416
+ md += `- Last ${this.recentDays} days: ${recent.length} items\n`;
372
417
  if (olderCount > 0)
373
- md += `- ${this.recentDays}일 이전: ${olderCount}건\n`;
418
+ md += `- Older than ${this.recentDays} days: ${olderCount} items\n`;
374
419
  return md;
375
420
  }
376
421
  topic(agentId, topicName) {
377
422
  const entries = this.loadAllEntries(agentId);
378
423
  const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
379
424
  if (filtered.length === 0) {
380
- console.log(`ν† ν”½ '${topicName}'에 ν•΄λ‹Ήν•˜λŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
425
+ console.log(`No memories found for topic '${topicName}'.`);
381
426
  return;
382
427
  }
383
- console.log(`## πŸ“‚ ν† ν”½: ${topicName} (${filtered.length}건)\n`);
428
+ console.log(`## πŸ“‚ Topic: ${topicName} (${filtered.length} items)\n`);
384
429
  for (const entry of filtered) {
385
430
  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(', ') || '-'}`);
431
+ console.log(`- File: data/${agentId}/entries/${entry.file}`);
432
+ console.log(`- Category: ${entry.category ?? '-'}`);
433
+ console.log(`- Tags: ${(entry.tags ?? []).join(', ') || '-'}`);
389
434
  console.log('');
390
435
  }
391
436
  }
392
437
  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);
438
+ const filtered = this.getRecentEntries(agentId, days);
398
439
  if (filtered.length === 0) {
399
- console.log(`졜근 ${days}일 λ‚΄ 기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
440
+ console.log(`No memories found in the last ${days} days.`);
400
441
  return;
401
442
  }
402
- console.log(`## πŸ”₯ 졜근 ${days}일 (${filtered.length}건)\n`);
443
+ console.log(`## πŸ”₯ Last ${days} days (${filtered.length} items)\n`);
403
444
  for (const entry of filtered) {
404
445
  const detailIcon = entry.hasBody ? ' πŸ“„' : '';
405
446
  const timestamp = String(entry.date).includes('T')
@@ -408,10 +449,59 @@ class MemoryEngine {
408
449
  console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
409
450
  }
410
451
  }
452
+ getRecentEntries(agentId, days = 30, opts = {}) {
453
+ const entries = this.loadAllEntries(agentId);
454
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
455
+ .toISOString()
456
+ .slice(0, 10);
457
+ const filtered = entries.filter((e) => this.resolveRecentDate(agentId, e).slice(0, 10) >= cutoff);
458
+ if (!opts.includeBody)
459
+ return filtered;
460
+ return filtered.map((e) => ({
461
+ ...e,
462
+ body: this.readEntryBody(agentId, e.file),
463
+ }));
464
+ }
465
+ resolveRecentDate(agentId, entry) {
466
+ const updated = this.toDateString(entry.updated);
467
+ if (updated)
468
+ return updated;
469
+ const createdAt = this.toDateString(entry['created_at']);
470
+ if (createdAt)
471
+ return createdAt;
472
+ const date = this.toDateString(entry.date);
473
+ if (date)
474
+ return date;
475
+ try {
476
+ const filePath = path_1.default.join(this.getEntriesDir(agentId), entry.file);
477
+ return fs_1.default.statSync(filePath).mtime.toISOString();
478
+ }
479
+ catch {
480
+ return '';
481
+ }
482
+ }
483
+ toDateString(value) {
484
+ if (value &&
485
+ typeof value === 'object' &&
486
+ typeof value.toISOString === 'function') {
487
+ return value.toISOString();
488
+ }
489
+ return typeof value === 'string' ? value : '';
490
+ }
491
+ readEntryBody(agentId, file) {
492
+ try {
493
+ const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), file), 'utf-8');
494
+ const { content } = (0, parser_1.parseFrontmatter)(raw);
495
+ return content.replace(/^#[^\n]*\n*/m, '').trim();
496
+ }
497
+ catch {
498
+ return '';
499
+ }
500
+ }
411
501
  find(agentId, keyword) {
412
502
  const entriesDir = this.getEntriesDir(agentId);
413
503
  if (!fs_1.default.existsSync(entriesDir)) {
414
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
504
+ console.log('No memories found.');
415
505
  return;
416
506
  }
417
507
  const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
@@ -423,18 +513,18 @@ class MemoryEngine {
423
513
  entries.push({ ...entry, file, body });
424
514
  }
425
515
  if (entries.length === 0) {
426
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
516
+ console.log('No memories found.');
427
517
  return;
428
518
  }
429
519
  const kcEntries = entries;
430
520
  const results = (0, knowledge_core_1.searchEntries)(kcEntries, keyword, { summary: 3, tags: 2, body: 1 });
431
521
  if (results.length === 0) {
432
- console.log(`'${keyword}' κ΄€λ ¨ 기얡을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
522
+ console.log(`No related memories found for '${keyword}'.`);
433
523
  return;
434
524
  }
435
- console.log(`## πŸ” 검색 κ²°κ³Ό: "${keyword}" (${results.length}건, BM25 점수순)\n`);
525
+ console.log(`## πŸ” Search results: "${keyword}" (${results.length} items, sorted by BM25 score)\n`);
436
526
  for (const entry of results) {
437
- const score = entry._score != null ? ` [${entry._score}점]` : '';
527
+ const score = entry._score != null ? ` [${entry._score}pts]` : '';
438
528
  console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
439
529
  }
440
530
  const resultTags = [...new Set(results.flatMap(e => e.tags ?? []))].slice(0, 3);
@@ -470,7 +560,7 @@ class MemoryEngine {
470
560
  const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, e.file), 'utf-8');
471
561
  const { content: rawContent } = (0, parser_1.parseFrontmatter)(raw);
472
562
  const body = rawContent.replace(/^#[^\n]*\n*/m, '').trim();
473
- if (body !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)')
563
+ if (body !== '(Add details here)')
474
564
  content = body;
475
565
  }
476
566
  catch (_err) {
@@ -494,10 +584,10 @@ class MemoryEngine {
494
584
  filters.push(`category=${options.category}`);
495
585
  if (options.tag)
496
586
  filters.push(`tag=${options.tag}`);
497
- console.log(`쑰건에 λ§žλŠ” 기얡이 μ—†μŠ΅λ‹ˆλ‹€. (${filters.join(', ') || '전체'})`);
587
+ console.log(`No memories match the filters. (${filters.join(', ') || 'all'})`);
498
588
  return;
499
589
  }
500
- console.log(`## πŸ“‹ λͺ©λ‘: ${agentId} (${filtered.length}건)\n`);
590
+ console.log(`## πŸ“‹ List: ${agentId} (${filtered.length} items)\n`);
501
591
  for (const entry of filtered) {
502
592
  console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
503
593
  }
@@ -518,7 +608,7 @@ class MemoryEngine {
518
608
  stats(agentId) {
519
609
  const entries = this.loadAllEntries(agentId);
520
610
  if (entries.length === 0) {
521
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
611
+ console.log('No memories found.');
522
612
  return;
523
613
  }
524
614
  const byCategory = {};
@@ -532,15 +622,15 @@ class MemoryEngine {
532
622
  byTag[tag] = (byTag[tag] ?? 0) + 1;
533
623
  }
534
624
  }
535
- console.log(`# ${agentId} 톡계 (${entries.length}건)\n`);
536
- console.log(`## μΉ΄ν…Œκ³ λ¦¬λ³„`);
625
+ console.log(`# ${agentId} Stats (${entries.length} items)\n`);
626
+ console.log(`## By category`);
537
627
  for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
538
- console.log(` ${cat}: ${count}건`);
628
+ console.log(` ${cat}: ${count} items`);
539
629
  }
540
- console.log(`\n## νƒœκ·Έ Top 20`);
630
+ console.log(`\n## Top 20 Tags`);
541
631
  const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
542
632
  for (const [tag, count] of sortedTags) {
543
- console.log(` ${tag}: ${count}건`);
633
+ console.log(` ${tag}: ${count} items`);
544
634
  }
545
635
  const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
546
636
  const statsCtx = {
@@ -557,7 +647,7 @@ class MemoryEngine {
557
647
  tags(agentId) {
558
648
  const entries = this.loadAllEntries(agentId);
559
649
  if (entries.length === 0) {
560
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
650
+ console.log('No memories found.');
561
651
  return;
562
652
  }
563
653
  const byTag = {};
@@ -568,18 +658,18 @@ class MemoryEngine {
568
658
  }
569
659
  const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
570
660
  if (sortedTags.length === 0) {
571
- console.log('νƒœκ·Έκ°€ μ—†μŠ΅λ‹ˆλ‹€.');
661
+ console.log('No tags found.');
572
662
  return;
573
663
  }
574
- console.log(`## 🏷️ 전체 νƒœκ·Έ (${sortedTags.length}개)\n`);
664
+ console.log(`## 🏷️ All tags (${sortedTags.length} items)\n`);
575
665
  for (const [tag, count] of sortedTags) {
576
- console.log(` ${tag}: ${count}건`);
666
+ console.log(` ${tag}: ${count} items`);
577
667
  }
578
668
  }
579
669
  get(agentId, memoryId) {
580
670
  const entriesDir = this.getEntriesDir(agentId);
581
671
  if (!fs_1.default.existsSync(entriesDir)) {
582
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
672
+ console.log('No memories found.');
583
673
  return;
584
674
  }
585
675
  const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
@@ -591,10 +681,10 @@ class MemoryEngine {
591
681
  const d = data;
592
682
  console.log(`## πŸ“„ ${d.summary}\n`);
593
683
  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}`);
684
+ console.log(`- Date: ${d.date}`);
685
+ console.log(`- Category: ${d.category ?? '-'}`);
686
+ console.log(`- Tags: ${(d.tags ?? []).join(', ') || '-'}`);
687
+ console.log(`- File: data/${agentId}/entries/${file}`);
598
688
  console.log(`\n---\n`);
599
689
  console.log(content.trim());
600
690
  const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
@@ -611,7 +701,7 @@ class MemoryEngine {
611
701
  nodeMap[n.id] = n;
612
702
  });
613
703
  console.log(`\n---\n`);
614
- console.log(`## πŸ”— μ—°κ΄€ κΈ°μ–΅ (${relatedEdges.length}개)\n`);
704
+ console.log(`## πŸ”— Related memories (${relatedEdges.length} items)\n`);
615
705
  for (const edge of relatedEdges) {
616
706
  const otherId = edge.from === memoryId ? edge.to : edge.from;
617
707
  const other = nodeMap[otherId];
@@ -638,12 +728,12 @@ class MemoryEngine {
638
728
  return;
639
729
  }
640
730
  }
641
- console.log(`ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
731
+ console.log(`ID '${memoryId}' not found.`);
642
732
  }
643
733
  update(agentId, memoryId, options = {}) {
644
734
  const entry = this.findEntryById(agentId, memoryId);
645
735
  if (!entry) {
646
- console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
736
+ console.log(`❌ ID '${memoryId}' not found.`);
647
737
  process.exit(1);
648
738
  }
649
739
  const { filePath, data, content } = entry;
@@ -664,11 +754,11 @@ class MemoryEngine {
664
754
  else if (options['no-important']) {
665
755
  delete newData['important'];
666
756
  }
667
- const newBody = options.body ?? existingBody ?? '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)';
757
+ const newBody = options.body ?? existingBody ?? '(Add details here)';
668
758
  const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
669
759
  const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
670
760
  fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
671
- console.log(`βœ… μˆ˜μ • μ™„λ£Œ: ${entry.file}`);
761
+ console.log(`βœ… Updated: ${entry.file}`);
672
762
  console.log(` ID: ${memoryId}`);
673
763
  if (options.summary)
674
764
  console.log(` Summary: ${String(newData['summary'])}`);
@@ -677,28 +767,28 @@ class MemoryEngine {
677
767
  if (options.tags)
678
768
  console.log(` Tags: ${newData['tags'].join(', ')}`);
679
769
  if (options.body)
680
- console.log(` Body: (μ—…λ°μ΄νŠΈλ¨)`);
770
+ console.log(` Body: (updated)`);
681
771
  }
682
772
  delete(agentId, memoryId, options = {}) {
683
773
  const entry = this.findEntryById(agentId, memoryId);
684
774
  if (!entry) {
685
- console.log(`❌ ID '${memoryId}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
775
+ console.log(`❌ ID '${memoryId}' not found.`);
686
776
  process.exit(1);
687
777
  }
688
778
  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`);
779
+ console.log(`⚠️ Delete target: [${entry.data.id}] ${entry.data.summary}`);
780
+ console.log(` File: ${entry.file}`);
781
+ console.log(`\nAdd --force to delete it.`);
782
+ console.log(`Example: memory delete ${agentId} ${memoryId} --force`);
693
783
  return;
694
784
  }
695
785
  fs_1.default.unlinkSync(entry.filePath);
696
- console.log(`πŸ—‘οΈ μ‚­μ œ μ™„λ£Œ: ${entry.file}`);
786
+ console.log(`πŸ—‘οΈ Deleted: ${entry.file}`);
697
787
  console.log(` ID: ${memoryId}`);
698
788
  console.log(` Summary: ${entry.data.summary}`);
699
789
  try {
700
790
  this.markSummaryDirty(agentId);
701
- console.log(` πŸ“ summary.md κ°±μ‹  μ˜ˆμ•½λ¨`);
791
+ console.log(` πŸ“ summary.md update scheduled`);
702
792
  }
703
793
  catch (_e) {
704
794
  }
@@ -707,27 +797,27 @@ class MemoryEngine {
707
797
  const entry1 = this.findEntryById(agentId, memoryId1);
708
798
  const entry2 = this.findEntryById(agentId, memoryId2);
709
799
  if (!entry1) {
710
- console.log(`❌ ID '${memoryId1}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
800
+ console.log(`❌ ID '${memoryId1}' not found.`);
711
801
  process.exit(1);
712
802
  }
713
803
  if (!entry2) {
714
- console.log(`❌ ID '${memoryId2}'λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.`);
804
+ console.log(`❌ ID '${memoryId2}' not found.`);
715
805
  process.exit(1);
716
806
  }
717
807
  const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
718
808
  const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
719
809
  const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
720
- const mergedBody = `## λ³‘ν•©λœ κΈ°μ–΅ 1 (${entry1.data.id}, ${entry1.data.date})
810
+ const mergedBody = `## Merged memory 1 (${entry1.data.id}, ${entry1.data.date})
721
811
  ${entry1.data.summary}
722
812
 
723
- ${body1 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body1 : '(상세 μ—†μŒ)'}
813
+ ${body1 !== '(Add details here)' ? body1 : '(No details)'}
724
814
 
725
815
  ---
726
816
 
727
- ## λ³‘ν•©λœ κΈ°μ–΅ 2 (${entry2.data.id}, ${entry2.data.date})
817
+ ## Merged memory 2 (${entry2.data.id}, ${entry2.data.date})
728
818
  ${entry2.data.summary}
729
819
 
730
- ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
820
+ ${body2 !== '(Add details here)' ? body2 : '(No details)'}`;
731
821
  const mergedTags = [
732
822
  ...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
733
823
  ];
@@ -749,10 +839,10 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
749
839
  fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
750
840
  fs_1.default.unlinkSync(entry1.filePath);
751
841
  fs_1.default.unlinkSync(entry2.filePath);
752
- console.log(`πŸ”€ 병합 μ™„λ£Œ!`);
753
- console.log(` μƒˆ ID: ${newId}`);
754
- console.log(` μƒˆ 파일: ${filename}`);
755
- console.log(` λ³‘ν•©λœ κΈ°μ–΅: [${memoryId1}] + [${memoryId2}]`);
842
+ console.log(`πŸ”€ Merge complete!`);
843
+ console.log(` New ID: ${newId}`);
844
+ console.log(` New file: ${filename}`);
845
+ console.log(` Merged memory: [${memoryId1}] + [${memoryId2}]`);
756
846
  console.log(` Summary: ${mergedSummary}`);
757
847
  }
758
848
  buildFallbackSummary(entries) {
@@ -778,7 +868,7 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
778
868
  const oldest = dates[0];
779
869
  const newest = dates[dates.length - 1];
780
870
  const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
781
- body += `## ${topic} (${items.length}건${rangeLabel})\n`;
871
+ body += `## ${topic} (${items.length} items${rangeLabel})\n`;
782
872
  for (const e of items)
783
873
  body += `- [${e.date}] ${e.summary}\n`;
784
874
  for (const e of items)
@@ -788,22 +878,26 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
788
878
  return body;
789
879
  }
790
880
  callSummarizer(prompt) {
791
- const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
792
881
  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) {
882
+ const result = (0, child_process_1.execSync)(`${CREWX_CLI} q "${this.summarizerAgent}"`, {
883
+ encoding: 'utf-8',
884
+ timeout: this.summarizerTimeout,
885
+ stdio: ['pipe', 'pipe', 'pipe'],
886
+ input: prompt,
887
+ });
888
+ const parsed = parseCrewxResponse(result);
889
+ this.recordSummarizerSuccess();
890
+ return parsed;
891
+ }
892
+ catch (e) {
893
+ const err = e;
894
+ const reason = err.code === 'ETIMEDOUT' || err.killed
895
+ ? `timed out after ${this.summarizerTimeout}ms`
896
+ : err.message;
897
+ console.error(`[memory] callSummarizer failed (agent=${this.summarizerAgent}), falling back to raw entry list: ${reason}`);
898
+ this.recordSummarizerFailure();
798
899
  return null;
799
900
  }
800
- finally {
801
- try {
802
- fs_1.default.unlinkSync(tmpFile);
803
- }
804
- catch (_e) {
805
- }
806
- }
807
901
  }
808
902
  generateSummary(agentId, options = {}) {
809
903
  const allEntries = this.loadAllEntries(agentId);
@@ -861,13 +955,13 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
861
955
  for (const entry of newItems) {
862
956
  const importantTag = entry.important ? ' [important]' : '';
863
957
  p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
864
- if (entry.body && entry.body !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)') {
958
+ if (entry.body && entry.body !== '(Add details here)') {
865
959
  p += ` β€” ${entry.body.slice(0, 200)}`;
866
960
  }
867
961
  p += ` (file: entries/${entry.file})\n`;
868
962
  }
869
963
  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`;
964
+ 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`;
871
965
  p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
872
966
  p += `- After each topic summary, list relevant entry file links as "β†’ [entries/filename](entries/filename)"\n`;
873
967
  p += `- Highlight key decisions, insights, and action items\n`;
@@ -917,26 +1011,26 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
917
1011
  summarize(agentId, options = {}) {
918
1012
  const entries = this.loadAllEntries(agentId);
919
1013
  if (entries.length === 0) {
920
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
1014
+ console.log('No memories found.');
921
1015
  return;
922
1016
  }
923
- console.log(`πŸ“ μš”μ•½ 생성 μ‹œμž‘ (${entries.length}개 μ—”νŠΈλ¦¬)\n`);
1017
+ console.log(`πŸ“ Starting summary generation (${entries.length} items entries)\n`);
924
1018
  const result = this.generateSummary(agentId, { force: options.force });
925
1019
  if (result.status === 'up-to-date') {
926
- console.log('βœ… summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.');
1020
+ console.log('βœ… summary.md is up to date.');
927
1021
  }
928
1022
  else if (result.status === 'fallback') {
929
- console.log(`πŸ“‹ 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
1023
+ console.log(`πŸ“‹ Fallback summary generated (AI call failed)`);
930
1024
  }
931
1025
  else if (result.status === 'updated') {
932
- console.log(`πŸ€– μš”μ•½ μ™„λ£Œ (${result.newEntries}건 반영)`);
1026
+ console.log(`πŸ€– Summary complete (${result.newEntries} items applied)`);
933
1027
  }
934
- console.log(` 파일: data/${agentId}/summary.md`);
935
- console.log(` μ—”νŠΈλ¦¬: ${result.entryCount}개`);
1028
+ console.log(` File: data/${agentId}/summary.md`);
1029
+ console.log(` entries: ${result.entryCount} items`);
936
1030
  }
937
1031
  summarizeDirty() {
938
1032
  if (!fs_1.default.existsSync(this.dataDir)) {
939
- console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
1033
+ console.log(`πŸ’‘ Register debounce cron:`);
940
1034
  console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
941
1035
  return;
942
1036
  }
@@ -951,42 +1045,42 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
951
1045
  try {
952
1046
  const result = this.generateSummary(agentId);
953
1047
  if (result.status === 'updated') {
954
- console.log(`πŸ€– ${agentId} summary.md κ°±μ‹  (${result.newEntries}건 반영)`);
1048
+ console.log(`πŸ€– ${agentId} summary.md updated (${result.newEntries} items applied)`);
955
1049
  }
956
1050
  else if (result.status === 'fallback') {
957
- console.log(`πŸ“‹ ${agentId} 폴백 μš”μ•½ 생성 μ™„λ£Œ (AI 호좜 μ‹€νŒ¨)`);
1051
+ console.log(`πŸ“‹ ${agentId} Fallback summary generated (AI call failed)`);
958
1052
  }
959
1053
  else if (result.status === 'up-to-date') {
960
- console.log(`βœ… ${agentId} summary.md μ΅œμ‹  μƒνƒœμž…λ‹ˆλ‹€.`);
1054
+ console.log(`βœ… ${agentId} summary.md is up to date.`);
961
1055
  }
962
1056
  else if (result.status === 'empty') {
963
- console.log(`ℹ️ ${agentId} μš”μ•½ν•  기얡이 μ—†μŠ΅λ‹ˆλ‹€.`);
1057
+ console.log(`ℹ️ ${agentId} has no memories to summarize.`);
964
1058
  }
965
1059
  fs_1.default.unlinkSync(dirtyPath);
966
1060
  }
967
1061
  catch (e) {
968
- console.error(`❌ ${agentId} summary.md κ°±μ‹  μ‹€νŒ¨: ${e.message}`);
1062
+ console.error(`❌ ${agentId} summary.md update failed: ${e.message}`);
969
1063
  }
970
1064
  }
971
- console.log(`πŸ’‘ λ””λ°”μš΄μŠ€ cron 등둝:`);
1065
+ console.log(`πŸ’‘ Register debounce cron:`);
972
1066
  console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
973
1067
  }
974
1068
  search(agentId, query) {
975
1069
  const entries = this.loadAllEntries(agentId);
976
1070
  if (entries.length === 0) {
977
- console.log('기얡이 μ—†μŠ΅λ‹ˆλ‹€.');
1071
+ console.log('No memories found.');
978
1072
  return;
979
1073
  }
980
1074
  const memoryList = entries
981
1075
  .map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
982
1076
  .join('\n');
983
- const task = `κΈ°μ–΅ λͺ©λ‘:\n${memoryList}\n\n질문: "${query}"`;
1077
+ const task = `Memory list:\n${memoryList}\n\nQuestion: "${query}"`;
984
1078
  try {
985
- console.log(`πŸ” "${query}" 검색 쀑... (${this.searcherAgent})\n`);
1079
+ console.log(`πŸ” "${query}" searching... (${this.searcherAgent})\n`);
986
1080
  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
1081
  const parsed = parseCrewxResponse(result);
988
1082
  if (parsed) {
989
- console.log(`## 🧠 μ‹œλ§¨ν‹± 검색 κ²°κ³Ό\n`);
1083
+ console.log(`## 🧠 Semantic search results\n`);
990
1084
  console.log(parsed);
991
1085
  }
992
1086
  else {
@@ -996,12 +1090,12 @@ ${body2 !== '(상세 λ‚΄μš©μ„ 여기에 μΆ”κ°€)' ? body2 : '(상세 μ—†μŒ)'}`;
996
1090
  catch (error) {
997
1091
  const err = error;
998
1092
  if (err.code === 'ETIMEDOUT' || err.killed) {
999
- console.log('검색 μ‹€νŒ¨: μ‹œκ°„ 초과 (μ΅œλŒ€ 5λΆ„)');
1093
+ console.log('Search failed: timed out (max 5 minutes)');
1000
1094
  }
1001
1095
  else {
1002
- console.log('검색 μ‹€νŒ¨:', err.message);
1096
+ console.log('Search failed:', err.message);
1003
1097
  }
1004
- console.log('\nπŸ’‘ Tip: find λͺ…λ ΉμœΌλ‘œ ν‚€μ›Œλ“œ 검색을 μ‹œλ„ν•΄λ³΄μ„Έμš”.');
1098
+ console.log('\nπŸ’‘ Tip: try keyword search with the find command.');
1005
1099
  }
1006
1100
  }
1007
1101
  }