@crewx/memory 0.1.0
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/README.md +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +365 -0
- package/dist/cli.js.map +1 -0
- package/dist/src/engine.d.ts +43 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +942 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/mindmap.d.ts +75 -0
- package/dist/src/mindmap.d.ts.map +1 -0
- package/dist/src/mindmap.js +838 -0
- package/dist/src/mindmap.js.map +1 -0
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +7 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +15 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CATEGORIES = exports.MemoryEngine = void 0;
|
|
7
|
+
exports.createMemoryEngine = createMemoryEngine;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const nanoid_1 = require("nanoid");
|
|
13
|
+
const parser_1 = require("./parser");
|
|
14
|
+
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
15
|
+
function parseCrewxResponse(raw) {
|
|
16
|
+
const responseMatch = raw.match(/π Response:\s*β+\s*([\s\S]*?)(?=π Working Directory|$)/);
|
|
17
|
+
if (responseMatch?.[1]) {
|
|
18
|
+
const captured = responseMatch[1].trim();
|
|
19
|
+
const capturedFirstLine = captured.split('\n')[0]?.trim() ?? '';
|
|
20
|
+
if (!capturedFirstLine.startsWith('{')) {
|
|
21
|
+
return captured;
|
|
22
|
+
}
|
|
23
|
+
return parseJsonlContent(captured);
|
|
24
|
+
}
|
|
25
|
+
return parseJsonlContent(raw);
|
|
26
|
+
}
|
|
27
|
+
function parseJsonlContent(raw) {
|
|
28
|
+
const lines = raw.split('\n').filter((l) => l.trim().startsWith('{'));
|
|
29
|
+
let assistantText = '';
|
|
30
|
+
let hasAssistantMessage = false;
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
try {
|
|
33
|
+
const obj = JSON.parse(line);
|
|
34
|
+
if (obj.type === 'message' && obj.role === 'assistant') {
|
|
35
|
+
hasAssistantMessage = true;
|
|
36
|
+
if (obj.content)
|
|
37
|
+
assistantText += obj.content;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (_e) {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (hasAssistantMessage)
|
|
44
|
+
return assistantText.trim() || null;
|
|
45
|
+
const trimmed = raw.trim();
|
|
46
|
+
if (trimmed.length === 0)
|
|
47
|
+
return null;
|
|
48
|
+
const firstLine = trimmed.split('\n')[0]?.trim() ?? '';
|
|
49
|
+
if (firstLine.startsWith('{'))
|
|
50
|
+
return null;
|
|
51
|
+
return trimmed;
|
|
52
|
+
}
|
|
53
|
+
class MemoryEngine {
|
|
54
|
+
constructor(config = {}) {
|
|
55
|
+
this.dataDir =
|
|
56
|
+
config.dataDir ??
|
|
57
|
+
process.env['MEMORY_DATA_DIR'] ??
|
|
58
|
+
path_1.default.resolve(process.cwd(), 'memory');
|
|
59
|
+
this.summaryDays =
|
|
60
|
+
config.summaryDays ??
|
|
61
|
+
(parseInt(process.env['MEMORY_SUMMARY_DAYS'] ?? '', 10) || 7);
|
|
62
|
+
this.recentDays =
|
|
63
|
+
config.recentDays ??
|
|
64
|
+
(parseInt(process.env['MEMORY_RECENT_DAYS'] ?? '', 10) || 30);
|
|
65
|
+
this.shortTermHours = config.shortTermHours ?? 24;
|
|
66
|
+
}
|
|
67
|
+
getAgentDir(agentId) {
|
|
68
|
+
return path_1.default.join(this.dataDir, agentId);
|
|
69
|
+
}
|
|
70
|
+
getEntriesDir(agentId) {
|
|
71
|
+
return path_1.default.join(this.getAgentDir(agentId), 'entries');
|
|
72
|
+
}
|
|
73
|
+
getSummaryPath(agentId) {
|
|
74
|
+
return path_1.default.join(this.getAgentDir(agentId), 'summary.md');
|
|
75
|
+
}
|
|
76
|
+
getDirtySummaryPath(agentId) {
|
|
77
|
+
return path_1.default.join(this.getAgentDir(agentId), '.dirty-summary');
|
|
78
|
+
}
|
|
79
|
+
ensureDir(dir) {
|
|
80
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
81
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
generateId() {
|
|
85
|
+
return (0, nanoid_1.nanoid)(6);
|
|
86
|
+
}
|
|
87
|
+
getToday() {
|
|
88
|
+
return new Date().toISOString().slice(0, 10);
|
|
89
|
+
}
|
|
90
|
+
getLocalISO8601() {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const offset = -now.getTimezoneOffset();
|
|
93
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
94
|
+
const offsetHours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
|
|
95
|
+
const offsetMins = String(Math.abs(offset) % 60).padStart(2, '0');
|
|
96
|
+
const y = now.getFullYear();
|
|
97
|
+
const m = String(now.getMonth() + 1).padStart(2, '0');
|
|
98
|
+
const d = String(now.getDate()).padStart(2, '0');
|
|
99
|
+
const h = String(now.getHours()).padStart(2, '0');
|
|
100
|
+
const min = String(now.getMinutes()).padStart(2, '0');
|
|
101
|
+
const s = String(now.getSeconds()).padStart(2, '0');
|
|
102
|
+
return `${y}-${m}-${d}T${h}:${min}:${s}${sign}${offsetHours}:${offsetMins}`;
|
|
103
|
+
}
|
|
104
|
+
getPrimaryTag(entry) {
|
|
105
|
+
return (entry.tags?.[0]) ?? 'general';
|
|
106
|
+
}
|
|
107
|
+
toEntryTimestamp(entry) {
|
|
108
|
+
if (entry['created_at'])
|
|
109
|
+
return String(entry['created_at']);
|
|
110
|
+
const d = entry['date'];
|
|
111
|
+
const dateStr = d && typeof d === 'object' && typeof d.toISOString === 'function' ? d.toISOString().slice(0, 10) : String(d ?? '');
|
|
112
|
+
if (dateStr.includes('T'))
|
|
113
|
+
return dateStr;
|
|
114
|
+
return dateStr + 'T09:00:00+09:00';
|
|
115
|
+
}
|
|
116
|
+
markSummaryDirty(agentId) {
|
|
117
|
+
this.ensureDir(this.getAgentDir(agentId));
|
|
118
|
+
fs_1.default.writeFileSync(this.getDirtySummaryPath(agentId), String(Date.now()), 'utf-8');
|
|
119
|
+
}
|
|
120
|
+
loadAllEntries(agentId) {
|
|
121
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
122
|
+
if (!fs_1.default.existsSync(entriesDir))
|
|
123
|
+
return [];
|
|
124
|
+
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
125
|
+
const entries = [];
|
|
126
|
+
for (const file of files) {
|
|
127
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
128
|
+
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
129
|
+
const bodyText = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
130
|
+
const hasBody = Boolean(bodyText) && bodyText !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)';
|
|
131
|
+
const entry = data;
|
|
132
|
+
entries.push({ ...entry, file, hasBody });
|
|
133
|
+
}
|
|
134
|
+
entries.sort((a, b) => this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a)));
|
|
135
|
+
return entries;
|
|
136
|
+
}
|
|
137
|
+
findEntryById(agentId, memoryId) {
|
|
138
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
139
|
+
if (!fs_1.default.existsSync(entriesDir))
|
|
140
|
+
return null;
|
|
141
|
+
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const filePath = path_1.default.join(entriesDir, file);
|
|
144
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
145
|
+
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
146
|
+
if (data.id === memoryId) {
|
|
147
|
+
return {
|
|
148
|
+
file,
|
|
149
|
+
filePath,
|
|
150
|
+
data: data,
|
|
151
|
+
content,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
save(agentId, summary, category = 'general', options = {}) {
|
|
158
|
+
this.ensureDir(this.getEntriesDir(agentId));
|
|
159
|
+
const id = this.generateId();
|
|
160
|
+
const dateTime = this.getLocalISO8601();
|
|
161
|
+
const tags = Array.isArray(options.tags)
|
|
162
|
+
? options.tags
|
|
163
|
+
: options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
|
|
164
|
+
const frontmatter = (0, parser_1.stringifyFrontmatter)({
|
|
165
|
+
id,
|
|
166
|
+
type: 'memory',
|
|
167
|
+
date: dateTime,
|
|
168
|
+
category,
|
|
169
|
+
tags,
|
|
170
|
+
summary,
|
|
171
|
+
...(options.important ? { important: true } : {}),
|
|
172
|
+
});
|
|
173
|
+
const filename = `${id}.md`;
|
|
174
|
+
const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
|
|
175
|
+
const body = options.body ?? '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)';
|
|
176
|
+
const content = `${frontmatter}\n\n# ${summary}\n\n${body}\n`;
|
|
177
|
+
fs_1.default.writeFileSync(filepath, content, 'utf-8');
|
|
178
|
+
console.log(`β
μ μ₯ μλ£: data/${agentId}/entries/${filename}`);
|
|
179
|
+
console.log(` ID: ${id}`);
|
|
180
|
+
console.log(` Tags: ${tags.join(', ') || '(μμ)'}`);
|
|
181
|
+
console.log(` Category: ${category}`);
|
|
182
|
+
try {
|
|
183
|
+
this.markSummaryDirty(agentId);
|
|
184
|
+
console.log(` π summary.md κ°±μ μμ½λ¨`);
|
|
185
|
+
}
|
|
186
|
+
catch (_e) {
|
|
187
|
+
}
|
|
188
|
+
const saveCtx = {
|
|
189
|
+
commandId: 'memory.save',
|
|
190
|
+
params: { agentId, tags: tags.join(',') },
|
|
191
|
+
resultTags: tags.slice(0, 3),
|
|
192
|
+
resultCount: 1,
|
|
193
|
+
};
|
|
194
|
+
const saveActions = (0, knowledge_core_1.generateNextActions)(saveCtx);
|
|
195
|
+
const saveBlock = (0, knowledge_core_1.formatActions)(saveActions);
|
|
196
|
+
if (saveBlock)
|
|
197
|
+
console.log(saveBlock);
|
|
198
|
+
}
|
|
199
|
+
index(agentId, options = {}) {
|
|
200
|
+
const summaryPath = this.getSummaryPath(agentId);
|
|
201
|
+
const entries = this.loadAllEntries(agentId);
|
|
202
|
+
const topics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))];
|
|
203
|
+
if (options.recent) {
|
|
204
|
+
const days = parseInt(String(options.recent), 10) || this.recentDays;
|
|
205
|
+
this.recent(agentId, days);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const cutoff24h = new Date(Date.now() - this.shortTermHours * 60 * 60 * 1000).toISOString();
|
|
209
|
+
const shortTermEntries = entries.filter((e) => this.toEntryTimestamp(e) >= cutoff24h);
|
|
210
|
+
console.log(`# ${agentId} κΈ°μ΅ μμ½\n`);
|
|
211
|
+
const summaryLastUpdated = fs_1.default.existsSync(summaryPath)
|
|
212
|
+
? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8')).data.last_updated ?? '-'
|
|
213
|
+
: '-';
|
|
214
|
+
console.log(`> ${entries.length}건 | ν ν½ ${topics.length}κ° | κ°±μ : ${summaryLastUpdated}`);
|
|
215
|
+
if (options.full) {
|
|
216
|
+
const byCat = {};
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
const cat = entry.category ?? 'general';
|
|
219
|
+
byCat[cat] = (byCat[cat] ?? 0) + 1;
|
|
220
|
+
}
|
|
221
|
+
const catSummary = Object.entries(byCat)
|
|
222
|
+
.sort((a, b) => b[1] - a[1])
|
|
223
|
+
.map(([cat, count]) => `${cat}:${count}`)
|
|
224
|
+
.join(' ');
|
|
225
|
+
console.log(`> π ${catSummary}`);
|
|
226
|
+
}
|
|
227
|
+
console.log('');
|
|
228
|
+
if (shortTermEntries.length > 0) {
|
|
229
|
+
const sortedShortTerm = [...shortTermEntries].sort((a, b) => {
|
|
230
|
+
const aImp = a.important ? 1 : 0;
|
|
231
|
+
const bImp = b.important ? 1 : 0;
|
|
232
|
+
if (aImp !== bImp)
|
|
233
|
+
return bImp - aImp;
|
|
234
|
+
return this.toEntryTimestamp(b).localeCompare(this.toEntryTimestamp(a));
|
|
235
|
+
});
|
|
236
|
+
console.log(`## π₯ μ΅κ·Ό μμ
(24h)\n`);
|
|
237
|
+
for (const e of sortedShortTerm) {
|
|
238
|
+
const star = e.important ? 'β ' : '';
|
|
239
|
+
console.log(`- ${star}[${e.id}] ${e.summary} (${this.getPrimaryTag(e)})`);
|
|
240
|
+
}
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
if (options.full) {
|
|
244
|
+
if (fs_1.default.existsSync(summaryPath)) {
|
|
245
|
+
const raw = fs_1.default.readFileSync(summaryPath, 'utf-8');
|
|
246
|
+
const { content: summaryBody } = (0, parser_1.parseFrontmatter)(raw);
|
|
247
|
+
if (entries.length > 0) {
|
|
248
|
+
const newestDate = entries[0]?.date;
|
|
249
|
+
const oldestDate = entries[entries.length - 1]?.date;
|
|
250
|
+
const newest = newestDate ? new Date(`${newestDate}T00:00:00Z`) : null;
|
|
251
|
+
const oldest = oldestDate ? new Date(`${oldestDate}T00:00:00Z`) : null;
|
|
252
|
+
if (newest &&
|
|
253
|
+
oldest &&
|
|
254
|
+
!Number.isNaN(newest.getTime()) &&
|
|
255
|
+
!Number.isNaN(oldest.getTime())) {
|
|
256
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
257
|
+
const diffDays = Math.max(1, Math.floor((newest.getTime() - oldest.getTime()) / msPerDay) + 1);
|
|
258
|
+
const toMD = (d) => `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
|
|
259
|
+
console.log('---');
|
|
260
|
+
console.log(`π ν ν½ μμ½ (${toMD(oldest)} ~ ${toMD(newest)}, ${diffDays}μΌκ°)\n`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
console.log(summaryBody.trim());
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
for (const tag of topics.sort()) {
|
|
267
|
+
const count = entries.filter((e) => this.getPrimaryTag(e) === tag).length;
|
|
268
|
+
console.log(`- **${tag}** (${count}건)`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const commandId = options.full ? 'memory.index-full' : 'memory.index';
|
|
273
|
+
const ctx = {
|
|
274
|
+
commandId,
|
|
275
|
+
params: { agentId },
|
|
276
|
+
resultTags: topics.slice(0, 3),
|
|
277
|
+
resultIds: entries.slice(0, 3).map(e => e.id),
|
|
278
|
+
resultCount: entries.length,
|
|
279
|
+
};
|
|
280
|
+
const actions = (0, knowledge_core_1.generateNextActions)(ctx);
|
|
281
|
+
const block = (0, knowledge_core_1.formatActions)(actions);
|
|
282
|
+
if (block)
|
|
283
|
+
console.log(block);
|
|
284
|
+
}
|
|
285
|
+
generateIndexContent(agentId) {
|
|
286
|
+
const entries = this.loadAllEntries(agentId);
|
|
287
|
+
const today = this.getToday();
|
|
288
|
+
const cutoff = new Date(Date.now() - this.recentDays * 24 * 60 * 60 * 1000)
|
|
289
|
+
.toISOString()
|
|
290
|
+
.slice(0, 10);
|
|
291
|
+
const byCategory = {};
|
|
292
|
+
const recent = [];
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (entry.date >= cutoff)
|
|
295
|
+
recent.push(entry);
|
|
296
|
+
const category = entry.category ?? 'general';
|
|
297
|
+
if (!byCategory[category])
|
|
298
|
+
byCategory[category] = [];
|
|
299
|
+
byCategory[category].push(entry);
|
|
300
|
+
}
|
|
301
|
+
let md = `# ${agentId} λ©λͺ¨λ¦¬\n\n`;
|
|
302
|
+
md += `> Last Updated: ${today}\n`;
|
|
303
|
+
md += `> Total: ${entries.length} memories\n\n`;
|
|
304
|
+
md += `## π₯ μ΅κ·Ό ${this.recentDays}μΌ\n\n`;
|
|
305
|
+
if (recent.length === 0) {
|
|
306
|
+
md += `(μμ)\n\n`;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
md += `| ID | λ μ§ | μΉ΄ν
κ³ λ¦¬ | μμ½ | μμΈ |\n`;
|
|
310
|
+
md += `|----|------|----------|------|------|\n`;
|
|
311
|
+
for (const entry of recent) {
|
|
312
|
+
const detailIcon = entry.hasBody ? 'π' : '-';
|
|
313
|
+
md += `| ${entry.id} | ${entry.date} | ${entry.category ?? '-'} | [${entry.summary}](entries/${entry.file}) | ${detailIcon} |\n`;
|
|
314
|
+
}
|
|
315
|
+
md += `\n`;
|
|
316
|
+
}
|
|
317
|
+
md += `## π·οΈ μΉ΄ν
κ³ λ¦¬λ³\n\n`;
|
|
318
|
+
for (const [category, items] of Object.entries(byCategory).sort()) {
|
|
319
|
+
md += `- **${category}**: ${items.length}건\n`;
|
|
320
|
+
}
|
|
321
|
+
const olderCount = entries.length - recent.length;
|
|
322
|
+
const oldestDate = entries[entries.length - 1]?.date ?? '-';
|
|
323
|
+
const newestDate = entries[0]?.date ?? '-';
|
|
324
|
+
md += `\n## π ν΅κ³\n\n`;
|
|
325
|
+
md += `- μ 체: ${entries.length}건 (${oldestDate} ~ ${newestDate})\n`;
|
|
326
|
+
md += `- μ΅κ·Ό ${this.recentDays}μΌ: ${recent.length}건\n`;
|
|
327
|
+
if (olderCount > 0)
|
|
328
|
+
md += `- ${this.recentDays}μΌ μ΄μ : ${olderCount}건\n`;
|
|
329
|
+
return md;
|
|
330
|
+
}
|
|
331
|
+
topic(agentId, topicName) {
|
|
332
|
+
const entries = this.loadAllEntries(agentId);
|
|
333
|
+
const filtered = entries.filter((e) => this.getPrimaryTag(e) === topicName);
|
|
334
|
+
if (filtered.length === 0) {
|
|
335
|
+
console.log(`ν ν½ '${topicName}'μ ν΄λΉνλ κΈ°μ΅μ΄ μμ΅λλ€.`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
console.log(`## π ν ν½: ${topicName} (${filtered.length}건)\n`);
|
|
339
|
+
for (const entry of filtered) {
|
|
340
|
+
console.log(`### [${entry.date}] ${entry.summary}`);
|
|
341
|
+
console.log(`- νμΌ: data/${agentId}/entries/${entry.file}`);
|
|
342
|
+
console.log(`- μΉ΄ν
κ³ λ¦¬: ${entry.category ?? '-'}`);
|
|
343
|
+
console.log(`- νκ·Έ: ${(entry.tags ?? []).join(', ') || '-'}`);
|
|
344
|
+
console.log('');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
recent(agentId, days = 30) {
|
|
348
|
+
const entries = this.loadAllEntries(agentId);
|
|
349
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
|
|
350
|
+
.toISOString()
|
|
351
|
+
.slice(0, 10);
|
|
352
|
+
const filtered = entries.filter((e) => e.date >= cutoff);
|
|
353
|
+
if (filtered.length === 0) {
|
|
354
|
+
console.log(`μ΅κ·Ό ${days}μΌ λ΄ κΈ°μ΅μ΄ μμ΅λλ€.`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
console.log(`## π₯ μ΅κ·Ό ${days}μΌ (${filtered.length}건)\n`);
|
|
358
|
+
for (const entry of filtered) {
|
|
359
|
+
const detailIcon = entry.hasBody ? ' π' : '';
|
|
360
|
+
const timestamp = String(entry.date).includes('T')
|
|
361
|
+
? String(entry.date).slice(0, 16).replace('T', ' ')
|
|
362
|
+
: entry.date;
|
|
363
|
+
console.log(`[${entry.id}] [${timestamp}] [${entry.category ?? '-'}] ${entry.summary}${detailIcon}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
find(agentId, keyword) {
|
|
367
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
368
|
+
if (!fs_1.default.existsSync(entriesDir)) {
|
|
369
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
373
|
+
const entries = [];
|
|
374
|
+
for (const file of files) {
|
|
375
|
+
const content = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
376
|
+
const { data, content: body } = (0, parser_1.parseFrontmatter)(content);
|
|
377
|
+
const entry = data;
|
|
378
|
+
entries.push({ ...entry, file, body });
|
|
379
|
+
}
|
|
380
|
+
if (entries.length === 0) {
|
|
381
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const kcEntries = entries;
|
|
385
|
+
const results = (0, knowledge_core_1.searchEntries)(kcEntries, keyword, { summary: 3, tags: 2, body: 1 });
|
|
386
|
+
if (results.length === 0) {
|
|
387
|
+
console.log(`'${keyword}' κ΄λ ¨ κΈ°μ΅μ μ°Ύμ μ μμ΅λλ€.`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
console.log(`## π κ²μ κ²°κ³Ό: "${keyword}" (${results.length}건, BM25 μ μμ)\n`);
|
|
391
|
+
for (const entry of results) {
|
|
392
|
+
const score = entry._score != null ? ` [${entry._score}μ ]` : '';
|
|
393
|
+
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${score} ${entry.summary}`);
|
|
394
|
+
}
|
|
395
|
+
const resultTags = [...new Set(results.flatMap(e => e.tags ?? []))].slice(0, 3);
|
|
396
|
+
const resultIds = results.slice(0, 3).map(e => e.id);
|
|
397
|
+
const findCtx = {
|
|
398
|
+
commandId: 'memory.find',
|
|
399
|
+
params: { agentId, query: keyword },
|
|
400
|
+
resultTags,
|
|
401
|
+
resultIds,
|
|
402
|
+
resultCount: results.length,
|
|
403
|
+
};
|
|
404
|
+
const findActions = (0, knowledge_core_1.generateNextActions)(findCtx);
|
|
405
|
+
const findBlock = (0, knowledge_core_1.formatActions)(findActions);
|
|
406
|
+
if (findBlock)
|
|
407
|
+
console.log(findBlock);
|
|
408
|
+
}
|
|
409
|
+
list(agentId, options = {}) {
|
|
410
|
+
const entries = this.loadAllEntries(agentId);
|
|
411
|
+
let filtered = entries;
|
|
412
|
+
if (options.category) {
|
|
413
|
+
filtered = filtered.filter(e => e.category === options.category);
|
|
414
|
+
}
|
|
415
|
+
if (options.tag) {
|
|
416
|
+
const requiredTags = options.tag.split(',').map(t => t.trim());
|
|
417
|
+
filtered = filtered.filter(e => requiredTags.every(t => (e.tags ?? []).includes(t)));
|
|
418
|
+
}
|
|
419
|
+
if (filtered.length === 0) {
|
|
420
|
+
const filters = [];
|
|
421
|
+
if (options.category)
|
|
422
|
+
filters.push(`category=${options.category}`);
|
|
423
|
+
if (options.tag)
|
|
424
|
+
filters.push(`tag=${options.tag}`);
|
|
425
|
+
console.log(`쑰건μ λ§λ κΈ°μ΅μ΄ μμ΅λλ€. (${filters.join(', ') || 'μ 체'})`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
console.log(`## π λͺ©λ‘: ${agentId} (${filtered.length}건)\n`);
|
|
429
|
+
for (const entry of filtered) {
|
|
430
|
+
console.log(`[${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}] ${entry.summary}`);
|
|
431
|
+
}
|
|
432
|
+
const resultTags = [...new Set(filtered.flatMap(e => e.tags ?? []))].slice(0, 3);
|
|
433
|
+
const resultIds = filtered.slice(0, 3).map(e => e.id);
|
|
434
|
+
const listCtx = {
|
|
435
|
+
commandId: 'memory.list',
|
|
436
|
+
params: { agentId, ...(options.category ? { category: options.category } : {}) },
|
|
437
|
+
resultTags,
|
|
438
|
+
resultIds,
|
|
439
|
+
resultCount: filtered.length,
|
|
440
|
+
};
|
|
441
|
+
const listActions = (0, knowledge_core_1.generateNextActions)(listCtx);
|
|
442
|
+
const listBlock = (0, knowledge_core_1.formatActions)(listActions);
|
|
443
|
+
if (listBlock)
|
|
444
|
+
console.log(listBlock);
|
|
445
|
+
}
|
|
446
|
+
stats(agentId) {
|
|
447
|
+
const entries = this.loadAllEntries(agentId);
|
|
448
|
+
if (entries.length === 0) {
|
|
449
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const byCategory = {};
|
|
453
|
+
for (const entry of entries) {
|
|
454
|
+
const cat = entry.category ?? 'general';
|
|
455
|
+
byCategory[cat] = (byCategory[cat] ?? 0) + 1;
|
|
456
|
+
}
|
|
457
|
+
const byTag = {};
|
|
458
|
+
for (const entry of entries) {
|
|
459
|
+
for (const tag of entry.tags ?? []) {
|
|
460
|
+
byTag[tag] = (byTag[tag] ?? 0) + 1;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
console.log(`# ${agentId} ν΅κ³ (${entries.length}건)\n`);
|
|
464
|
+
console.log(`## μΉ΄ν
κ³ λ¦¬λ³`);
|
|
465
|
+
for (const [cat, count] of Object.entries(byCategory).sort((a, b) => b[1] - a[1])) {
|
|
466
|
+
console.log(` ${cat}: ${count}건`);
|
|
467
|
+
}
|
|
468
|
+
console.log(`\n## νκ·Έ Top 20`);
|
|
469
|
+
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
|
470
|
+
for (const [tag, count] of sortedTags) {
|
|
471
|
+
console.log(` ${tag}: ${count}건`);
|
|
472
|
+
}
|
|
473
|
+
const topCategories = Object.entries(byCategory).sort((a, b) => b[1] - a[1]).map(t => t[0]);
|
|
474
|
+
const statsCtx = {
|
|
475
|
+
commandId: 'memory.stats',
|
|
476
|
+
params: { agentId },
|
|
477
|
+
resultTags: topCategories.slice(0, 3),
|
|
478
|
+
resultCount: entries.length,
|
|
479
|
+
};
|
|
480
|
+
const statsActions = (0, knowledge_core_1.generateNextActions)(statsCtx);
|
|
481
|
+
const statsBlock = (0, knowledge_core_1.formatActions)(statsActions);
|
|
482
|
+
if (statsBlock)
|
|
483
|
+
console.log(statsBlock);
|
|
484
|
+
}
|
|
485
|
+
tags(agentId) {
|
|
486
|
+
const entries = this.loadAllEntries(agentId);
|
|
487
|
+
if (entries.length === 0) {
|
|
488
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const byTag = {};
|
|
492
|
+
for (const entry of entries) {
|
|
493
|
+
for (const tag of entry.tags ?? []) {
|
|
494
|
+
byTag[tag] = (byTag[tag] ?? 0) + 1;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const sortedTags = Object.entries(byTag).sort((a, b) => b[1] - a[1]);
|
|
498
|
+
if (sortedTags.length === 0) {
|
|
499
|
+
console.log('νκ·Έκ° μμ΅λλ€.');
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
console.log(`## π·οΈ μ 체 νκ·Έ (${sortedTags.length}κ°)\n`);
|
|
503
|
+
for (const [tag, count] of sortedTags) {
|
|
504
|
+
console.log(` ${tag}: ${count}건`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
get(agentId, memoryId) {
|
|
508
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
509
|
+
if (!fs_1.default.existsSync(entriesDir)) {
|
|
510
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const files = fs_1.default.readdirSync(entriesDir).filter((f) => f.endsWith('.md'));
|
|
514
|
+
for (const file of files) {
|
|
515
|
+
const filePath = path_1.default.join(entriesDir, file);
|
|
516
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
517
|
+
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
518
|
+
if (data.id === memoryId) {
|
|
519
|
+
const d = data;
|
|
520
|
+
console.log(`## π ${d.summary}\n`);
|
|
521
|
+
console.log(`- ID: ${d.id}`);
|
|
522
|
+
console.log(`- λ μ§: ${d.date}`);
|
|
523
|
+
console.log(`- μΉ΄ν
κ³ λ¦¬: ${d.category ?? '-'}`);
|
|
524
|
+
console.log(`- νκ·Έ: ${(d.tags ?? []).join(', ') || '-'}`);
|
|
525
|
+
console.log(`- νμΌ: data/${agentId}/entries/${file}`);
|
|
526
|
+
console.log(`\n---\n`);
|
|
527
|
+
console.log(content.trim());
|
|
528
|
+
const graphPath = path_1.default.join(this.getAgentDir(agentId), 'graph.json');
|
|
529
|
+
if (fs_1.default.existsSync(graphPath)) {
|
|
530
|
+
try {
|
|
531
|
+
const graph = JSON.parse(fs_1.default.readFileSync(graphPath, 'utf-8'));
|
|
532
|
+
const relatedEdges = graph.edges
|
|
533
|
+
.filter((e) => e.from === memoryId || e.to === memoryId)
|
|
534
|
+
.sort((a, b) => b.weight - a.weight)
|
|
535
|
+
.slice(0, 5);
|
|
536
|
+
if (relatedEdges.length > 0) {
|
|
537
|
+
const nodeMap = {};
|
|
538
|
+
graph.nodes.forEach((n) => {
|
|
539
|
+
nodeMap[n.id] = n;
|
|
540
|
+
});
|
|
541
|
+
console.log(`\n---\n`);
|
|
542
|
+
console.log(`## π μ°κ΄ κΈ°μ΅ (${relatedEdges.length}κ°)\n`);
|
|
543
|
+
for (const edge of relatedEdges) {
|
|
544
|
+
const otherId = edge.from === memoryId ? edge.to : edge.from;
|
|
545
|
+
const other = nodeMap[otherId];
|
|
546
|
+
if (other) {
|
|
547
|
+
console.log(`[${edge.weight}] [${otherId}] ${other.summary.slice(0, 50)}...`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch (_e) {
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const getCtx = {
|
|
556
|
+
commandId: 'memory.get',
|
|
557
|
+
params: { agentId, id: memoryId },
|
|
558
|
+
resultTags: (d.tags ?? []).slice(0, 3),
|
|
559
|
+
resultIds: [memoryId],
|
|
560
|
+
resultCount: 1,
|
|
561
|
+
};
|
|
562
|
+
const getActions = (0, knowledge_core_1.generateNextActions)(getCtx);
|
|
563
|
+
const getBlock = (0, knowledge_core_1.formatActions)(getActions);
|
|
564
|
+
if (getBlock)
|
|
565
|
+
console.log(getBlock);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
console.log(`ID '${memoryId}'λ₯Ό μ°Ύμ μ μμ΅λλ€.`);
|
|
570
|
+
}
|
|
571
|
+
update(agentId, memoryId, options = {}) {
|
|
572
|
+
const entry = this.findEntryById(agentId, memoryId);
|
|
573
|
+
if (!entry) {
|
|
574
|
+
console.log(`β ID '${memoryId}'λ₯Ό μ°Ύμ μ μμ΅λλ€.`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
}
|
|
577
|
+
const { filePath, data, content } = entry;
|
|
578
|
+
const existingBody = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
579
|
+
const tags = Array.isArray(options.tags)
|
|
580
|
+
? options.tags
|
|
581
|
+
: options.tags ? options.tags.split(',').map((t) => t.trim()) : (data.tags ?? []);
|
|
582
|
+
const newData = {
|
|
583
|
+
...data,
|
|
584
|
+
summary: options.summary ?? data.summary,
|
|
585
|
+
category: options.category ?? data.category,
|
|
586
|
+
tags,
|
|
587
|
+
updated: this.getToday(),
|
|
588
|
+
};
|
|
589
|
+
if (options.important) {
|
|
590
|
+
newData['important'] = true;
|
|
591
|
+
}
|
|
592
|
+
else if (options['no-important']) {
|
|
593
|
+
delete newData['important'];
|
|
594
|
+
}
|
|
595
|
+
const newBody = options.body ?? existingBody ?? '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)';
|
|
596
|
+
const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
|
|
597
|
+
const newContent = `${newFrontmatter}\n\n# ${String(newData['summary'])}\n\n${newBody}\n`;
|
|
598
|
+
fs_1.default.writeFileSync(filePath, newContent, 'utf-8');
|
|
599
|
+
console.log(`β
μμ μλ£: ${entry.file}`);
|
|
600
|
+
console.log(` ID: ${memoryId}`);
|
|
601
|
+
if (options.summary)
|
|
602
|
+
console.log(` Summary: ${String(newData['summary'])}`);
|
|
603
|
+
if (options.category)
|
|
604
|
+
console.log(` Category: ${String(newData['category'])}`);
|
|
605
|
+
if (options.tags)
|
|
606
|
+
console.log(` Tags: ${newData['tags'].join(', ')}`);
|
|
607
|
+
if (options.body)
|
|
608
|
+
console.log(` Body: (μ
λ°μ΄νΈλ¨)`);
|
|
609
|
+
}
|
|
610
|
+
delete(agentId, memoryId, options = {}) {
|
|
611
|
+
const entry = this.findEntryById(agentId, memoryId);
|
|
612
|
+
if (!entry) {
|
|
613
|
+
console.log(`β ID '${memoryId}'λ₯Ό μ°Ύμ μ μμ΅λλ€.`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
if (!options.force) {
|
|
617
|
+
console.log(`β οΈ μμ λμ: [${entry.data.id}] ${entry.data.summary}`);
|
|
618
|
+
console.log(` νμΌ: ${entry.file}`);
|
|
619
|
+
console.log(`\n--force μ΅μ
μ μΆκ°νλ©΄ μμ λ©λλ€.`);
|
|
620
|
+
console.log(`μ: memory delete ${agentId} ${memoryId} --force`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
fs_1.default.unlinkSync(entry.filePath);
|
|
624
|
+
console.log(`ποΈ μμ μλ£: ${entry.file}`);
|
|
625
|
+
console.log(` ID: ${memoryId}`);
|
|
626
|
+
console.log(` Summary: ${entry.data.summary}`);
|
|
627
|
+
try {
|
|
628
|
+
this.markSummaryDirty(agentId);
|
|
629
|
+
console.log(` π summary.md κ°±μ μμ½λ¨`);
|
|
630
|
+
}
|
|
631
|
+
catch (_e) {
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
merge(agentId, memoryId1, memoryId2, options = {}) {
|
|
635
|
+
const entry1 = this.findEntryById(agentId, memoryId1);
|
|
636
|
+
const entry2 = this.findEntryById(agentId, memoryId2);
|
|
637
|
+
if (!entry1) {
|
|
638
|
+
console.log(`β ID '${memoryId1}'λ₯Ό μ°Ύμ μ μμ΅λλ€.`);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
if (!entry2) {
|
|
642
|
+
console.log(`β ID '${memoryId2}'λ₯Ό μ°Ύμ μ μμ΅λλ€.`);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
const body1 = entry1.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
646
|
+
const body2 = entry2.content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
647
|
+
const mergedSummary = options.summary ?? `${entry1.data.summary} + ${entry2.data.summary}`;
|
|
648
|
+
const mergedBody = `## λ³ν©λ κΈ°μ΅ 1 (${entry1.data.id}, ${entry1.data.date})
|
|
649
|
+
${entry1.data.summary}
|
|
650
|
+
|
|
651
|
+
${body1 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body1 : '(μμΈ μμ)'}
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## λ³ν©λ κΈ°μ΅ 2 (${entry2.data.id}, ${entry2.data.date})
|
|
656
|
+
${entry2.data.summary}
|
|
657
|
+
|
|
658
|
+
${body2 !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)' ? body2 : '(μμΈ μμ)'}`;
|
|
659
|
+
const mergedTags = [
|
|
660
|
+
...new Set([...(entry1.data.tags ?? []), ...(entry2.data.tags ?? [])]),
|
|
661
|
+
];
|
|
662
|
+
const newerDate = entry1.data.date > entry2.data.date ? entry1.data.date : entry2.data.date;
|
|
663
|
+
const newId = this.generateId();
|
|
664
|
+
const newData = {
|
|
665
|
+
id: newId,
|
|
666
|
+
type: 'memory',
|
|
667
|
+
date: newerDate,
|
|
668
|
+
category: entry1.data.category,
|
|
669
|
+
tags: mergedTags,
|
|
670
|
+
summary: mergedSummary,
|
|
671
|
+
merged_from: [memoryId1, memoryId2],
|
|
672
|
+
};
|
|
673
|
+
const filename = `${newId}.md`;
|
|
674
|
+
const filepath = path_1.default.join(this.getEntriesDir(agentId), filename);
|
|
675
|
+
const newFrontmatter = (0, parser_1.stringifyFrontmatter)(newData);
|
|
676
|
+
const newContent = `${newFrontmatter}\n\n# ${mergedSummary}\n\n${mergedBody}\n`;
|
|
677
|
+
fs_1.default.writeFileSync(filepath, newContent, 'utf-8');
|
|
678
|
+
fs_1.default.unlinkSync(entry1.filePath);
|
|
679
|
+
fs_1.default.unlinkSync(entry2.filePath);
|
|
680
|
+
console.log(`π λ³ν© μλ£!`);
|
|
681
|
+
console.log(` μ ID: ${newId}`);
|
|
682
|
+
console.log(` μ νμΌ: ${filename}`);
|
|
683
|
+
console.log(` λ³ν©λ κΈ°μ΅: [${memoryId1}] + [${memoryId2}]`);
|
|
684
|
+
console.log(` Summary: ${mergedSummary}`);
|
|
685
|
+
}
|
|
686
|
+
buildFallbackSummary(entries) {
|
|
687
|
+
const byTag = {};
|
|
688
|
+
for (const entry of entries) {
|
|
689
|
+
const tag = this.getPrimaryTag(entry);
|
|
690
|
+
if (!byTag[tag])
|
|
691
|
+
byTag[tag] = [];
|
|
692
|
+
byTag[tag].push(entry);
|
|
693
|
+
}
|
|
694
|
+
const toMD = (dateStr) => {
|
|
695
|
+
if (!dateStr)
|
|
696
|
+
return '-';
|
|
697
|
+
const normalized = String(dateStr).includes('T') ? dateStr : `${dateStr}T00:00:00Z`;
|
|
698
|
+
const d = new Date(normalized);
|
|
699
|
+
if (Number.isNaN(d.getTime()))
|
|
700
|
+
return '-';
|
|
701
|
+
return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}`;
|
|
702
|
+
};
|
|
703
|
+
let body = '';
|
|
704
|
+
for (const [topic, items] of Object.entries(byTag).sort()) {
|
|
705
|
+
const dates = items.map((i) => i.date).filter(Boolean).sort();
|
|
706
|
+
const oldest = dates[0];
|
|
707
|
+
const newest = dates[dates.length - 1];
|
|
708
|
+
const rangeLabel = oldest && newest ? `, ${toMD(oldest)}~${toMD(newest)}` : '';
|
|
709
|
+
body += `## ${topic} (${items.length}건${rangeLabel})\n`;
|
|
710
|
+
for (const e of items)
|
|
711
|
+
body += `- [${e.date}] ${e.summary}\n`;
|
|
712
|
+
for (const e of items)
|
|
713
|
+
body += `β [entries/${e.file}](entries/${e.file})\n`;
|
|
714
|
+
body += `\n`;
|
|
715
|
+
}
|
|
716
|
+
return body;
|
|
717
|
+
}
|
|
718
|
+
callSummarizer(prompt) {
|
|
719
|
+
const tmpFile = path_1.default.join(os_1.default.tmpdir(), `memory-summarizer-${Date.now()}.txt`);
|
|
720
|
+
try {
|
|
721
|
+
fs_1.default.writeFileSync(tmpFile, prompt, 'utf-8');
|
|
722
|
+
const result = (0, child_process_1.execSync)(`./crewx q "@memory_summarizer $(cat '${tmpFile}')"`, { encoding: 'utf-8', timeout: 60000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
723
|
+
return parseCrewxResponse(result);
|
|
724
|
+
}
|
|
725
|
+
catch (_e) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
finally {
|
|
729
|
+
try {
|
|
730
|
+
fs_1.default.unlinkSync(tmpFile);
|
|
731
|
+
}
|
|
732
|
+
catch (_e) {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
generateSummary(agentId, options = {}) {
|
|
737
|
+
const allEntries = this.loadAllEntries(agentId);
|
|
738
|
+
if (allEntries.length === 0)
|
|
739
|
+
return { status: 'empty' };
|
|
740
|
+
const cutoff = new Date(Date.now() - this.summaryDays * 24 * 60 * 60 * 1000)
|
|
741
|
+
.toISOString()
|
|
742
|
+
.slice(0, 10);
|
|
743
|
+
const entries = options.force
|
|
744
|
+
? allEntries
|
|
745
|
+
: allEntries.filter((e) => e.date >= cutoff);
|
|
746
|
+
if (entries.length === 0)
|
|
747
|
+
return { status: 'empty', entryCount: allEntries.length };
|
|
748
|
+
const summaryPath = this.getSummaryPath(agentId);
|
|
749
|
+
const existing = fs_1.default.existsSync(summaryPath)
|
|
750
|
+
? (0, parser_1.parseFrontmatter)(fs_1.default.readFileSync(summaryPath, 'utf-8'))
|
|
751
|
+
: null;
|
|
752
|
+
const lastEntryId = existing?.data?.last_entry_id ?? null;
|
|
753
|
+
const existingBody = existing?.content.trim() ?? null;
|
|
754
|
+
const getNewSince = (arr, lastId) => {
|
|
755
|
+
if (!lastId)
|
|
756
|
+
return arr;
|
|
757
|
+
const idx = arr.findIndex((e) => e.id === lastId);
|
|
758
|
+
if (idx === -1)
|
|
759
|
+
return arr;
|
|
760
|
+
return arr.slice(0, idx);
|
|
761
|
+
};
|
|
762
|
+
const newEntries = getNewSince(entries, lastEntryId);
|
|
763
|
+
if (newEntries.length === 0 && !options.force) {
|
|
764
|
+
return { status: 'up-to-date', entryCount: allEntries.length };
|
|
765
|
+
}
|
|
766
|
+
const entriesToProcess = options.force ? entries : newEntries;
|
|
767
|
+
const enriched = [...entriesToProcess].reverse().map((e) => {
|
|
768
|
+
if (e.file) {
|
|
769
|
+
try {
|
|
770
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
|
|
771
|
+
const { content } = (0, parser_1.parseFrontmatter)(raw);
|
|
772
|
+
const body = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
773
|
+
return { ...e, body };
|
|
774
|
+
}
|
|
775
|
+
catch (_e) {
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return e;
|
|
779
|
+
});
|
|
780
|
+
const allTopics = [...new Set(entries.map((e) => this.getPrimaryTag(e)))].sort();
|
|
781
|
+
const buildPrompt = (existBody, newItems, topics) => {
|
|
782
|
+
let p = `You are a memory summarizer for an AI agent.\n\n`;
|
|
783
|
+
if (existBody) {
|
|
784
|
+
p += `## Existing summary:\n${existBody}\n\n## New entries to incorporate:\n`;
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
p += `## All entries:\n`;
|
|
788
|
+
}
|
|
789
|
+
for (const entry of newItems) {
|
|
790
|
+
const importantTag = entry.important ? ' [important]' : '';
|
|
791
|
+
p += `- [${entry.id}] [${entry.date}] [${this.getPrimaryTag(entry)}]${importantTag} ${entry.summary}`;
|
|
792
|
+
if (entry.body && entry.body !== '(μμΈ λ΄μ©μ μ¬κΈ°μ μΆκ°)') {
|
|
793
|
+
p += ` β ${entry.body.slice(0, 200)}`;
|
|
794
|
+
}
|
|
795
|
+
p += ` (file: entries/${entry.file})\n`;
|
|
796
|
+
}
|
|
797
|
+
p += `\n## Instructions:\n`;
|
|
798
|
+
p += `- Group by topic, each topic as ## heading with entry count and date range, e.g. "## mcp-http (4건, 01/28~02/14)"\n`;
|
|
799
|
+
p += `- Write concise Korean summary per topic (2-5 sentences)\n`;
|
|
800
|
+
p += `- After each topic summary, list relevant entry file links as "β [entries/filename](entries/filename)"\n`;
|
|
801
|
+
p += `- Highlight key decisions, insights, and action items\n`;
|
|
802
|
+
p += `- If there's an existing summary, merge new information into it\n`;
|
|
803
|
+
p += `- Topics to cover: ${topics.join(', ')}\n`;
|
|
804
|
+
p += `- Output ONLY the markdown body (topic sections), no frontmatter or top-level heading\n`;
|
|
805
|
+
return p;
|
|
806
|
+
};
|
|
807
|
+
const prompt = buildPrompt(options.force ? null : existingBody, enriched, allTopics);
|
|
808
|
+
const summaryText = this.callSummarizer(prompt);
|
|
809
|
+
const newestEntryId = entries[0]?.id;
|
|
810
|
+
if (!summaryText) {
|
|
811
|
+
const allEnriched = entries.map((e) => {
|
|
812
|
+
if (e.file) {
|
|
813
|
+
try {
|
|
814
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(this.getEntriesDir(agentId), e.file), 'utf-8');
|
|
815
|
+
const { content } = (0, parser_1.parseFrontmatter)(raw);
|
|
816
|
+
const body = content.replace(/^#[^\n]*\n*/m, '').trim();
|
|
817
|
+
return { ...e, body };
|
|
818
|
+
}
|
|
819
|
+
catch (_e) {
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return e;
|
|
823
|
+
});
|
|
824
|
+
const fallbackBody = this.buildFallbackSummary(allEnriched);
|
|
825
|
+
const fm = (0, parser_1.stringifyFrontmatter)({
|
|
826
|
+
last_entry_id: newestEntryId,
|
|
827
|
+
last_updated: this.getToday(),
|
|
828
|
+
entry_count: allEntries.length,
|
|
829
|
+
summary_days: this.summaryDays,
|
|
830
|
+
summarizer: 'fallback',
|
|
831
|
+
});
|
|
832
|
+
fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${fallbackBody}`, 'utf-8');
|
|
833
|
+
return { status: 'fallback', entryCount: allEntries.length, newEntries: entriesToProcess.length };
|
|
834
|
+
}
|
|
835
|
+
const fm = (0, parser_1.stringifyFrontmatter)({
|
|
836
|
+
last_entry_id: newestEntryId,
|
|
837
|
+
last_updated: this.getToday(),
|
|
838
|
+
entry_count: allEntries.length,
|
|
839
|
+
summary_days: this.summaryDays,
|
|
840
|
+
summarizer: 'memory_summarizer',
|
|
841
|
+
});
|
|
842
|
+
fs_1.default.writeFileSync(summaryPath, `${fm}\n\n${summaryText}\n`, 'utf-8');
|
|
843
|
+
return { status: 'updated', entryCount: allEntries.length, newEntries: entriesToProcess.length };
|
|
844
|
+
}
|
|
845
|
+
summarize(agentId, options = {}) {
|
|
846
|
+
const entries = this.loadAllEntries(agentId);
|
|
847
|
+
if (entries.length === 0) {
|
|
848
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
console.log(`π μμ½ μμ± μμ (${entries.length}κ° μνΈλ¦¬)\n`);
|
|
852
|
+
const result = this.generateSummary(agentId, { force: options.force });
|
|
853
|
+
if (result.status === 'up-to-date') {
|
|
854
|
+
console.log('β
summary.md μ΅μ μνμ
λλ€.');
|
|
855
|
+
}
|
|
856
|
+
else if (result.status === 'fallback') {
|
|
857
|
+
console.log(`π ν΄λ°± μμ½ μμ± μλ£ (AI νΈμΆ μ€ν¨)`);
|
|
858
|
+
}
|
|
859
|
+
else if (result.status === 'updated') {
|
|
860
|
+
console.log(`π€ μμ½ μλ£ (${result.newEntries}건 λ°μ)`);
|
|
861
|
+
}
|
|
862
|
+
console.log(` νμΌ: data/${agentId}/summary.md`);
|
|
863
|
+
console.log(` μνΈλ¦¬: ${result.entryCount}κ°`);
|
|
864
|
+
}
|
|
865
|
+
summarizeDirty() {
|
|
866
|
+
if (!fs_1.default.existsSync(this.dataDir)) {
|
|
867
|
+
console.log(`π‘ λλ°μ΄μ€ cron λ±λ‘:`);
|
|
868
|
+
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const agentDirs = fs_1.default
|
|
872
|
+
.readdirSync(this.dataDir, { withFileTypes: true })
|
|
873
|
+
.filter((entry) => entry.isDirectory())
|
|
874
|
+
.map((entry) => entry.name);
|
|
875
|
+
for (const agentId of agentDirs) {
|
|
876
|
+
const dirtyPath = this.getDirtySummaryPath(agentId);
|
|
877
|
+
if (!fs_1.default.existsSync(dirtyPath))
|
|
878
|
+
continue;
|
|
879
|
+
try {
|
|
880
|
+
const result = this.generateSummary(agentId);
|
|
881
|
+
if (result.status === 'updated') {
|
|
882
|
+
console.log(`π€ ${agentId} summary.md κ°±μ (${result.newEntries}건 λ°μ)`);
|
|
883
|
+
}
|
|
884
|
+
else if (result.status === 'fallback') {
|
|
885
|
+
console.log(`π ${agentId} ν΄λ°± μμ½ μμ± μλ£ (AI νΈμΆ μ€ν¨)`);
|
|
886
|
+
}
|
|
887
|
+
else if (result.status === 'up-to-date') {
|
|
888
|
+
console.log(`β
${agentId} summary.md μ΅μ μνμ
λλ€.`);
|
|
889
|
+
}
|
|
890
|
+
else if (result.status === 'empty') {
|
|
891
|
+
console.log(`βΉοΈ ${agentId} μμ½ν κΈ°μ΅μ΄ μμ΅λλ€.`);
|
|
892
|
+
}
|
|
893
|
+
fs_1.default.unlinkSync(dirtyPath);
|
|
894
|
+
}
|
|
895
|
+
catch (e) {
|
|
896
|
+
console.error(`β ${agentId} summary.md κ°±μ μ€ν¨: ${e.message}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
console.log(`π‘ λλ°μ΄μ€ cron λ±λ‘:`);
|
|
900
|
+
console.log(`npx cron add "*/1 * * * *" "npx memory summarize-dirty" --mode command --name "memory-debounce"`);
|
|
901
|
+
}
|
|
902
|
+
search(agentId, query) {
|
|
903
|
+
const entries = this.loadAllEntries(agentId);
|
|
904
|
+
if (entries.length === 0) {
|
|
905
|
+
console.log('κΈ°μ΅μ΄ μμ΅λλ€.');
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const memoryList = entries
|
|
909
|
+
.map((e) => `[${e.id}] [${e.date}] [${this.getPrimaryTag(e)}] ${e.summary}`)
|
|
910
|
+
.join('\n');
|
|
911
|
+
const task = `κΈ°μ΅ λͺ©λ‘:\n${memoryList}\n\nμ§λ¬Έ: "${query}"`;
|
|
912
|
+
try {
|
|
913
|
+
console.log(`π "${query}" κ²μ μ€... (@memory_searcher)\n`);
|
|
914
|
+
const result = (0, child_process_1.execSync)(`./crewx q "@memory_searcher ${task.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf-8', timeout: 300000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
915
|
+
const parsed = parseCrewxResponse(result);
|
|
916
|
+
if (parsed) {
|
|
917
|
+
console.log(`## π§ μλ§¨ν± κ²μ κ²°κ³Ό\n`);
|
|
918
|
+
console.log(parsed);
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
console.log(result.trim());
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
const err = error;
|
|
926
|
+
if (err.code === 'ETIMEDOUT' || err.killed) {
|
|
927
|
+
console.log('κ²μ μ€ν¨: μκ° μ΄κ³Ό (μ΅λ 5λΆ)');
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
console.log('κ²μ μ€ν¨:', err.message);
|
|
931
|
+
}
|
|
932
|
+
console.log('\nπ‘ Tip: find λͺ
λ ΉμΌλ‘ ν€μλ κ²μμ μλν΄λ³΄μΈμ.');
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
exports.MemoryEngine = MemoryEngine;
|
|
937
|
+
function createMemoryEngine(config) {
|
|
938
|
+
return new MemoryEngine(config);
|
|
939
|
+
}
|
|
940
|
+
var types_1 = require("./types");
|
|
941
|
+
Object.defineProperty(exports, "CATEGORIES", { enumerable: true, get: function () { return types_1.CATEGORIES; } });
|
|
942
|
+
//# sourceMappingURL=engine.js.map
|