@crewx/memory 0.1.23-rc.9 β 0.1.23-rc.91
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 +24 -0
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +355 -160
- 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,21 @@ 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.SUMMARIZE_LOCK_STALE_MS = 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';
|
|
19
|
+
const FIND_RESULT_LIMIT = 50;
|
|
20
|
+
exports.SUMMARIZE_LOCK_STALE_MS = 10 * 60 * 1000;
|
|
18
21
|
function parseCrewxResponse(raw) {
|
|
19
22
|
const responseMatch = raw.match(/π Response:\s*β+\s*([\s\S]*?)(?=π Working Directory|$)/);
|
|
20
23
|
if (responseMatch?.[1]) {
|
|
@@ -75,6 +78,7 @@ function getMemoryConfig() {
|
|
|
75
78
|
}
|
|
76
79
|
class MemoryEngine {
|
|
77
80
|
constructor(config = {}) {
|
|
81
|
+
this.searchIndexCache = new Map();
|
|
78
82
|
const yamlConfig = getMemoryConfig();
|
|
79
83
|
this.dataDir =
|
|
80
84
|
config.dataDir ??
|
|
@@ -103,7 +107,7 @@ class MemoryEngine {
|
|
|
103
107
|
this.summarizerTimeout =
|
|
104
108
|
config.summarizerTimeout ??
|
|
105
109
|
(yamlConfig.summarizer_timeout != null ? Number(yamlConfig.summarizer_timeout) : undefined) ??
|
|
106
|
-
|
|
110
|
+
480000;
|
|
107
111
|
this.searcherTimeout =
|
|
108
112
|
config.searcherTimeout ??
|
|
109
113
|
(yamlConfig.searcher_timeout != null ? Number(yamlConfig.searcher_timeout) : undefined) ??
|
|
@@ -115,12 +119,59 @@ class MemoryEngine {
|
|
|
115
119
|
getEntriesDir(agentId) {
|
|
116
120
|
return path_1.default.join(this.getAgentDir(agentId), 'entries');
|
|
117
121
|
}
|
|
122
|
+
getEntryFilesFingerprint(agentId) {
|
|
123
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
124
|
+
if (!fs_1.default.existsSync(entriesDir))
|
|
125
|
+
return { files: [], fingerprint: '' };
|
|
126
|
+
const files = fs_1.default.readdirSync(entriesDir)
|
|
127
|
+
.filter((file) => file.endsWith('.md'))
|
|
128
|
+
.sort();
|
|
129
|
+
const fingerprint = files.map((file) => {
|
|
130
|
+
const stat = fs_1.default.statSync(path_1.default.join(entriesDir, file));
|
|
131
|
+
return `${file}\u0000${stat.size}\u0000${stat.mtimeMs}`;
|
|
132
|
+
}).join('\u0001');
|
|
133
|
+
return { files, fingerprint };
|
|
134
|
+
}
|
|
135
|
+
buildSearchIndex(agentId, files) {
|
|
136
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
137
|
+
const entries = files.map((file) => {
|
|
138
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
139
|
+
const { data, content: body } = (0, parser_1.parseFrontmatter)(raw);
|
|
140
|
+
return { ...data, file, body };
|
|
141
|
+
});
|
|
142
|
+
return (0, knowledge_core_1.createSearchIndex)(entries, { summary: 3, tags: 2, body: 1 });
|
|
143
|
+
}
|
|
144
|
+
getSearchIndex(agentId) {
|
|
145
|
+
const snapshot = this.getEntryFilesFingerprint(agentId);
|
|
146
|
+
const cached = this.searchIndexCache.get(agentId);
|
|
147
|
+
if (cached?.fingerprint === snapshot.fingerprint) {
|
|
148
|
+
return { files: snapshot.files, index: cached.index };
|
|
149
|
+
}
|
|
150
|
+
const index = this.buildSearchIndex(agentId, snapshot.files);
|
|
151
|
+
this.searchIndexCache.set(agentId, { fingerprint: snapshot.fingerprint, index });
|
|
152
|
+
return { files: snapshot.files, index };
|
|
153
|
+
}
|
|
154
|
+
invalidateSearchIndex(agentId) {
|
|
155
|
+
this.searchIndexCache.delete(agentId);
|
|
156
|
+
}
|
|
157
|
+
createFindActionContext(agentId, keyword, results) {
|
|
158
|
+
return {
|
|
159
|
+
commandId: 'memory.find',
|
|
160
|
+
params: { agentId, query: keyword },
|
|
161
|
+
resultTags: [...new Set(results.flatMap((entry) => entry.tags ?? []))].slice(0, 3),
|
|
162
|
+
resultIds: results.slice(0, 3).map((entry) => entry.id),
|
|
163
|
+
resultCount: results.length,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
118
166
|
getSummaryPath(agentId) {
|
|
119
167
|
return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
|
|
120
168
|
}
|
|
121
169
|
getDirtySummaryPath(agentId) {
|
|
122
170
|
return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
|
|
123
171
|
}
|
|
172
|
+
getSummarizeLockPath() {
|
|
173
|
+
return path_1.default.join(this.dataDir, '.summarize.lock');
|
|
174
|
+
}
|
|
124
175
|
ensureDir(dir) {
|
|
125
176
|
if (!fs_1.default.existsSync(dir)) {
|
|
126
177
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
@@ -162,6 +213,50 @@ class MemoryEngine {
|
|
|
162
213
|
this.ensureDir(this.getAgentDir(agentId));
|
|
163
214
|
fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
|
|
164
215
|
}
|
|
216
|
+
getSummarizerHealthPath() {
|
|
217
|
+
return path_1.default.join(this.dataDir, SUMMARIZER_HEALTH_FILE);
|
|
218
|
+
}
|
|
219
|
+
readSummarizerHealth() {
|
|
220
|
+
try {
|
|
221
|
+
const raw = fs_1.default.readFileSync(this.getSummarizerHealthPath(), 'utf-8');
|
|
222
|
+
const parsed = JSON.parse(raw);
|
|
223
|
+
return {
|
|
224
|
+
consecutiveFailures: typeof parsed.consecutiveFailures === 'number' ? parsed.consecutiveFailures : 0,
|
|
225
|
+
notified: Boolean(parsed.notified),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return { consecutiveFailures: 0, notified: false };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
writeSummarizerHealth(state) {
|
|
233
|
+
this.ensureDir(this.dataDir);
|
|
234
|
+
fs_1.default.writeFileSync(this.getSummarizerHealthPath(), JSON.stringify(state), 'utf-8');
|
|
235
|
+
}
|
|
236
|
+
recordSummarizerFailure() {
|
|
237
|
+
const state = this.readSummarizerHealth();
|
|
238
|
+
state.consecutiveFailures += 1;
|
|
239
|
+
if (state.consecutiveFailures >= exports.SUMMARIZER_FAILURE_THRESHOLD && !state.notified) {
|
|
240
|
+
this.notifySummarizerDegraded(state.consecutiveFailures);
|
|
241
|
+
state.notified = true;
|
|
242
|
+
}
|
|
243
|
+
this.writeSummarizerHealth(state);
|
|
244
|
+
}
|
|
245
|
+
recordSummarizerSuccess() {
|
|
246
|
+
const state = this.readSummarizerHealth();
|
|
247
|
+
if (state.consecutiveFailures === 0 && !state.notified)
|
|
248
|
+
return;
|
|
249
|
+
this.writeSummarizerHealth({ consecutiveFailures: 0, notified: false });
|
|
250
|
+
}
|
|
251
|
+
notifySummarizerDegraded(streakCount) {
|
|
252
|
+
try {
|
|
253
|
+
(0, child_process_1.execSync)(`${CREWX_CLI} notify "AI memory summarization keeps failing β falling back to mechanical summaries" --level=warning --body="callSummarizer(agent=${this.summarizerAgent}) failed ${streakCount} times in a row, so summarization switched to the fallback (mechanical) path."`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15000 });
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
const err = e;
|
|
257
|
+
console.error(`[memory] failed to send summarizer-health notify: ${err.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
165
260
|
loadAllEntries(agentId) {
|
|
166
261
|
const entriesDir = this.getEntriesDir(agentId);
|
|
167
262
|
if (!fs_1.default.existsSync(entriesDir))
|
|
@@ -172,7 +267,7 @@ class MemoryEngine {
|
|
|
172
267
|
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
173
268
|
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
174
269
|
const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
175
|
-
const hasBody = Boolean(bodyText) && bodyText !== '(
|
|
270
|
+
const hasBody = Boolean(bodyText) && bodyText !== '(Add details here)';
|
|
176
271
|
const entry = data;
|
|
177
272
|
entries.push({ ...entry, file, hasBody });
|
|
178
273
|
}
|
|
@@ -217,16 +312,17 @@ class MemoryEngine {
|
|
|
217
312
|
});
|
|
218
313
|
const filename = `${id}.md`;
|
|
219
314
|
const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
|
|
220
|
-
const body = options.body ?? '(
|
|
315
|
+
const body = options.body ?? '(Add details here)';
|
|
221
316
|
const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
|
|
222
317
|
fs_1.default.writeFileSync(filepath, content, 'utf-8');
|
|
223
|
-
|
|
318
|
+
this.invalidateSearchIndex(agentId);
|
|
319
|
+
console.log(`β
Saved: data/${agentId}/entries/${filename}`);
|
|
224
320
|
console.log(` ID: ${id}`);
|
|
225
|
-
console.log(` Tags: ${tags.join(', ') || '(
|
|
321
|
+
console.log(` Tags: ${tags.join(', ') || '(none)'}`);
|
|
226
322
|
console.log(` Category: ${category}`);
|
|
227
323
|
try {
|
|
228
324
|
this.markSummaryDirty(agentId);
|
|
229
|
-
console.log(` π summary.md
|
|
325
|
+
console.log(` π summary.md update scheduled`);
|
|
230
326
|
}
|
|
231
327
|
catch (_e) {
|
|
232
328
|
}
|
|
@@ -252,11 +348,11 @@ class MemoryEngine {
|
|
|
252
348
|
}
|
|
253
349
|
const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
|
|
254
350
|
const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
|
|
255
|
-
console.log(`# ${agentId}
|
|
351
|
+
console.log(`# ${agentId} memory summary\n`);
|
|
256
352
|
const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
|
|
257
353
|
? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
|
|
258
354
|
: '-';
|
|
259
|
-
console.log(`> ${entries.length}
|
|
355
|
+
console.log(`> ${entries.length} items | topics ${topics.length} items | updated: ${summaryLastUpdated}`);
|
|
260
356
|
if (options.full) {
|
|
261
357
|
const byCat = {};
|
|
262
358
|
for (const entry of entries) {
|
|
@@ -278,7 +374,7 @@ class MemoryEngine {
|
|
|
278
374
|
return bImp - aImp;
|
|
279
375
|
return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
|
|
280
376
|
});
|
|
281
|
-
console.log(`## π₯
|
|
377
|
+
console.log(`## π₯ last Task (24h)\n`);
|
|
282
378
|
for (const e of sortedShortTerm) {
|
|
283
379
|
const star = e.important ? 'β ' : '';
|
|
284
380
|
console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
|
|
@@ -302,7 +398,7 @@ class MemoryEngine {
|
|
|
302
398
|
const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
|
|
303
399
|
const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
|
|
304
400
|
console.log('---');
|
|
305
|
-
console.log(`π
|
|
401
|
+
console.log(`π Topic summary (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays} days)\n`);
|
|
306
402
|
}
|
|
307
403
|
}
|
|
308
404
|
console.log(summaryBody.trim());
|
|
@@ -310,7 +406,7 @@ class MemoryEngine {
|
|
|
310
406
|
else {
|
|
311
407
|
for (const tag of topics.sort()) {
|
|
312
408
|
const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
|
|
313
|
-
console.log(`- **${tag}** (${count}
|
|
409
|
+
console.log(`- **${tag}** (${count} items)`);
|
|
314
410
|
}
|
|
315
411
|
}
|
|
316
412
|
}
|
|
@@ -343,15 +439,15 @@ class MemoryEngine {
|
|
|
343
439
|
byCategory[category] = [];
|
|
344
440
|
byCategory[category].push(entry);
|
|
345
441
|
}
|
|
346
|
-
let md = `# ${agentId}
|
|
442
|
+
let md = `# ${agentId} memory\n\n`;
|
|
347
443
|
md += `> Last Updated: ${today}\n`;
|
|
348
444
|
md += `> Total: ${entries.length} memories\n\n`;
|
|
349
|
-
md += `## π₯
|
|
445
|
+
md += `## π₯ Last ${this.recentDays} days\n\n`;
|
|
350
446
|
if (recent.length === 0) {
|
|
351
|
-
md += `(
|
|
447
|
+
md += `(none)\n\n`;
|
|
352
448
|
}
|
|
353
449
|
else {
|
|
354
|
-
md += `| ID |
|
|
450
|
+
md += `| ID | Date | Category | Summary | Details |\n`;
|
|
355
451
|
md += `|----|------|----------|------|------|\n`;
|
|
356
452
|
for (const entry of recent) {
|
|
357
453
|
const detailIcon = entry.hasBody ? 'π' : '-';
|
|
@@ -359,47 +455,43 @@ class MemoryEngine {
|
|
|
359
455
|
}
|
|
360
456
|
md += `\n`;
|
|
361
457
|
}
|
|
362
|
-
md += `## π·οΈ
|
|
458
|
+
md += `## π·οΈ By category\n\n`;
|
|
363
459
|
for (const [category, items] of Object.entries(byCategory).sort()) {
|
|
364
|
-
md += `- **${category}**: ${items.length}
|
|
460
|
+
md += `- **${category}**: ${items.length} items\n`;
|
|
365
461
|
}
|
|
366
462
|
const olderCount = entries.length - recent.length;
|
|
367
463
|
const oldestDate = entries[entries.length - 1]?.date ?? '-';
|
|
368
464
|
const newestDate = entries[0]?.date ?? '-';
|
|
369
|
-
md += `\n## π
|
|
370
|
-
md += `-
|
|
371
|
-
md += `-
|
|
465
|
+
md += `\n## π Stats\n\n`;
|
|
466
|
+
md += `- Total: ${entries.length} items (${oldestDate} ~ ${newestDate})\n`;
|
|
467
|
+
md += `- Last ${this.recentDays} days: ${recent.length} items\n`;
|
|
372
468
|
if (olderCount > 0)
|
|
373
|
-
md += `- ${this.recentDays}
|
|
469
|
+
md += `- Older than ${this.recentDays} days: ${olderCount} items\n`;
|
|
374
470
|
return md;
|
|
375
471
|
}
|
|
376
472
|
topic(agentId, topicName) {
|
|
377
473
|
const entries = this.loadAllEntries(agentId);
|
|
378
474
|
const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
|
|
379
475
|
if (filtered.length === 0) {
|
|
380
|
-
console.log(
|
|
476
|
+
console.log(`No memories found for topic '${topicName}'.`);
|
|
381
477
|
return;
|
|
382
478
|
}
|
|
383
|
-
console.log(`## π
|
|
479
|
+
console.log(`## π Topic: ${topicName} (${filtered.length} items)\n`);
|
|
384
480
|
for (const entry of filtered) {
|
|
385
481
|
console.log(`### [${entry.date}] ${entry.summary}`);
|
|
386
|
-
console.log(`-
|
|
387
|
-
console.log(`-
|
|
388
|
-
console.log(`-
|
|
482
|
+
console.log(`- File: data/${agentId}/entries/${entry.file}`);
|
|
483
|
+
console.log(`- Category: ${entry.category ?? '-'}`);
|
|
484
|
+
console.log(`- Tags: ${(entry.tags ?? []).join(', ') || '-'}`);
|
|
389
485
|
console.log('');
|
|
390
486
|
}
|
|
391
487
|
}
|
|
392
488
|
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);
|
|
489
|
+
const filtered = this.getRecentEntries(agentId, days);
|
|
398
490
|
if (filtered.length === 0) {
|
|
399
|
-
console.log(
|
|
491
|
+
console.log(`No memories found in the last ${days} days.`);
|
|
400
492
|
return;
|
|
401
493
|
}
|
|
402
|
-
console.log(`## π₯
|
|
494
|
+
console.log(`## π₯ Last ${days} days (${filtered.length} items)\n`);
|
|
403
495
|
for (const entry of filtered) {
|
|
404
496
|
const detailIcon = entry.hasBody ? ' π' : '';
|
|
405
497
|
const timestamp = String(entry.date).includes('T')
|
|
@@ -408,44 +500,75 @@ class MemoryEngine {
|
|
|
408
500
|
console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
|
|
409
501
|
}
|
|
410
502
|
}
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
503
|
+
getRecentEntries(agentId, days = 30, opts = {}) {
|
|
504
|
+
const entries = this.loadAllEntries(agentId);
|
|
505
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
|
|
506
|
+
.toISOString()
|
|
507
|
+
.slice(0, 10);
|
|
508
|
+
const filtered = entries.filter((e) => this.resolveRecentDate(agentId, e).slice(0, 10) >= cutoff);
|
|
509
|
+
if (!opts.includeBody)
|
|
510
|
+
return filtered;
|
|
511
|
+
return filtered.map((e) => ({
|
|
512
|
+
...e,
|
|
513
|
+
body: this.readEntryBody(agentId, e.file),
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
resolveRecentDate(agentId, entry) {
|
|
517
|
+
const updated = this.toDateString(entry.updated);
|
|
518
|
+
if (updated)
|
|
519
|
+
return updated;
|
|
520
|
+
const createdAt = this.toDateString(entry['created_at']);
|
|
521
|
+
if (createdAt)
|
|
522
|
+
return createdAt;
|
|
523
|
+
const date = this.toDateString(entry.date);
|
|
524
|
+
if (date)
|
|
525
|
+
return date;
|
|
526
|
+
try {
|
|
527
|
+
const filePath = path_1.default.join(this.getEntriesDir(agentId), entry.file);
|
|
528
|
+
return fs_1.default.statSync(filePath).mtime.toISOString();
|
|
416
529
|
}
|
|
417
|
-
|
|
418
|
-
|
|
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 });
|
|
530
|
+
catch {
|
|
531
|
+
return '';
|
|
424
532
|
}
|
|
425
|
-
|
|
426
|
-
|
|
533
|
+
}
|
|
534
|
+
toDateString(value) {
|
|
535
|
+
if (value &&
|
|
536
|
+
typeof value === 'object' &&
|
|
537
|
+
typeof value.toISOString === 'function') {
|
|
538
|
+
return value.toISOString();
|
|
539
|
+
}
|
|
540
|
+
return typeof value === 'string' ? value : '';
|
|
541
|
+
}
|
|
542
|
+
readEntryBody(agentId, file) {
|
|
543
|
+
try {
|
|
544
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), file), 'utf-8');
|
|
545
|
+
const { content } = (0, parser_1.parseFrontmatter)(raw);
|
|
546
|
+
return content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
return '';
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
find(agentId, keyword) {
|
|
553
|
+
const { files, index } = this.getSearchIndex(agentId);
|
|
554
|
+
if (files.length === 0) {
|
|
555
|
+
console.log('No memories found.');
|
|
427
556
|
return;
|
|
428
557
|
}
|
|
429
|
-
const
|
|
430
|
-
const
|
|
558
|
+
const indexedResults = index.search(keyword, FIND_RESULT_LIMIT + 1);
|
|
559
|
+
const truncated = indexedResults.length > FIND_RESULT_LIMIT;
|
|
560
|
+
const results = indexedResults.slice(0, FIND_RESULT_LIMIT);
|
|
431
561
|
if (results.length === 0) {
|
|
432
|
-
console.log(`'${keyword}'
|
|
562
|
+
console.log(`No related memories found for '${keyword}'.`);
|
|
433
563
|
return;
|
|
434
564
|
}
|
|
435
|
-
|
|
565
|
+
const limitNote = truncated ? `, top ${FIND_RESULT_LIMIT}` : '';
|
|
566
|
+
console.log(`## π Search results: "${keyword}" (${results.length} items${limitNote}, sorted by BM25 score)\n`);
|
|
436
567
|
for (const entry of results) {
|
|
437
|
-
const score = entry._score != null ? ` [${entry._score}
|
|
568
|
+
const score = entry._score != null ? ` [${entry._score}pts]` : '';
|
|
438
569
|
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
|
|
439
570
|
}
|
|
440
|
-
const
|
|
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
|
-
};
|
|
571
|
+
const findCtx = this.createFindActionContext(agentId, keyword, results);
|
|
449
572
|
const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
|
|
450
573
|
const findBlock = (0, knowledge_core_1.formatActions)(findActions);
|
|
451
574
|
if (findBlock)
|
|
@@ -470,7 +593,7 @@ class MemoryEngine {
|
|
|
470
593
|
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, e.file), 'utf-8');
|
|
471
594
|
const { content: rawContent } = (0, parser_1.parseFrontmatter)(raw);
|
|
472
595
|
const body = rawContent.replace(/^#[^\n]*\n*/m, '').trim();
|
|
473
|
-
if (body !== '(
|
|
596
|
+
if (body !== '(Add details here)')
|
|
474
597
|
content = body;
|
|
475
598
|
}
|
|
476
599
|
catch (_err) {
|
|
@@ -494,10 +617,10 @@ class MemoryEngine {
|
|
|
494
617
|
filters.push(`category=${options.category}`);
|
|
495
618
|
if (options.tag)
|
|
496
619
|
filters.push(`tag=${options.tag}`);
|
|
497
|
-
console.log(
|
|
620
|
+
console.log(`No memories match the filters. (${filters.join(', ') || 'all'})`);
|
|
498
621
|
return;
|
|
499
622
|
}
|
|
500
|
-
console.log(`## π
|
|
623
|
+
console.log(`## π List: ${agentId} (${filtered.length} items)\n`);
|
|
501
624
|
for (const entry of filtered) {
|
|
502
625
|
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
|
|
503
626
|
}
|
|
@@ -518,7 +641,7 @@ class MemoryEngine {
|
|
|
518
641
|
stats(agentId) {
|
|
519
642
|
const entries = this.loadAllEntries(agentId);
|
|
520
643
|
if (entries.length === 0) {
|
|
521
|
-
console.log('
|
|
644
|
+
console.log('No memories found.');
|
|
522
645
|
return;
|
|
523
646
|
}
|
|
524
647
|
const byCategory = {};
|
|
@@ -532,15 +655,15 @@ class MemoryEngine {
|
|
|
532
655
|
byTag[tag] = (byTag[tag] ?? 0) + 1;
|
|
533
656
|
}
|
|
534
657
|
}
|
|
535
|
-
console.log(`# ${agentId}
|
|
536
|
-
console.log(`##
|
|
658
|
+
console.log(`# ${agentId} Stats (${entries.length} items)\n`);
|
|
659
|
+
console.log(`## By category`);
|
|
537
660
|
for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
|
538
|
-
console.log(` ${cat}: ${count}
|
|
661
|
+
console.log(` ${cat}: ${count} items`);
|
|
539
662
|
}
|
|
540
|
-
console.log(`\n##
|
|
663
|
+
console.log(`\n## Top 20 Tags`);
|
|
541
664
|
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
|
542
665
|
for (const [tag, count] of sortedTags) {
|
|
543
|
-
console.log(` ${tag}: ${count}
|
|
666
|
+
console.log(` ${tag}: ${count} items`);
|
|
544
667
|
}
|
|
545
668
|
const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
|
|
546
669
|
const statsCtx = {
|
|
@@ -557,7 +680,7 @@ class MemoryEngine {
|
|
|
557
680
|
tags(agentId) {
|
|
558
681
|
const entries = this.loadAllEntries(agentId);
|
|
559
682
|
if (entries.length === 0) {
|
|
560
|
-
console.log('
|
|
683
|
+
console.log('No memories found.');
|
|
561
684
|
return;
|
|
562
685
|
}
|
|
563
686
|
const byTag = {};
|
|
@@ -568,18 +691,18 @@ class MemoryEngine {
|
|
|
568
691
|
}
|
|
569
692
|
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
|
|
570
693
|
if (sortedTags.length === 0) {
|
|
571
|
-
console.log('
|
|
694
|
+
console.log('No tags found.');
|
|
572
695
|
return;
|
|
573
696
|
}
|
|
574
|
-
console.log(`## π·οΈ
|
|
697
|
+
console.log(`## π·οΈ All tags (${sortedTags.length} items)\n`);
|
|
575
698
|
for (const [tag, count] of sortedTags) {
|
|
576
|
-
console.log(` ${tag}: ${count}
|
|
699
|
+
console.log(` ${tag}: ${count} items`);
|
|
577
700
|
}
|
|
578
701
|
}
|
|
579
702
|
get(agentId, memoryId) {
|
|
580
703
|
const entriesDir = this.getEntriesDir(agentId);
|
|
581
704
|
if (!fs_1.default.existsSync(entriesDir)) {
|
|
582
|
-
console.log('
|
|
705
|
+
console.log('No memories found.');
|
|
583
706
|
return;
|
|
584
707
|
}
|
|
585
708
|
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
@@ -591,10 +714,10 @@ class MemoryEngine {
|
|
|
591
714
|
const d = data;
|
|
592
715
|
console.log(`## π ${d.summary}\n`);
|
|
593
716
|
console.log(`- ID: ${d.id}`);
|
|
594
|
-
console.log(`-
|
|
595
|
-
console.log(`-
|
|
596
|
-
console.log(`-
|
|
597
|
-
console.log(`-
|
|
717
|
+
console.log(`- Date: ${d.date}`);
|
|
718
|
+
console.log(`- Category: ${d.category ?? '-'}`);
|
|
719
|
+
console.log(`- Tags: ${(d.tags ?? []).join(', ') || '-'}`);
|
|
720
|
+
console.log(`- File: data/${agentId}/entries/${file}`);
|
|
598
721
|
console.log(`\n---\n`);
|
|
599
722
|
console.log(content.trim());
|
|
600
723
|
const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
|
|
@@ -611,7 +734,7 @@ class MemoryEngine {
|
|
|
611
734
|
nodeMap[n.id] = n;
|
|
612
735
|
});
|
|
613
736
|
console.log(`\n---\n`);
|
|
614
|
-
console.log(`## π
|
|
737
|
+
console.log(`## π Related memories (${relatedEdges.length} items)\n`);
|
|
615
738
|
for (const edge of relatedEdges) {
|
|
616
739
|
const otherId = edge.from === memoryId ? edge.to : edge.from;
|
|
617
740
|
const other = nodeMap[otherId];
|
|
@@ -638,12 +761,12 @@ class MemoryEngine {
|
|
|
638
761
|
return;
|
|
639
762
|
}
|
|
640
763
|
}
|
|
641
|
-
console.log(`ID '${memoryId}'
|
|
764
|
+
console.log(`ID '${memoryId}' not found.`);
|
|
642
765
|
}
|
|
643
766
|
update(agentId, memoryId, options = {}) {
|
|
644
767
|
const entry = this.findEntryById(agentId, memoryId);
|
|
645
768
|
if (!entry) {
|
|
646
|
-
console.log(`β ID '${memoryId}'
|
|
769
|
+
console.log(`β ID '${memoryId}' not found.`);
|
|
647
770
|
process.exit(1);
|
|
648
771
|
}
|
|
649
772
|
const { filePath, data, content } = entry;
|
|
@@ -664,11 +787,12 @@ class MemoryEngine {
|
|
|
664
787
|
else if (options['no-important']) {
|
|
665
788
|
delete newData['important'];
|
|
666
789
|
}
|
|
667
|
-
const newBody = options.body ?? existingBody ?? '(
|
|
790
|
+
const newBody = options.body ?? existingBody ?? '(Add details here)';
|
|
668
791
|
const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
|
|
669
792
|
const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
|
|
670
793
|
fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
|
|
671
|
-
|
|
794
|
+
this.invalidateSearchIndex(agentId);
|
|
795
|
+
console.log(`β
Updated: ${entry.file}`);
|
|
672
796
|
console.log(` ID: ${memoryId}`);
|
|
673
797
|
if (options.summary)
|
|
674
798
|
console.log(` Summary: ${String(newData['summary'])}`);
|
|
@@ -677,28 +801,29 @@ class MemoryEngine {
|
|
|
677
801
|
if (options.tags)
|
|
678
802
|
console.log(` Tags: ${newData['tags'].join(', ')}`);
|
|
679
803
|
if (options.body)
|
|
680
|
-
console.log(` Body: (
|
|
804
|
+
console.log(` Body: (updated)`);
|
|
681
805
|
}
|
|
682
806
|
delete(agentId, memoryId, options = {}) {
|
|
683
807
|
const entry = this.findEntryById(agentId, memoryId);
|
|
684
808
|
if (!entry) {
|
|
685
|
-
console.log(`β ID '${memoryId}'
|
|
809
|
+
console.log(`β ID '${memoryId}' not found.`);
|
|
686
810
|
process.exit(1);
|
|
687
811
|
}
|
|
688
812
|
if (!options.force) {
|
|
689
|
-
console.log(`β οΈ
|
|
690
|
-
console.log(`
|
|
691
|
-
console.log(`\
|
|
692
|
-
console.log(
|
|
813
|
+
console.log(`β οΈ Delete target: [${entry.data.id}] ${entry.data.summary}`);
|
|
814
|
+
console.log(` File: ${entry.file}`);
|
|
815
|
+
console.log(`\nAdd --force to delete it.`);
|
|
816
|
+
console.log(`Example: memory delete ${agentId} ${memoryId} --force`);
|
|
693
817
|
return;
|
|
694
818
|
}
|
|
695
819
|
fs_1.default.unlinkSync(entry.filePath);
|
|
696
|
-
|
|
820
|
+
this.invalidateSearchIndex(agentId);
|
|
821
|
+
console.log(`ποΈ Deleted: ${entry.file}`);
|
|
697
822
|
console.log(` ID: ${memoryId}`);
|
|
698
823
|
console.log(` Summary: ${entry.data.summary}`);
|
|
699
824
|
try {
|
|
700
825
|
this.markSummaryDirty(agentId);
|
|
701
|
-
console.log(` π summary.md
|
|
826
|
+
console.log(` π summary.md update scheduled`);
|
|
702
827
|
}
|
|
703
828
|
catch (_e) {
|
|
704
829
|
}
|
|
@@ -707,27 +832,27 @@ class MemoryEngine {
|
|
|
707
832
|
const entry1 = this.findEntryById(agentId, memoryId1);
|
|
708
833
|
const entry2 = this.findEntryById(agentId, memoryId2);
|
|
709
834
|
if (!entry1) {
|
|
710
|
-
console.log(`β ID '${memoryId1}'
|
|
835
|
+
console.log(`β ID '${memoryId1}' not found.`);
|
|
711
836
|
process.exit(1);
|
|
712
837
|
}
|
|
713
838
|
if (!entry2) {
|
|
714
|
-
console.log(`β ID '${memoryId2}'
|
|
839
|
+
console.log(`β ID '${memoryId2}' not found.`);
|
|
715
840
|
process.exit(1);
|
|
716
841
|
}
|
|
717
842
|
const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
718
843
|
const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
719
844
|
const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
|
|
720
|
-
const mergedBody = `##
|
|
845
|
+
const mergedBody = `## Merged memory 1 (${entry1.data.id}, ${entry1.data.date})
|
|
721
846
|
${entry1.data.summary}
|
|
722
847
|
|
|
723
|
-
${body1 !== '(
|
|
848
|
+
${body1 !== '(Add details here)' ? body1 : '(No details)'}
|
|
724
849
|
|
|
725
850
|
---
|
|
726
851
|
|
|
727
|
-
##
|
|
852
|
+
## Merged memory 2 (${entry2.data.id}, ${entry2.data.date})
|
|
728
853
|
${entry2.data.summary}
|
|
729
854
|
|
|
730
|
-
${body2 !== '(
|
|
855
|
+
${body2 !== '(Add details here)' ? body2 : '(No details)'}`;
|
|
731
856
|
const mergedTags = [
|
|
732
857
|
...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
|
|
733
858
|
];
|
|
@@ -749,10 +874,11 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
749
874
|
fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
|
|
750
875
|
fs_1.default.unlinkSync(entry1.filePath);
|
|
751
876
|
fs_1.default.unlinkSync(entry2.filePath);
|
|
752
|
-
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(`
|
|
755
|
-
console.log(`
|
|
877
|
+
this.invalidateSearchIndex(agentId);
|
|
878
|
+
console.log(`π Merge complete!`);
|
|
879
|
+
console.log(` New ID: ${newId}`);
|
|
880
|
+
console.log(` New file: ${filename}`);
|
|
881
|
+
console.log(` Merged memory: [${memoryId1}] + [${memoryId2}]`);
|
|
756
882
|
console.log(` Summary: ${mergedSummary}`);
|
|
757
883
|
}
|
|
758
884
|
buildFallbackSummary(entries) {
|
|
@@ -778,7 +904,7 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
778
904
|
const oldest = dates[0];
|
|
779
905
|
const newest = dates[dates.length - 1];
|
|
780
906
|
const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
|
|
781
|
-
body += `## ${topic} (${items.length}
|
|
907
|
+
body += `## ${topic} (${items.length} items${rangeLabel})\n`;
|
|
782
908
|
for (const e of items)
|
|
783
909
|
body += `- [${e.date}] ${e.summary}\n`;
|
|
784
910
|
for (const e of items)
|
|
@@ -788,22 +914,26 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
788
914
|
return body;
|
|
789
915
|
}
|
|
790
916
|
callSummarizer(prompt) {
|
|
791
|
-
const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
|
|
792
917
|
try {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
918
|
+
const result = (0, child_process_1.execSync)(`${CREWX_CLI} q "${this.summarizerAgent}"`, {
|
|
919
|
+
encoding: 'utf-8',
|
|
920
|
+
timeout: this.summarizerTimeout,
|
|
921
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
922
|
+
input: prompt,
|
|
923
|
+
});
|
|
924
|
+
const parsed = parseCrewxResponse(result);
|
|
925
|
+
this.recordSummarizerSuccess();
|
|
926
|
+
return parsed;
|
|
927
|
+
}
|
|
928
|
+
catch (e) {
|
|
929
|
+
const err = e;
|
|
930
|
+
const reason = err.code === 'ETIMEDOUT' || err.killed
|
|
931
|
+
? `timed out after ${this.summarizerTimeout}ms`
|
|
932
|
+
: err.message;
|
|
933
|
+
console.error(`[memory] callSummarizer failed (agent=${this.summarizerAgent}), falling back to raw entry list: ${reason}`);
|
|
934
|
+
this.recordSummarizerFailure();
|
|
798
935
|
return null;
|
|
799
936
|
}
|
|
800
|
-
finally {
|
|
801
|
-
try {
|
|
802
|
-
fs_1.default.unlinkSync(tmpFile);
|
|
803
|
-
}
|
|
804
|
-
catch (_e) {
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
937
|
}
|
|
808
938
|
generateSummary(agentId, options = {}) {
|
|
809
939
|
const allEntries = this.loadAllEntries(agentId);
|
|
@@ -861,13 +991,13 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
861
991
|
for (const entry of newItems) {
|
|
862
992
|
const importantTag = entry.important ? ' [important]' : '';
|
|
863
993
|
p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
|
|
864
|
-
if (entry.body && entry.body !== '(
|
|
994
|
+
if (entry.body && entry.body !== '(Add details here)') {
|
|
865
995
|
p += ` β ${entry.body.slice(0, 200)}`;
|
|
866
996
|
}
|
|
867
997
|
p += ` (file: entries/${entry.file})\n`;
|
|
868
998
|
}
|
|
869
999
|
p += `\n## Instructions:\n`;
|
|
870
|
-
p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4
|
|
1000
|
+
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
1001
|
p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
|
|
872
1002
|
p += `- After each topic summary, list relevant entry file links as "β [entries/filename](entries/filename)"\n`;
|
|
873
1003
|
p += `- Highlight key decisions, insights, and action items\n`;
|
|
@@ -917,76 +1047,141 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
917
1047
|
summarize(agentId, options = {}) {
|
|
918
1048
|
const entries = this.loadAllEntries(agentId);
|
|
919
1049
|
if (entries.length === 0) {
|
|
920
|
-
console.log('
|
|
1050
|
+
console.log('No memories found.');
|
|
921
1051
|
return;
|
|
922
1052
|
}
|
|
923
|
-
console.log(`π
|
|
1053
|
+
console.log(`π Starting summary generation (${entries.length} items entries)\n`);
|
|
924
1054
|
const result = this.generateSummary(agentId, { force: options.force });
|
|
925
1055
|
if (result.status === 'up-to-date') {
|
|
926
|
-
console.log('β
summary.md
|
|
1056
|
+
console.log('β
summary.md is up to date.');
|
|
927
1057
|
}
|
|
928
1058
|
else if (result.status === 'fallback') {
|
|
929
|
-
console.log(`π
|
|
1059
|
+
console.log(`π Fallback summary generated (AI call failed)`);
|
|
930
1060
|
}
|
|
931
1061
|
else if (result.status === 'updated') {
|
|
932
|
-
console.log(`π€
|
|
1062
|
+
console.log(`π€ Summary complete (${result.newEntries} items applied)`);
|
|
1063
|
+
}
|
|
1064
|
+
console.log(` File: data/${agentId}/summary.md`);
|
|
1065
|
+
console.log(` entries: ${result.entryCount} items`);
|
|
1066
|
+
}
|
|
1067
|
+
isSummarizeLockStale(lockPath) {
|
|
1068
|
+
let content;
|
|
1069
|
+
try {
|
|
1070
|
+
content = fs_1.default.readFileSync(lockPath, 'utf-8');
|
|
1071
|
+
}
|
|
1072
|
+
catch {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
const pidMatch = content.match(/^pid=(\d+)/m);
|
|
1076
|
+
const pid = pidMatch ? Number(pidMatch[1]) : NaN;
|
|
1077
|
+
if (Number.isFinite(pid)) {
|
|
1078
|
+
try {
|
|
1079
|
+
process.kill(pid, 0);
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
catch (e) {
|
|
1083
|
+
const err = e;
|
|
1084
|
+
if (err.code === 'ESRCH')
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
const stat = fs_1.default.statSync(lockPath);
|
|
1090
|
+
return Date.now() - stat.mtimeMs > exports.SUMMARIZE_LOCK_STALE_MS;
|
|
1091
|
+
}
|
|
1092
|
+
catch {
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
acquireSummarizeLock() {
|
|
1097
|
+
const lockPath = this.getSummarizeLockPath();
|
|
1098
|
+
const content = `pid=${process.pid}\nmtime=${new Date().toISOString()}\n`;
|
|
1099
|
+
try {
|
|
1100
|
+
const fd = fs_1.default.openSync(lockPath, 'wx');
|
|
1101
|
+
fs_1.default.writeSync(fd, content);
|
|
1102
|
+
fs_1.default.closeSync(fd);
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
catch (e) {
|
|
1106
|
+
const err = e;
|
|
1107
|
+
if (err.code !== 'EEXIST')
|
|
1108
|
+
throw err;
|
|
1109
|
+
}
|
|
1110
|
+
if (!this.isSummarizeLockStale(lockPath)) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
fs_1.default.writeFileSync(lockPath, content, 'utf-8');
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
releaseSummarizeLock() {
|
|
1117
|
+
try {
|
|
1118
|
+
fs_1.default.unlinkSync(this.getSummarizeLockPath());
|
|
1119
|
+
}
|
|
1120
|
+
catch {
|
|
933
1121
|
}
|
|
934
|
-
console.log(` νμΌ: data/${agentId}/summary.md`);
|
|
935
|
-
console.log(` μνΈλ¦¬: ${result.entryCount}κ°`);
|
|
936
1122
|
}
|
|
937
1123
|
summarizeDirty() {
|
|
938
1124
|
if (!fs_1.default.existsSync(this.dataDir)) {
|
|
939
|
-
console.log(`π‘
|
|
1125
|
+
console.log(`π‘ Register debounce cron:`);
|
|
940
1126
|
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
941
1127
|
return;
|
|
942
1128
|
}
|
|
943
|
-
|
|
944
|
-
.
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1129
|
+
if (!this.acquireSummarizeLock()) {
|
|
1130
|
+
console.log(`βοΈ summarize-dirty is already running (lock held) β skipping this run.`);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
const agentDirs = fs_1.default
|
|
1135
|
+
.readdirSync(this.dataDir, { withFileTypes: true })
|
|
1136
|
+
.filter((entry) => entry.isDirectory())
|
|
1137
|
+
.map((entry) => entry.name);
|
|
1138
|
+
for (const agentId of agentDirs) {
|
|
1139
|
+
const dirtyPath = this.getDirtySummaryPath(agentId);
|
|
1140
|
+
if (!fs_1.default.existsSync(dirtyPath))
|
|
1141
|
+
continue;
|
|
1142
|
+
try {
|
|
1143
|
+
const result = this.generateSummary(agentId);
|
|
1144
|
+
if (result.status === 'updated') {
|
|
1145
|
+
console.log(`π€ ${agentId} summary.md updated (${result.newEntries} items applied)`);
|
|
1146
|
+
}
|
|
1147
|
+
else if (result.status === 'fallback') {
|
|
1148
|
+
console.log(`π ${agentId} Fallback summary generated (AI call failed)`);
|
|
1149
|
+
}
|
|
1150
|
+
else if (result.status === 'up-to-date') {
|
|
1151
|
+
console.log(`β
${agentId} summary.md is up to date.`);
|
|
1152
|
+
}
|
|
1153
|
+
else if (result.status === 'empty') {
|
|
1154
|
+
console.log(`βΉοΈ ${agentId} has no memories to summarize.`);
|
|
1155
|
+
}
|
|
1156
|
+
fs_1.default.unlinkSync(dirtyPath);
|
|
961
1157
|
}
|
|
962
|
-
|
|
963
|
-
console.
|
|
1158
|
+
catch (e) {
|
|
1159
|
+
console.error(`β ${agentId} summary.md update failed: ${e.message}`);
|
|
964
1160
|
}
|
|
965
|
-
fs_1.default.unlinkSync(dirtyPath);
|
|
966
|
-
}
|
|
967
|
-
catch (e) {
|
|
968
|
-
console.error(`β ${agentId} summary.md κ°±μ μ€ν¨: ${e.message}`);
|
|
969
1161
|
}
|
|
970
1162
|
}
|
|
971
|
-
|
|
1163
|
+
finally {
|
|
1164
|
+
this.releaseSummarizeLock();
|
|
1165
|
+
}
|
|
1166
|
+
console.log(`π‘ Register debounce cron:`);
|
|
972
1167
|
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
973
1168
|
}
|
|
974
1169
|
search(agentId, query) {
|
|
975
1170
|
const entries = this.loadAllEntries(agentId);
|
|
976
1171
|
if (entries.length === 0) {
|
|
977
|
-
console.log('
|
|
1172
|
+
console.log('No memories found.');
|
|
978
1173
|
return;
|
|
979
1174
|
}
|
|
980
1175
|
const memoryList = entries
|
|
981
1176
|
.map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
|
|
982
1177
|
.join('\n');
|
|
983
|
-
const task =
|
|
1178
|
+
const task = `Memory list:\n${memoryList}\n\nQuestion: "${query}"`;
|
|
984
1179
|
try {
|
|
985
|
-
console.log(`π "${query}"
|
|
1180
|
+
console.log(`π "${query}" searching... (${this.searcherAgent})\n`);
|
|
986
1181
|
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
1182
|
const parsed = parseCrewxResponse(result);
|
|
988
1183
|
if (parsed) {
|
|
989
|
-
console.log(`## π§
|
|
1184
|
+
console.log(`## π§ Semantic search results\n`);
|
|
990
1185
|
console.log(parsed);
|
|
991
1186
|
}
|
|
992
1187
|
else {
|
|
@@ -996,12 +1191,12 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
996
1191
|
catch (error) {
|
|
997
1192
|
const err = error;
|
|
998
1193
|
if (err.code === 'ETIMEDOUT' || err.killed) {
|
|
999
|
-
console.log('
|
|
1194
|
+
console.log('Search failed: timed out (max 5 minutes)');
|
|
1000
1195
|
}
|
|
1001
1196
|
else {
|
|
1002
|
-
console.log('
|
|
1197
|
+
console.log('Search failed:', err.message);
|
|
1003
1198
|
}
|
|
1004
|
-
console.log('\nπ‘ Tip:
|
|
1199
|
+
console.log('\nπ‘ Tip: try keyword search with the find command.');
|
|
1005
1200
|
}
|
|
1006
1201
|
}
|
|
1007
1202
|
}
|