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