@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.
- package/dist/cli.js +8 -8
- package/dist/cli.js.map +1 -1
- package/dist/src/engine.d.ts +13 -0
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +211 -117
- package/dist/src/engine.js.map +1 -1
- package/dist/src/mindmap.js +57 -57
- package/dist/src/mindmap.js.map +1 -1
- package/package.json +3 -3
package/dist/src/engine.js
CHANGED
|
@@ -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(`β
|
|
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}
|
|
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}
|
|
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(`## π₯
|
|
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(`π
|
|
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}
|
|
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 += `## π₯
|
|
394
|
+
md += `## π₯ Last ${this.recentDays} days\n\n`;
|
|
350
395
|
if (recent.length === 0) {
|
|
351
|
-
md += `(
|
|
396
|
+
md += `(none)\n\n`;
|
|
352
397
|
}
|
|
353
398
|
else {
|
|
354
|
-
md += `| ID |
|
|
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 += `## π·οΈ
|
|
407
|
+
md += `## π·οΈ By category\n\n`;
|
|
363
408
|
for (const [category, items] of Object.entries(byCategory).sort()) {
|
|
364
|
-
md += `- **${category}**: ${items.length}
|
|
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## π
|
|
370
|
-
md += `-
|
|
371
|
-
md += `-
|
|
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}
|
|
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(
|
|
425
|
+
console.log(`No memories found for topic '${topicName}'.`);
|
|
381
426
|
return;
|
|
382
427
|
}
|
|
383
|
-
console.log(`## π
|
|
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(`-
|
|
387
|
-
console.log(`-
|
|
388
|
-
console.log(`-
|
|
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
|
|
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(
|
|
440
|
+
console.log(`No memories found in the last ${days} days.`);
|
|
400
441
|
return;
|
|
401
442
|
}
|
|
402
|
-
console.log(`## π₯
|
|
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(`## π
|
|
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(
|
|
587
|
+
console.log(`No memories match the filters. (${filters.join(', ') || 'all'})`);
|
|
498
588
|
return;
|
|
499
589
|
}
|
|
500
|
-
console.log(`## π
|
|
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}
|
|
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##
|
|
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(`## π·οΈ
|
|
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(`-
|
|
595
|
-
console.log(`-
|
|
596
|
-
console.log(`-
|
|
597
|
-
console.log(`-
|
|
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(`## π
|
|
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(`β
|
|
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(`β οΈ
|
|
690
|
-
console.log(`
|
|
691
|
-
console.log(`\
|
|
692
|
-
console.log(
|
|
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(`ποΈ
|
|
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 = `##
|
|
810
|
+
const mergedBody = `## Merged memory 1 (${entry1.data.id}, ${entry1.data.date})
|
|
721
811
|
${entry1.data.summary}
|
|
722
812
|
|
|
723
|
-
${body1 !== '(
|
|
813
|
+
${body1 !== '(Add details here)' ? body1 : '(No details)'}
|
|
724
814
|
|
|
725
815
|
---
|
|
726
816
|
|
|
727
|
-
##
|
|
817
|
+
## Merged memory 2 (${entry2.data.id}, ${entry2.data.date})
|
|
728
818
|
${entry2.data.summary}
|
|
729
819
|
|
|
730
|
-
${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(`
|
|
754
|
-
console.log(`
|
|
755
|
-
console.log(`
|
|
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}
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
|
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(`π
|
|
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(`π
|
|
1023
|
+
console.log(`π Fallback summary generated (AI call failed)`);
|
|
930
1024
|
}
|
|
931
1025
|
else if (result.status === 'updated') {
|
|
932
|
-
console.log(`π€
|
|
1026
|
+
console.log(`π€ Summary complete (${result.newEntries} items applied)`);
|
|
933
1027
|
}
|
|
934
|
-
console.log(`
|
|
935
|
-
console.log(`
|
|
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(`π‘
|
|
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
|
|
1048
|
+
console.log(`π€ ${agentId} summary.md updated (${result.newEntries} items applied)`);
|
|
955
1049
|
}
|
|
956
1050
|
else if (result.status === 'fallback') {
|
|
957
|
-
console.log(`π ${agentId}
|
|
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
|
|
1062
|
+
console.error(`β ${agentId} summary.md update failed: ${e.message}`);
|
|
969
1063
|
}
|
|
970
1064
|
}
|
|
971
|
-
console.log(`π‘
|
|
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 =
|
|
1077
|
+
const task = `Memory list:\n${memoryList}\n\nQuestion: "${query}"`;
|
|
984
1078
|
try {
|
|
985
|
-
console.log(`π "${query}"
|
|
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(`## π§
|
|
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('
|
|
1093
|
+
console.log('Search failed: timed out (max 5 minutes)');
|
|
1000
1094
|
}
|
|
1001
1095
|
else {
|
|
1002
|
-
console.log('
|
|
1096
|
+
console.log('Search failed:', err.message);
|
|
1003
1097
|
}
|
|
1004
|
-
console.log('\nπ‘ Tip:
|
|
1098
|
+
console.log('\nπ‘ Tip: try keyword search with the find command.');
|
|
1005
1099
|
}
|
|
1006
1100
|
}
|
|
1007
1101
|
}
|