@crewx/memory 0.1.23-rc.14 β 0.1.23-rc.140
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 +400 -162
- 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 +4 -3
package/dist/src/engine.js
CHANGED
|
@@ -3,18 +3,57 @@ 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
|
-
const
|
|
16
|
+
const sdk_1 = require("@crewx/sdk");
|
|
17
|
+
exports.SUMMARIZER_FAILURE_THRESHOLD = 3;
|
|
18
|
+
const SUMMARIZER_HEALTH_FILE = '.summarizer-health.json';
|
|
19
|
+
const FIND_RESULT_LIMIT = 50;
|
|
20
|
+
function resolveCrewxInvocation() {
|
|
21
|
+
const resolution = (0, sdk_1.resolveCrewxExecutable)({
|
|
22
|
+
env: process.env,
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
});
|
|
25
|
+
if (!resolution.ok) {
|
|
26
|
+
const error = new Error((0, sdk_1.formatCrewxExecutableFailure)(resolution));
|
|
27
|
+
Object.assign(error, { code: 'CLI_UNRESOLVABLE' });
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const childInvocation = (0, sdk_1.createCrewxChildInvocation)(resolution, { baseEnv: process.env });
|
|
31
|
+
const program = (0, sdk_1.resolveWindowsSpawnProgram)({
|
|
32
|
+
command: childInvocation.command,
|
|
33
|
+
env: childInvocation.env,
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
packageName: 'crewx',
|
|
36
|
+
allowShellFallback: true,
|
|
37
|
+
});
|
|
38
|
+
const invocation = (0, sdk_1.materializeWindowsSpawnProgram)(program, childInvocation.args);
|
|
39
|
+
return {
|
|
40
|
+
command: invocation.command,
|
|
41
|
+
args: invocation.argv,
|
|
42
|
+
env: childInvocation.env,
|
|
43
|
+
shell: invocation.shell,
|
|
44
|
+
windowsHide: invocation.windowsHide,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function execCrewx(args, options) {
|
|
48
|
+
const invocation = resolveCrewxInvocation();
|
|
49
|
+
return (0, child_process_1.execFileSync)(invocation.command, [...invocation.args, ...args], {
|
|
50
|
+
...options,
|
|
51
|
+
env: invocation.env,
|
|
52
|
+
...(invocation.shell === undefined ? {} : { shell: invocation.shell }),
|
|
53
|
+
...(invocation.windowsHide === undefined ? {} : { windowsHide: invocation.windowsHide }),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
exports.SUMMARIZE_LOCK_STALE_MS = 10 * 60 * 1000;
|
|
18
57
|
function parseCrewxResponse(raw) {
|
|
19
58
|
const responseMatch = raw.match(/π Response:\s*β+\s*([\s\S]*?)(?=π Working Directory|$)/);
|
|
20
59
|
if (responseMatch?.[1]) {
|
|
@@ -75,6 +114,7 @@ function getMemoryConfig() {
|
|
|
75
114
|
}
|
|
76
115
|
class MemoryEngine {
|
|
77
116
|
constructor(config = {}) {
|
|
117
|
+
this.searchIndexCache = new Map();
|
|
78
118
|
const yamlConfig = getMemoryConfig();
|
|
79
119
|
this.dataDir =
|
|
80
120
|
config.dataDir ??
|
|
@@ -103,7 +143,7 @@ class MemoryEngine {
|
|
|
103
143
|
this.summarizerTimeout =
|
|
104
144
|
config.summarizerTimeout ??
|
|
105
145
|
(yamlConfig.summarizer_timeout != null ? Number(yamlConfig.summarizer_timeout) : undefined) ??
|
|
106
|
-
|
|
146
|
+
480000;
|
|
107
147
|
this.searcherTimeout =
|
|
108
148
|
config.searcherTimeout ??
|
|
109
149
|
(yamlConfig.searcher_timeout != null ? Number(yamlConfig.searcher_timeout) : undefined) ??
|
|
@@ -115,12 +155,59 @@ class MemoryEngine {
|
|
|
115
155
|
getEntriesDir(agentId) {
|
|
116
156
|
return path_1.default.join(this.getAgentDir(agentId), 'entries');
|
|
117
157
|
}
|
|
158
|
+
getEntryFilesFingerprint(agentId) {
|
|
159
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
160
|
+
if (!fs_1.default.existsSync(entriesDir))
|
|
161
|
+
return { files: [], fingerprint: '' };
|
|
162
|
+
const files = fs_1.default.readdirSync(entriesDir)
|
|
163
|
+
.filter((file) => file.endsWith('.md'))
|
|
164
|
+
.sort();
|
|
165
|
+
const fingerprint = files.map((file) => {
|
|
166
|
+
const stat = fs_1.default.statSync(path_1.default.join(entriesDir, file));
|
|
167
|
+
return `${file}\u0000${stat.size}\u0000${stat.mtimeMs}`;
|
|
168
|
+
}).join('\u0001');
|
|
169
|
+
return { files, fingerprint };
|
|
170
|
+
}
|
|
171
|
+
buildSearchIndex(agentId, files) {
|
|
172
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
173
|
+
const entries = files.map((file) => {
|
|
174
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
175
|
+
const { data, content: body } = (0, parser_1.parseFrontmatter)(raw);
|
|
176
|
+
return { ...data, file, body };
|
|
177
|
+
});
|
|
178
|
+
return (0, knowledge_core_1.createSearchIndex)(entries, { summary: 3, tags: 2, body: 1 });
|
|
179
|
+
}
|
|
180
|
+
getSearchIndex(agentId) {
|
|
181
|
+
const snapshot = this.getEntryFilesFingerprint(agentId);
|
|
182
|
+
const cached = this.searchIndexCache.get(agentId);
|
|
183
|
+
if (cached?.fingerprint === snapshot.fingerprint) {
|
|
184
|
+
return { files: snapshot.files, index: cached.index };
|
|
185
|
+
}
|
|
186
|
+
const index = this.buildSearchIndex(agentId, snapshot.files);
|
|
187
|
+
this.searchIndexCache.set(agentId, { fingerprint: snapshot.fingerprint, index });
|
|
188
|
+
return { files: snapshot.files, index };
|
|
189
|
+
}
|
|
190
|
+
invalidateSearchIndex(agentId) {
|
|
191
|
+
this.searchIndexCache.delete(agentId);
|
|
192
|
+
}
|
|
193
|
+
createFindActionContext(agentId, keyword, results) {
|
|
194
|
+
return {
|
|
195
|
+
commandId: 'memory.find',
|
|
196
|
+
params: { agentId, query: keyword },
|
|
197
|
+
resultTags: [...new Set(results.flatMap((entry) => entry.tags ?? []))].slice(0, 3),
|
|
198
|
+
resultIds: results.slice(0, 3).map((entry) => entry.id),
|
|
199
|
+
resultCount: results.length,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
118
202
|
getSummaryPath(agentId) {
|
|
119
203
|
return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
|
|
120
204
|
}
|
|
121
205
|
getDirtySummaryPath(agentId) {
|
|
122
206
|
return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
|
|
123
207
|
}
|
|
208
|
+
getSummarizeLockPath() {
|
|
209
|
+
return path_1.default.join(this.dataDir, '.summarize.lock');
|
|
210
|
+
}
|
|
124
211
|
ensureDir(dir) {
|
|
125
212
|
if (!fs_1.default.existsSync(dir)) {
|
|
126
213
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
@@ -162,6 +249,57 @@ class MemoryEngine {
|
|
|
162
249
|
this.ensureDir(this.getAgentDir(agentId));
|
|
163
250
|
fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
|
|
164
251
|
}
|
|
252
|
+
getSummarizerHealthPath() {
|
|
253
|
+
return path_1.default.join(this.dataDir, SUMMARIZER_HEALTH_FILE);
|
|
254
|
+
}
|
|
255
|
+
readSummarizerHealth() {
|
|
256
|
+
try {
|
|
257
|
+
const raw = fs_1.default.readFileSync(this.getSummarizerHealthPath(), 'utf-8');
|
|
258
|
+
const parsed = JSON.parse(raw);
|
|
259
|
+
return {
|
|
260
|
+
consecutiveFailures: typeof parsed.consecutiveFailures === 'number' ? parsed.consecutiveFailures : 0,
|
|
261
|
+
notified: Boolean(parsed.notified),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return { consecutiveFailures: 0, notified: false };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
writeSummarizerHealth(state) {
|
|
269
|
+
this.ensureDir(this.dataDir);
|
|
270
|
+
fs_1.default.writeFileSync(this.getSummarizerHealthPath(), JSON.stringify(state), 'utf-8');
|
|
271
|
+
}
|
|
272
|
+
recordSummarizerFailure() {
|
|
273
|
+
const state = this.readSummarizerHealth();
|
|
274
|
+
state.consecutiveFailures += 1;
|
|
275
|
+
if (state.consecutiveFailures >= exports.SUMMARIZER_FAILURE_THRESHOLD && !state.notified) {
|
|
276
|
+
this.notifySummarizerDegraded(state.consecutiveFailures);
|
|
277
|
+
state.notified = true;
|
|
278
|
+
}
|
|
279
|
+
this.writeSummarizerHealth(state);
|
|
280
|
+
}
|
|
281
|
+
recordSummarizerSuccess() {
|
|
282
|
+
const state = this.readSummarizerHealth();
|
|
283
|
+
if (state.consecutiveFailures === 0 && !state.notified)
|
|
284
|
+
return;
|
|
285
|
+
this.writeSummarizerHealth({ consecutiveFailures: 0, notified: false });
|
|
286
|
+
}
|
|
287
|
+
notifySummarizerDegraded(streakCount) {
|
|
288
|
+
try {
|
|
289
|
+
execCrewx([
|
|
290
|
+
'notify',
|
|
291
|
+
'AI memory summarization keeps failing β falling back to mechanical summaries',
|
|
292
|
+
'--level=warning',
|
|
293
|
+
'--body=callSummarizer(agent=' + this.summarizerAgent +
|
|
294
|
+
') failed ' + streakCount +
|
|
295
|
+
' times in a row, so summarization switched to the fallback (mechanical) path.',
|
|
296
|
+
], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15000 });
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
const err = e;
|
|
300
|
+
console.error(`[memory] failed to send summarizer-health notify: ${err.message}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
165
303
|
loadAllEntries(agentId) {
|
|
166
304
|
const entriesDir = this.getEntriesDir(agentId);
|
|
167
305
|
if (!fs_1.default.existsSync(entriesDir))
|
|
@@ -172,7 +310,7 @@ class MemoryEngine {
|
|
|
172
310
|
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
173
311
|
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
174
312
|
const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
175
|
-
const hasBody = Boolean(bodyText) && bodyText !== '(
|
|
313
|
+
const hasBody = Boolean(bodyText) && bodyText !== '(Add details here)';
|
|
176
314
|
const entry = data;
|
|
177
315
|
entries.push({ ...entry, file, hasBody });
|
|
178
316
|
}
|
|
@@ -217,16 +355,17 @@ class MemoryEngine {
|
|
|
217
355
|
});
|
|
218
356
|
const filename = `${id}.md`;
|
|
219
357
|
const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
|
|
220
|
-
const body = options.body ?? '(
|
|
358
|
+
const body = options.body ?? '(Add details here)';
|
|
221
359
|
const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
|
|
222
360
|
fs_1.default.writeFileSync(filepath, content, 'utf-8');
|
|
223
|
-
|
|
361
|
+
this.invalidateSearchIndex(agentId);
|
|
362
|
+
console.log(`β
Saved: data/${agentId}/entries/${filename}`);
|
|
224
363
|
console.log(` ID: ${id}`);
|
|
225
|
-
console.log(` Tags: ${tags.join(', ') || '(
|
|
364
|
+
console.log(` Tags: ${tags.join(', ') || '(none)'}`);
|
|
226
365
|
console.log(` Category: ${category}`);
|
|
227
366
|
try {
|
|
228
367
|
this.markSummaryDirty(agentId);
|
|
229
|
-
console.log(` π summary.md
|
|
368
|
+
console.log(` π summary.md update scheduled`);
|
|
230
369
|
}
|
|
231
370
|
catch (_e) {
|
|
232
371
|
}
|
|
@@ -252,11 +391,11 @@ class MemoryEngine {
|
|
|
252
391
|
}
|
|
253
392
|
const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
|
|
254
393
|
const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
|
|
255
|
-
console.log(`# ${agentId}
|
|
394
|
+
console.log(`# ${agentId} memory summary\n`);
|
|
256
395
|
const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
|
|
257
396
|
? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
|
|
258
397
|
: '-';
|
|
259
|
-
console.log(`> ${entries.length}
|
|
398
|
+
console.log(`> ${entries.length} items | topics ${topics.length} items | updated: ${summaryLastUpdated}`);
|
|
260
399
|
if (options.full) {
|
|
261
400
|
const byCat = {};
|
|
262
401
|
for (const entry of entries) {
|
|
@@ -278,7 +417,7 @@ class MemoryEngine {
|
|
|
278
417
|
return bImp - aImp;
|
|
279
418
|
return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
|
|
280
419
|
});
|
|
281
|
-
console.log(`## π₯
|
|
420
|
+
console.log(`## π₯ last Task (24h)\n`);
|
|
282
421
|
for (const e of sortedShortTerm) {
|
|
283
422
|
const star = e.important ? 'β ' : '';
|
|
284
423
|
console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
|
|
@@ -302,7 +441,7 @@ class MemoryEngine {
|
|
|
302
441
|
const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
|
|
303
442
|
const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
|
|
304
443
|
console.log('---');
|
|
305
|
-
console.log(`π
|
|
444
|
+
console.log(`π Topic summary (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays} days)\n`);
|
|
306
445
|
}
|
|
307
446
|
}
|
|
308
447
|
console.log(summaryBody.trim());
|
|
@@ -310,7 +449,7 @@ class MemoryEngine {
|
|
|
310
449
|
else {
|
|
311
450
|
for (const tag of topics.sort()) {
|
|
312
451
|
const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
|
|
313
|
-
console.log(`- **${tag}** (${count}
|
|
452
|
+
console.log(`- **${tag}** (${count} items)`);
|
|
314
453
|
}
|
|
315
454
|
}
|
|
316
455
|
}
|
|
@@ -343,15 +482,15 @@ class MemoryEngine {
|
|
|
343
482
|
byCategory[category] = [];
|
|
344
483
|
byCategory[category].push(entry);
|
|
345
484
|
}
|
|
346
|
-
let md = `# ${agentId}
|
|
485
|
+
let md = `# ${agentId} memory\n\n`;
|
|
347
486
|
md += `> Last Updated: ${today}\n`;
|
|
348
487
|
md += `> Total: ${entries.length} memories\n\n`;
|
|
349
|
-
md += `## π₯
|
|
488
|
+
md += `## π₯ Last ${this.recentDays} days\n\n`;
|
|
350
489
|
if (recent.length === 0) {
|
|
351
|
-
md += `(
|
|
490
|
+
md += `(none)\n\n`;
|
|
352
491
|
}
|
|
353
492
|
else {
|
|
354
|
-
md += `| ID |
|
|
493
|
+
md += `| ID | Date | Category | Summary | Details |\n`;
|
|
355
494
|
md += `|----|------|----------|------|------|\n`;
|
|
356
495
|
for (const entry of recent) {
|
|
357
496
|
const detailIcon = entry.hasBody ? 'π' : '-';
|
|
@@ -359,47 +498,43 @@ class MemoryEngine {
|
|
|
359
498
|
}
|
|
360
499
|
md += `\n`;
|
|
361
500
|
}
|
|
362
|
-
md += `## π·οΈ
|
|
501
|
+
md += `## π·οΈ By category\n\n`;
|
|
363
502
|
for (const [category, items] of Object.entries(byCategory).sort()) {
|
|
364
|
-
md += `- **${category}**: ${items.length}
|
|
503
|
+
md += `- **${category}**: ${items.length} items\n`;
|
|
365
504
|
}
|
|
366
505
|
const olderCount = entries.length - recent.length;
|
|
367
506
|
const oldestDate = entries[entries.length - 1]?.date ?? '-';
|
|
368
507
|
const newestDate = entries[0]?.date ?? '-';
|
|
369
|
-
md += `\n## π
|
|
370
|
-
md += `-
|
|
371
|
-
md += `-
|
|
508
|
+
md += `\n## π Stats\n\n`;
|
|
509
|
+
md += `- Total: ${entries.length} items (${oldestDate} ~ ${newestDate})\n`;
|
|
510
|
+
md += `- Last ${this.recentDays} days: ${recent.length} items\n`;
|
|
372
511
|
if (olderCount > 0)
|
|
373
|
-
md += `- ${this.recentDays}
|
|
512
|
+
md += `- Older than ${this.recentDays} days: ${olderCount} items\n`;
|
|
374
513
|
return md;
|
|
375
514
|
}
|
|
376
515
|
topic(agentId, topicName) {
|
|
377
516
|
const entries = this.loadAllEntries(agentId);
|
|
378
517
|
const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
|
|
379
518
|
if (filtered.length === 0) {
|
|
380
|
-
console.log(
|
|
519
|
+
console.log(`No memories found for topic '${topicName}'.`);
|
|
381
520
|
return;
|
|
382
521
|
}
|
|
383
|
-
console.log(`## π
|
|
522
|
+
console.log(`## π Topic: ${topicName} (${filtered.length} items)\n`);
|
|
384
523
|
for (const entry of filtered) {
|
|
385
524
|
console.log(`### [${entry.date}] ${entry.summary}`);
|
|
386
|
-
console.log(`-
|
|
387
|
-
console.log(`-
|
|
388
|
-
console.log(`-
|
|
525
|
+
console.log(`- File: data/${agentId}/entries/${entry.file}`);
|
|
526
|
+
console.log(`- Category: ${entry.category ?? '-'}`);
|
|
527
|
+
console.log(`- Tags: ${(entry.tags ?? []).join(', ') || '-'}`);
|
|
389
528
|
console.log('');
|
|
390
529
|
}
|
|
391
530
|
}
|
|
392
531
|
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);
|
|
532
|
+
const filtered = this.getRecentEntries(agentId, days);
|
|
398
533
|
if (filtered.length === 0) {
|
|
399
|
-
console.log(
|
|
534
|
+
console.log(`No memories found in the last ${days} days.`);
|
|
400
535
|
return;
|
|
401
536
|
}
|
|
402
|
-
console.log(`## π₯
|
|
537
|
+
console.log(`## π₯ Last ${days} days (${filtered.length} items)\n`);
|
|
403
538
|
for (const entry of filtered) {
|
|
404
539
|
const detailIcon = entry.hasBody ? ' π' : '';
|
|
405
540
|
const timestamp = String(entry.date).includes('T')
|
|
@@ -408,44 +543,75 @@ class MemoryEngine {
|
|
|
408
543
|
console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
|
|
409
544
|
}
|
|
410
545
|
}
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
546
|
+
getRecentEntries(agentId, days = 30, opts = {}) {
|
|
547
|
+
const entries = this.loadAllEntries(agentId);
|
|
548
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
|
|
549
|
+
.toISOString()
|
|
550
|
+
.slice(0, 10);
|
|
551
|
+
const filtered = entries.filter((e) => this.resolveRecentDate(agentId, e).slice(0, 10) >= cutoff);
|
|
552
|
+
if (!opts.includeBody)
|
|
553
|
+
return filtered;
|
|
554
|
+
return filtered.map((e) => ({
|
|
555
|
+
...e,
|
|
556
|
+
body: this.readEntryBody(agentId, e.file),
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
resolveRecentDate(agentId, entry) {
|
|
560
|
+
const updated = this.toDateString(entry.updated);
|
|
561
|
+
if (updated)
|
|
562
|
+
return updated;
|
|
563
|
+
const createdAt = this.toDateString(entry['created_at']);
|
|
564
|
+
if (createdAt)
|
|
565
|
+
return createdAt;
|
|
566
|
+
const date = this.toDateString(entry.date);
|
|
567
|
+
if (date)
|
|
568
|
+
return date;
|
|
569
|
+
try {
|
|
570
|
+
const filePath = path_1.default.join(this.getEntriesDir(agentId), entry.file);
|
|
571
|
+
return fs_1.default.statSync(filePath).mtime.toISOString();
|
|
416
572
|
}
|
|
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 });
|
|
573
|
+
catch {
|
|
574
|
+
return '';
|
|
424
575
|
}
|
|
425
|
-
|
|
426
|
-
|
|
576
|
+
}
|
|
577
|
+
toDateString(value) {
|
|
578
|
+
if (value &&
|
|
579
|
+
typeof value === 'object' &&
|
|
580
|
+
typeof value.toISOString === 'function') {
|
|
581
|
+
return value.toISOString();
|
|
582
|
+
}
|
|
583
|
+
return typeof value === 'string' ? value : '';
|
|
584
|
+
}
|
|
585
|
+
readEntryBody(agentId, file) {
|
|
586
|
+
try {
|
|
587
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), file), 'utf-8');
|
|
588
|
+
const { content } = (0, parser_1.parseFrontmatter)(raw);
|
|
589
|
+
return content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
return '';
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
find(agentId, keyword) {
|
|
596
|
+
const { files, index } = this.getSearchIndex(agentId);
|
|
597
|
+
if (files.length === 0) {
|
|
598
|
+
console.log('No memories found.');
|
|
427
599
|
return;
|
|
428
600
|
}
|
|
429
|
-
const
|
|
430
|
-
const
|
|
601
|
+
const indexedResults = index.search(keyword, FIND_RESULT_LIMIT + 1);
|
|
602
|
+
const truncated = indexedResults.length > FIND_RESULT_LIMIT;
|
|
603
|
+
const results = indexedResults.slice(0, FIND_RESULT_LIMIT);
|
|
431
604
|
if (results.length === 0) {
|
|
432
|
-
console.log(`'${keyword}'
|
|
605
|
+
console.log(`No related memories found for '${keyword}'.`);
|
|
433
606
|
return;
|
|
434
607
|
}
|
|
435
|
-
|
|
608
|
+
const limitNote = truncated ? `, top ${FIND_RESULT_LIMIT}` : '';
|
|
609
|
+
console.log(`## π Search results: "${keyword}" (${results.length} items${limitNote}, sorted by BM25 score)\n`);
|
|
436
610
|
for (const entry of results) {
|
|
437
|
-
const score = entry._score != null ? ` [${entry._score}
|
|
611
|
+
const score = entry._score != null ? ` [${entry._score}pts]` : '';
|
|
438
612
|
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
|
|
439
613
|
}
|
|
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
|
-
};
|
|
614
|
+
const findCtx = this.createFindActionContext(agentId, keyword, results);
|
|
449
615
|
const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
|
|
450
616
|
const findBlock = (0, knowledge_core_1.formatActions)(findActions);
|
|
451
617
|
if (findBlock)
|
|
@@ -470,7 +636,7 @@ class MemoryEngine {
|
|
|
470
636
|
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, e.file), 'utf-8');
|
|
471
637
|
const { content: rawContent } = (0, parser_1.parseFrontmatter)(raw);
|
|
472
638
|
const body = rawContent.replace(/^#[^\n]*\n*/m, '').trim();
|
|
473
|
-
if (body !== '(
|
|
639
|
+
if (body !== '(Add details here)')
|
|
474
640
|
content = body;
|
|
475
641
|
}
|
|
476
642
|
catch (_err) {
|
|
@@ -494,10 +660,10 @@ class MemoryEngine {
|
|
|
494
660
|
filters.push(`category=${options.category}`);
|
|
495
661
|
if (options.tag)
|
|
496
662
|
filters.push(`tag=${options.tag}`);
|
|
497
|
-
console.log(
|
|
663
|
+
console.log(`No memories match the filters. (${filters.join(', ') || 'all'})`);
|
|
498
664
|
return;
|
|
499
665
|
}
|
|
500
|
-
console.log(`## π
|
|
666
|
+
console.log(`## π List: ${agentId} (${filtered.length} items)\n`);
|
|
501
667
|
for (const entry of filtered) {
|
|
502
668
|
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
|
|
503
669
|
}
|
|
@@ -518,7 +684,7 @@ class MemoryEngine {
|
|
|
518
684
|
stats(agentId) {
|
|
519
685
|
const entries = this.loadAllEntries(agentId);
|
|
520
686
|
if (entries.length === 0) {
|
|
521
|
-
console.log('
|
|
687
|
+
console.log('No memories found.');
|
|
522
688
|
return;
|
|
523
689
|
}
|
|
524
690
|
const byCategory = {};
|
|
@@ -532,15 +698,15 @@ class MemoryEngine {
|
|
|
532
698
|
byTag[tag] = (byTag[tag] ?? 0) + 1;
|
|
533
699
|
}
|
|
534
700
|
}
|
|
535
|
-
console.log(`# ${agentId}
|
|
536
|
-
console.log(`##
|
|
701
|
+
console.log(`# ${agentId} Stats (${entries.length} items)\n`);
|
|
702
|
+
console.log(`## By category`);
|
|
537
703
|
for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
|
538
|
-
console.log(` ${cat}: ${count}
|
|
704
|
+
console.log(` ${cat}: ${count} items`);
|
|
539
705
|
}
|
|
540
|
-
console.log(`\n##
|
|
706
|
+
console.log(`\n## Top 20 Tags`);
|
|
541
707
|
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
|
542
708
|
for (const [tag, count] of sortedTags) {
|
|
543
|
-
console.log(` ${tag}: ${count}
|
|
709
|
+
console.log(` ${tag}: ${count} items`);
|
|
544
710
|
}
|
|
545
711
|
const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
|
|
546
712
|
const statsCtx = {
|
|
@@ -557,7 +723,7 @@ class MemoryEngine {
|
|
|
557
723
|
tags(agentId) {
|
|
558
724
|
const entries = this.loadAllEntries(agentId);
|
|
559
725
|
if (entries.length === 0) {
|
|
560
|
-
console.log('
|
|
726
|
+
console.log('No memories found.');
|
|
561
727
|
return;
|
|
562
728
|
}
|
|
563
729
|
const byTag = {};
|
|
@@ -568,18 +734,18 @@ class MemoryEngine {
|
|
|
568
734
|
}
|
|
569
735
|
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
|
|
570
736
|
if (sortedTags.length === 0) {
|
|
571
|
-
console.log('
|
|
737
|
+
console.log('No tags found.');
|
|
572
738
|
return;
|
|
573
739
|
}
|
|
574
|
-
console.log(`## π·οΈ
|
|
740
|
+
console.log(`## π·οΈ All tags (${sortedTags.length} items)\n`);
|
|
575
741
|
for (const [tag, count] of sortedTags) {
|
|
576
|
-
console.log(` ${tag}: ${count}
|
|
742
|
+
console.log(` ${tag}: ${count} items`);
|
|
577
743
|
}
|
|
578
744
|
}
|
|
579
745
|
get(agentId, memoryId) {
|
|
580
746
|
const entriesDir = this.getEntriesDir(agentId);
|
|
581
747
|
if (!fs_1.default.existsSync(entriesDir)) {
|
|
582
|
-
console.log('
|
|
748
|
+
console.log('No memories found.');
|
|
583
749
|
return;
|
|
584
750
|
}
|
|
585
751
|
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
@@ -591,10 +757,10 @@ class MemoryEngine {
|
|
|
591
757
|
const d = data;
|
|
592
758
|
console.log(`## π ${d.summary}\n`);
|
|
593
759
|
console.log(`- ID: ${d.id}`);
|
|
594
|
-
console.log(`-
|
|
595
|
-
console.log(`-
|
|
596
|
-
console.log(`-
|
|
597
|
-
console.log(`-
|
|
760
|
+
console.log(`- Date: ${d.date}`);
|
|
761
|
+
console.log(`- Category: ${d.category ?? '-'}`);
|
|
762
|
+
console.log(`- Tags: ${(d.tags ?? []).join(', ') || '-'}`);
|
|
763
|
+
console.log(`- File: data/${agentId}/entries/${file}`);
|
|
598
764
|
console.log(`\n---\n`);
|
|
599
765
|
console.log(content.trim());
|
|
600
766
|
const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
|
|
@@ -611,7 +777,7 @@ class MemoryEngine {
|
|
|
611
777
|
nodeMap[n.id] = n;
|
|
612
778
|
});
|
|
613
779
|
console.log(`\n---\n`);
|
|
614
|
-
console.log(`## π
|
|
780
|
+
console.log(`## π Related memories (${relatedEdges.length} items)\n`);
|
|
615
781
|
for (const edge of relatedEdges) {
|
|
616
782
|
const otherId = edge.from === memoryId ? edge.to : edge.from;
|
|
617
783
|
const other = nodeMap[otherId];
|
|
@@ -638,12 +804,12 @@ class MemoryEngine {
|
|
|
638
804
|
return;
|
|
639
805
|
}
|
|
640
806
|
}
|
|
641
|
-
console.log(`ID '${memoryId}'
|
|
807
|
+
console.log(`ID '${memoryId}' not found.`);
|
|
642
808
|
}
|
|
643
809
|
update(agentId, memoryId, options = {}) {
|
|
644
810
|
const entry = this.findEntryById(agentId, memoryId);
|
|
645
811
|
if (!entry) {
|
|
646
|
-
console.log(`β ID '${memoryId}'
|
|
812
|
+
console.log(`β ID '${memoryId}' not found.`);
|
|
647
813
|
process.exit(1);
|
|
648
814
|
}
|
|
649
815
|
const { filePath, data, content } = entry;
|
|
@@ -664,11 +830,12 @@ class MemoryEngine {
|
|
|
664
830
|
else if (options['no-important']) {
|
|
665
831
|
delete newData['important'];
|
|
666
832
|
}
|
|
667
|
-
const newBody = options.body ?? existingBody ?? '(
|
|
833
|
+
const newBody = options.body ?? existingBody ?? '(Add details here)';
|
|
668
834
|
const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
|
|
669
835
|
const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
|
|
670
836
|
fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
|
|
671
|
-
|
|
837
|
+
this.invalidateSearchIndex(agentId);
|
|
838
|
+
console.log(`β
Updated: ${entry.file}`);
|
|
672
839
|
console.log(` ID: ${memoryId}`);
|
|
673
840
|
if (options.summary)
|
|
674
841
|
console.log(` Summary: ${String(newData['summary'])}`);
|
|
@@ -677,28 +844,29 @@ class MemoryEngine {
|
|
|
677
844
|
if (options.tags)
|
|
678
845
|
console.log(` Tags: ${newData['tags'].join(', ')}`);
|
|
679
846
|
if (options.body)
|
|
680
|
-
console.log(` Body: (
|
|
847
|
+
console.log(` Body: (updated)`);
|
|
681
848
|
}
|
|
682
849
|
delete(agentId, memoryId, options = {}) {
|
|
683
850
|
const entry = this.findEntryById(agentId, memoryId);
|
|
684
851
|
if (!entry) {
|
|
685
|
-
console.log(`β ID '${memoryId}'
|
|
852
|
+
console.log(`β ID '${memoryId}' not found.`);
|
|
686
853
|
process.exit(1);
|
|
687
854
|
}
|
|
688
855
|
if (!options.force) {
|
|
689
|
-
console.log(`β οΈ
|
|
690
|
-
console.log(`
|
|
691
|
-
console.log(`\
|
|
692
|
-
console.log(
|
|
856
|
+
console.log(`β οΈ Delete target: [${entry.data.id}] ${entry.data.summary}`);
|
|
857
|
+
console.log(` File: ${entry.file}`);
|
|
858
|
+
console.log(`\nAdd --force to delete it.`);
|
|
859
|
+
console.log(`Example: memory delete ${agentId} ${memoryId} --force`);
|
|
693
860
|
return;
|
|
694
861
|
}
|
|
695
862
|
fs_1.default.unlinkSync(entry.filePath);
|
|
696
|
-
|
|
863
|
+
this.invalidateSearchIndex(agentId);
|
|
864
|
+
console.log(`ποΈ Deleted: ${entry.file}`);
|
|
697
865
|
console.log(` ID: ${memoryId}`);
|
|
698
866
|
console.log(` Summary: ${entry.data.summary}`);
|
|
699
867
|
try {
|
|
700
868
|
this.markSummaryDirty(agentId);
|
|
701
|
-
console.log(` π summary.md
|
|
869
|
+
console.log(` π summary.md update scheduled`);
|
|
702
870
|
}
|
|
703
871
|
catch (_e) {
|
|
704
872
|
}
|
|
@@ -707,27 +875,27 @@ class MemoryEngine {
|
|
|
707
875
|
const entry1 = this.findEntryById(agentId, memoryId1);
|
|
708
876
|
const entry2 = this.findEntryById(agentId, memoryId2);
|
|
709
877
|
if (!entry1) {
|
|
710
|
-
console.log(`β ID '${memoryId1}'
|
|
878
|
+
console.log(`β ID '${memoryId1}' not found.`);
|
|
711
879
|
process.exit(1);
|
|
712
880
|
}
|
|
713
881
|
if (!entry2) {
|
|
714
|
-
console.log(`β ID '${memoryId2}'
|
|
882
|
+
console.log(`β ID '${memoryId2}' not found.`);
|
|
715
883
|
process.exit(1);
|
|
716
884
|
}
|
|
717
885
|
const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
718
886
|
const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
719
887
|
const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
|
|
720
|
-
const mergedBody = `##
|
|
888
|
+
const mergedBody = `## Merged memory 1 (${entry1.data.id}, ${entry1.data.date})
|
|
721
889
|
${entry1.data.summary}
|
|
722
890
|
|
|
723
|
-
${body1 !== '(
|
|
891
|
+
${body1 !== '(Add details here)' ? body1 : '(No details)'}
|
|
724
892
|
|
|
725
893
|
---
|
|
726
894
|
|
|
727
|
-
##
|
|
895
|
+
## Merged memory 2 (${entry2.data.id}, ${entry2.data.date})
|
|
728
896
|
${entry2.data.summary}
|
|
729
897
|
|
|
730
|
-
${body2 !== '(
|
|
898
|
+
${body2 !== '(Add details here)' ? body2 : '(No details)'}`;
|
|
731
899
|
const mergedTags = [
|
|
732
900
|
...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
|
|
733
901
|
];
|
|
@@ -749,10 +917,11 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
749
917
|
fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
|
|
750
918
|
fs_1.default.unlinkSync(entry1.filePath);
|
|
751
919
|
fs_1.default.unlinkSync(entry2.filePath);
|
|
752
|
-
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(`
|
|
755
|
-
console.log(`
|
|
920
|
+
this.invalidateSearchIndex(agentId);
|
|
921
|
+
console.log(`π Merge complete!`);
|
|
922
|
+
console.log(` New ID: ${newId}`);
|
|
923
|
+
console.log(` New file: ${filename}`);
|
|
924
|
+
console.log(` Merged memory: [${memoryId1}] + [${memoryId2}]`);
|
|
756
925
|
console.log(` Summary: ${mergedSummary}`);
|
|
757
926
|
}
|
|
758
927
|
buildFallbackSummary(entries) {
|
|
@@ -778,7 +947,7 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
778
947
|
const oldest = dates[0];
|
|
779
948
|
const newest = dates[dates.length - 1];
|
|
780
949
|
const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
|
|
781
|
-
body += `## ${topic} (${items.length}
|
|
950
|
+
body += `## ${topic} (${items.length} items${rangeLabel})\n`;
|
|
782
951
|
for (const e of items)
|
|
783
952
|
body += `- [${e.date}] ${e.summary}\n`;
|
|
784
953
|
for (const e of items)
|
|
@@ -788,22 +957,26 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
788
957
|
return body;
|
|
789
958
|
}
|
|
790
959
|
callSummarizer(prompt) {
|
|
791
|
-
const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
|
|
792
960
|
try {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
961
|
+
const result = execCrewx(['q', this.summarizerAgent], {
|
|
962
|
+
encoding: 'utf-8',
|
|
963
|
+
timeout: this.summarizerTimeout,
|
|
964
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
965
|
+
input: prompt,
|
|
966
|
+
});
|
|
967
|
+
const parsed = parseCrewxResponse(result);
|
|
968
|
+
this.recordSummarizerSuccess();
|
|
969
|
+
return parsed;
|
|
970
|
+
}
|
|
971
|
+
catch (e) {
|
|
972
|
+
const err = e;
|
|
973
|
+
const reason = err.code === 'ETIMEDOUT' || err.killed
|
|
974
|
+
? `timed out after ${this.summarizerTimeout}ms`
|
|
975
|
+
: err.message;
|
|
976
|
+
console.error(`[memory] callSummarizer failed (agent=${this.summarizerAgent}), falling back to raw entry list: ${reason}`);
|
|
977
|
+
this.recordSummarizerFailure();
|
|
798
978
|
return null;
|
|
799
979
|
}
|
|
800
|
-
finally {
|
|
801
|
-
try {
|
|
802
|
-
fs_1.default.unlinkSync(tmpFile);
|
|
803
|
-
}
|
|
804
|
-
catch (_e) {
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
980
|
}
|
|
808
981
|
generateSummary(agentId, options = {}) {
|
|
809
982
|
const allEntries = this.loadAllEntries(agentId);
|
|
@@ -861,13 +1034,13 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
861
1034
|
for (const entry of newItems) {
|
|
862
1035
|
const importantTag = entry.important ? ' [important]' : '';
|
|
863
1036
|
p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
|
|
864
|
-
if (entry.body && entry.body !== '(
|
|
1037
|
+
if (entry.body && entry.body !== '(Add details here)') {
|
|
865
1038
|
p += ` β ${entry.body.slice(0, 200)}`;
|
|
866
1039
|
}
|
|
867
1040
|
p += ` (file: entries/${entry.file})\n`;
|
|
868
1041
|
}
|
|
869
1042
|
p += `\n## Instructions:\n`;
|
|
870
|
-
p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4
|
|
1043
|
+
p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4 items, 01/28~02/14)"\n`;
|
|
871
1044
|
p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
|
|
872
1045
|
p += `- After each topic summary, list relevant entry file links as "β [entries/filename](entries/filename)"\n`;
|
|
873
1046
|
p += `- Highlight key decisions, insights, and action items\n`;
|
|
@@ -917,76 +1090,141 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
917
1090
|
summarize(agentId, options = {}) {
|
|
918
1091
|
const entries = this.loadAllEntries(agentId);
|
|
919
1092
|
if (entries.length === 0) {
|
|
920
|
-
console.log('
|
|
1093
|
+
console.log('No memories found.');
|
|
921
1094
|
return;
|
|
922
1095
|
}
|
|
923
|
-
console.log(`π
|
|
1096
|
+
console.log(`π Starting summary generation (${entries.length} items entries)\n`);
|
|
924
1097
|
const result = this.generateSummary(agentId, { force: options.force });
|
|
925
1098
|
if (result.status === 'up-to-date') {
|
|
926
|
-
console.log('β
summary.md
|
|
1099
|
+
console.log('β
summary.md is up to date.');
|
|
927
1100
|
}
|
|
928
1101
|
else if (result.status === 'fallback') {
|
|
929
|
-
console.log(`π
|
|
1102
|
+
console.log(`π Fallback summary generated (AI call failed)`);
|
|
930
1103
|
}
|
|
931
1104
|
else if (result.status === 'updated') {
|
|
932
|
-
console.log(`π€
|
|
1105
|
+
console.log(`π€ Summary complete (${result.newEntries} items applied)`);
|
|
1106
|
+
}
|
|
1107
|
+
console.log(` File: data/${agentId}/summary.md`);
|
|
1108
|
+
console.log(` entries: ${result.entryCount} items`);
|
|
1109
|
+
}
|
|
1110
|
+
isSummarizeLockStale(lockPath) {
|
|
1111
|
+
let content;
|
|
1112
|
+
try {
|
|
1113
|
+
content = fs_1.default.readFileSync(lockPath, 'utf-8');
|
|
1114
|
+
}
|
|
1115
|
+
catch {
|
|
1116
|
+
return true;
|
|
1117
|
+
}
|
|
1118
|
+
const pidMatch = content.match(/^pid=(\d+)/m);
|
|
1119
|
+
const pid = pidMatch ? Number(pidMatch[1]) : NaN;
|
|
1120
|
+
if (Number.isFinite(pid)) {
|
|
1121
|
+
try {
|
|
1122
|
+
process.kill(pid, 0);
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
catch (e) {
|
|
1126
|
+
const err = e;
|
|
1127
|
+
if (err.code === 'ESRCH')
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const stat = fs_1.default.statSync(lockPath);
|
|
1133
|
+
return Date.now() - stat.mtimeMs > exports.SUMMARIZE_LOCK_STALE_MS;
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
return true;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
acquireSummarizeLock() {
|
|
1140
|
+
const lockPath = this.getSummarizeLockPath();
|
|
1141
|
+
const content = `pid=${process.pid}\nmtime=${new Date().toISOString()}\n`;
|
|
1142
|
+
try {
|
|
1143
|
+
const fd = fs_1.default.openSync(lockPath, 'wx');
|
|
1144
|
+
fs_1.default.writeSync(fd, content);
|
|
1145
|
+
fs_1.default.closeSync(fd);
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
catch (e) {
|
|
1149
|
+
const err = e;
|
|
1150
|
+
if (err.code !== 'EEXIST')
|
|
1151
|
+
throw err;
|
|
1152
|
+
}
|
|
1153
|
+
if (!this.isSummarizeLockStale(lockPath)) {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
fs_1.default.writeFileSync(lockPath, content, 'utf-8');
|
|
1157
|
+
return true;
|
|
1158
|
+
}
|
|
1159
|
+
releaseSummarizeLock() {
|
|
1160
|
+
try {
|
|
1161
|
+
fs_1.default.unlinkSync(this.getSummarizeLockPath());
|
|
1162
|
+
}
|
|
1163
|
+
catch {
|
|
933
1164
|
}
|
|
934
|
-
console.log(` νμΌ: data/${agentId}/summary.md`);
|
|
935
|
-
console.log(` μνΈλ¦¬: ${result.entryCount}κ°`);
|
|
936
1165
|
}
|
|
937
1166
|
summarizeDirty() {
|
|
938
1167
|
if (!fs_1.default.existsSync(this.dataDir)) {
|
|
939
|
-
console.log(`π‘
|
|
1168
|
+
console.log(`π‘ Register debounce cron:`);
|
|
940
1169
|
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
941
1170
|
return;
|
|
942
1171
|
}
|
|
943
|
-
|
|
944
|
-
.
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1172
|
+
if (!this.acquireSummarizeLock()) {
|
|
1173
|
+
console.log(`βοΈ summarize-dirty is already running (lock held) β skipping this run.`);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
try {
|
|
1177
|
+
const agentDirs = fs_1.default
|
|
1178
|
+
.readdirSync(this.dataDir, { withFileTypes: true })
|
|
1179
|
+
.filter((entry) => entry.isDirectory())
|
|
1180
|
+
.map((entry) => entry.name);
|
|
1181
|
+
for (const agentId of agentDirs) {
|
|
1182
|
+
const dirtyPath = this.getDirtySummaryPath(agentId);
|
|
1183
|
+
if (!fs_1.default.existsSync(dirtyPath))
|
|
1184
|
+
continue;
|
|
1185
|
+
try {
|
|
1186
|
+
const result = this.generateSummary(agentId);
|
|
1187
|
+
if (result.status === 'updated') {
|
|
1188
|
+
console.log(`π€ ${agentId} summary.md updated (${result.newEntries} items applied)`);
|
|
1189
|
+
}
|
|
1190
|
+
else if (result.status === 'fallback') {
|
|
1191
|
+
console.log(`π ${agentId} Fallback summary generated (AI call failed)`);
|
|
1192
|
+
}
|
|
1193
|
+
else if (result.status === 'up-to-date') {
|
|
1194
|
+
console.log(`β
${agentId} summary.md is up to date.`);
|
|
1195
|
+
}
|
|
1196
|
+
else if (result.status === 'empty') {
|
|
1197
|
+
console.log(`βΉοΈ ${agentId} has no memories to summarize.`);
|
|
1198
|
+
}
|
|
1199
|
+
fs_1.default.unlinkSync(dirtyPath);
|
|
961
1200
|
}
|
|
962
|
-
|
|
963
|
-
console.
|
|
1201
|
+
catch (e) {
|
|
1202
|
+
console.error(`β ${agentId} summary.md update failed: ${e.message}`);
|
|
964
1203
|
}
|
|
965
|
-
fs_1.default.unlinkSync(dirtyPath);
|
|
966
|
-
}
|
|
967
|
-
catch (e) {
|
|
968
|
-
console.error(`β ${agentId} summary.md κ°±μ μ€ν¨: ${e.message}`);
|
|
969
1204
|
}
|
|
970
1205
|
}
|
|
971
|
-
|
|
1206
|
+
finally {
|
|
1207
|
+
this.releaseSummarizeLock();
|
|
1208
|
+
}
|
|
1209
|
+
console.log(`π‘ Register debounce cron:`);
|
|
972
1210
|
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
973
1211
|
}
|
|
974
1212
|
search(agentId, query) {
|
|
975
1213
|
const entries = this.loadAllEntries(agentId);
|
|
976
1214
|
if (entries.length === 0) {
|
|
977
|
-
console.log('
|
|
1215
|
+
console.log('No memories found.');
|
|
978
1216
|
return;
|
|
979
1217
|
}
|
|
980
1218
|
const memoryList = entries
|
|
981
1219
|
.map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
|
|
982
1220
|
.join('\n');
|
|
983
|
-
const task =
|
|
1221
|
+
const task = `Memory list:\n${memoryList}\n\nQuestion: "${query}"`;
|
|
984
1222
|
try {
|
|
985
|
-
console.log(`π "${query}"
|
|
986
|
-
const result = (
|
|
1223
|
+
console.log(`π "${query}" searching... (${this.searcherAgent})\n`);
|
|
1224
|
+
const result = execCrewx(['q', `${this.searcherAgent} ${task}`], { encoding: 'utf-8', timeout: this.searcherTimeout, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
987
1225
|
const parsed = parseCrewxResponse(result);
|
|
988
1226
|
if (parsed) {
|
|
989
|
-
console.log(`## π§
|
|
1227
|
+
console.log(`## π§ Semantic search results\n`);
|
|
990
1228
|
console.log(parsed);
|
|
991
1229
|
}
|
|
992
1230
|
else {
|
|
@@ -996,12 +1234,12 @@ ${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
|
996
1234
|
catch (error) {
|
|
997
1235
|
const err = error;
|
|
998
1236
|
if (err.code === 'ETIMEDOUT' || err.killed) {
|
|
999
|
-
console.log('
|
|
1237
|
+
console.log('Search failed: timed out (max 5 minutes)');
|
|
1000
1238
|
}
|
|
1001
1239
|
else {
|
|
1002
|
-
console.log('
|
|
1240
|
+
console.log('Search failed:', err.message);
|
|
1003
1241
|
}
|
|
1004
|
-
console.log('\nπ‘ Tip:
|
|
1242
|
+
console.log('\nπ‘ Tip: try keyword search with the find command.');
|
|
1005
1243
|
}
|
|
1006
1244
|
}
|
|
1007
1245
|
}
|