xiaozuoassistant 0.2.7 → 0.2.8

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.
Files changed (54) hide show
  1. package/dist/client/assets/browser-ponyfill-C7q-vgM7.js +2 -0
  2. package/dist/client/assets/index-DkOojrRj.js +201 -0
  3. package/dist/client/assets/index-u0lXmgyZ.css +1 -0
  4. package/dist/client/favicon.svg +4 -0
  5. package/dist/client/index.html +14 -0
  6. package/dist/client/locales/en/translation.json +110 -0
  7. package/dist/client/locales/zh/translation.json +112 -0
  8. package/dist/server/agents/office.js +23 -0
  9. package/dist/server/app.js +50 -0
  10. package/dist/server/channels/base-channel.js +23 -0
  11. package/dist/server/channels/create-channels.js +18 -0
  12. package/dist/server/channels/dingtalk.js +83 -0
  13. package/dist/server/channels/feishu.js +108 -0
  14. package/dist/server/channels/telegram.js +53 -0
  15. package/dist/server/channels/terminal.js +49 -0
  16. package/dist/server/channels/web.js +66 -0
  17. package/dist/server/channels/wechat.js +107 -0
  18. package/dist/server/config/loader.js +96 -0
  19. package/dist/server/config/paths.js +24 -0
  20. package/dist/server/config/prompts.js +12 -0
  21. package/dist/server/core/agents/manager.js +27 -0
  22. package/dist/server/core/agents/runtime.js +92 -0
  23. package/dist/server/core/brain.js +255 -0
  24. package/dist/server/core/event-bus.js +24 -0
  25. package/dist/server/core/logger.js +71 -0
  26. package/dist/server/core/memories/manager.js +238 -0
  27. package/dist/server/core/memories/short-term.js +512 -0
  28. package/dist/server/core/memories/structured.js +357 -0
  29. package/dist/server/core/memories/vector.js +137 -0
  30. package/dist/server/core/memory.js +2 -0
  31. package/dist/server/core/plugin-manager.js +128 -0
  32. package/dist/server/core/plugin.js +1 -0
  33. package/dist/server/core/scheduler.js +85 -0
  34. package/dist/server/core/task-queue.js +104 -0
  35. package/dist/server/core/types.js +1 -0
  36. package/dist/server/index.js +862 -0
  37. package/dist/server/llm/openai.js +23 -0
  38. package/dist/server/plugins/core-skills/src/create-agent.js +58 -0
  39. package/dist/server/plugins/core-skills/src/delegate.js +39 -0
  40. package/dist/server/plugins/core-skills/src/file-system.js +142 -0
  41. package/dist/server/plugins/core-skills/src/index.js +26 -0
  42. package/dist/server/plugins/core-skills/src/list-agents.js +24 -0
  43. package/dist/server/plugins/core-skills/src/search.js +31 -0
  44. package/dist/server/plugins/core-skills/src/system-time.js +27 -0
  45. package/dist/server/plugins/office-skills/src/index.js +19 -0
  46. package/dist/server/plugins/office-skills/src/office-excel.js +84 -0
  47. package/dist/server/plugins/office-skills/src/office-ppt.js +58 -0
  48. package/dist/server/plugins/office-skills/src/office-word.js +90 -0
  49. package/dist/server/routes/auth.js +28 -0
  50. package/dist/server/server/create-http.js +22 -0
  51. package/dist/server/server.js +29 -0
  52. package/dist/server/skills/base-skill.js +20 -0
  53. package/dist/server/skills/registry.js +52 -0
  54. package/package.json +1 -1
@@ -0,0 +1,512 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ const SESSIONS_DIR = path.resolve(process.cwd(), 'sessions');
5
+ const INDEX_FILE = path.join(SESSIONS_DIR, 'index.json');
6
+ function defaultWorkspacePath() {
7
+ return path.resolve(process.cwd(), 'workspace');
8
+ }
9
+ function normalizeWorkspace(input) {
10
+ const workspace = (input ?? '').trim();
11
+ if (!workspace)
12
+ return defaultWorkspacePath();
13
+ return path.resolve(workspace);
14
+ }
15
+ export class ShortTermMemory {
16
+ constructor() {
17
+ this.instanceId = Math.random().toString(36).substring(7);
18
+ // 主存:所有 Session Meta 数据
19
+ this.sessions = new Map();
20
+ this.initPromise = null;
21
+ // 持久化队列,确保串行写入 index.json
22
+ this.saveQueue = Promise.resolve();
23
+ // Session级写入队列,避免messages.json并发冲突
24
+ this.sessionQueues = new Map();
25
+ console.log(`[ShortTermMemory] Instance created: ${this.instanceId}`);
26
+ fs.ensureDirSync(SESSIONS_DIR);
27
+ }
28
+ static getInstance() {
29
+ if (!ShortTermMemory.instance) {
30
+ ShortTermMemory.instance = new ShortTermMemory();
31
+ }
32
+ return ShortTermMemory.instance;
33
+ }
34
+ async ensureReady() {
35
+ if (!this.initPromise) {
36
+ this.initPromise = this.init();
37
+ }
38
+ await this.initPromise;
39
+ }
40
+ async init() {
41
+ try {
42
+ await fs.ensureDir(SESSIONS_DIR);
43
+ const indexExists = await fs.pathExists(INDEX_FILE);
44
+ if (indexExists) {
45
+ await this.loadSessionsFromDisk();
46
+ }
47
+ else {
48
+ await this.migrateLegacyIfNeeded();
49
+ // 迁移后再次尝试加载,如果还没有则初始化为空
50
+ if (await fs.pathExists(INDEX_FILE)) {
51
+ await this.loadSessionsFromDisk();
52
+ }
53
+ else {
54
+ this.sessions.clear();
55
+ await this.persistIndex();
56
+ }
57
+ }
58
+ console.log(`[ShortTermMemory] Initialized with ${this.sessions.size} sessions.`);
59
+ }
60
+ catch (e) {
61
+ console.error('[ShortTermMemory] Initialization failed:', e);
62
+ // 即使失败,sessions 也是空的 Map,不会崩溃,但数据可能丢失
63
+ }
64
+ }
65
+ async loadSessionsFromDisk() {
66
+ try {
67
+ // Invalidate cache by reading file directly
68
+ const content = await fs.readFile(INDEX_FILE, 'utf-8');
69
+ const data = JSON.parse(content);
70
+ console.log(`[ShortTermMemory:${this.instanceId}] Loaded ${Array.isArray(data) ? data.length : 0} sessions from disk.`);
71
+ // MERGE instead of replace to prevent wiping out pending changes in current process
72
+ if (Array.isArray(data)) {
73
+ for (const meta of data) {
74
+ if (meta && typeof meta.id === 'string') {
75
+ // Only update if not exists or if disk version is newer (optional, but for now just trusting disk for existence)
76
+ // But wait, if we have a pending write, our memory version is newer.
77
+ // However, we are reloading because we are MISSING a session.
78
+ // So we should add missing ones.
79
+ if (!this.sessions.has(meta.id)) {
80
+ this.sessions.set(meta.id, meta);
81
+ }
82
+ else {
83
+ // If it exists, should we update?
84
+ // If we update, we might overwrite in-memory changes that haven't been flushed.
85
+ // Safest is to only ADD missing ones.
86
+ // But if another process updated a session, we want that update.
87
+ // Complex conflict resolution needed?
88
+ // For now, let's assume if we are reloading, it's because we need data.
89
+ // Let's just add missing ones.
90
+ // NO, wait. If we don't update existing ones, we miss updates from other processes.
91
+ // But if we overwrite, we lose our own updates.
92
+ // Ideally, we only reload when we *know* we are missing something or on init.
93
+ // Compromise: Update everything. Why? Because `persistIndex` queues writes.
94
+ // If we have a pending write, it will overwrite disk later anyway.
95
+ // So momentarily reverting to disk state is fine, as long as our write queue eventually flushes OUR state.
96
+ this.sessions.set(meta.id, meta);
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ catch (e) {
103
+ console.error(`[ShortTermMemory:${this.instanceId}] Failed to load index.json:`, e);
104
+ }
105
+ }
106
+ async persistIndex() {
107
+ // 将当前所有 sessions 转为数组并排序
108
+ const metas = Array.from(this.sessions.values());
109
+ metas.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
110
+ // 加入写入队列
111
+ this.saveQueue = this.saveQueue.then(async () => {
112
+ try {
113
+ await fs.writeJson(INDEX_FILE, metas, { spaces: 2 });
114
+ }
115
+ catch (e) {
116
+ console.error('[ShortTermMemory] Failed to persist index.json:', e);
117
+ }
118
+ });
119
+ return this.saveQueue;
120
+ }
121
+ getSessionDir(sessionId) {
122
+ return path.join(SESSIONS_DIR, sessionId);
123
+ }
124
+ getMessagesFile(sessionId) {
125
+ return path.join(this.getSessionDir(sessionId), 'messages.json');
126
+ }
127
+ getContextFile(sessionId) {
128
+ return path.join(this.getSessionDir(sessionId), 'context.txt');
129
+ }
130
+ getRunsFile(sessionId) {
131
+ return path.join(this.getSessionDir(sessionId), 'runs.json');
132
+ }
133
+ async migrateLegacyIfNeeded() {
134
+ const files = await fs.readdir(SESSIONS_DIR);
135
+ const legacyFiles = files.filter(f => f.endsWith('.json') && f !== 'index.json');
136
+ if (legacyFiles.length === 0)
137
+ return;
138
+ console.log('[ShortTermMemory] Migrating legacy sessions...');
139
+ const metas = [];
140
+ for (const file of legacyFiles) {
141
+ const filePath = path.join(SESSIONS_DIR, file);
142
+ try {
143
+ const legacy = await fs.readJson(filePath);
144
+ if (!legacy || typeof legacy.id !== 'string')
145
+ continue;
146
+ const id = legacy.id;
147
+ const createdAt = typeof legacy.createdAt === 'number' ? legacy.createdAt : Date.now();
148
+ const updatedAt = typeof legacy.updatedAt === 'number' ? legacy.updatedAt : Date.now();
149
+ const messages = Array.isArray(legacy.messages) ? legacy.messages : [];
150
+ const meta = {
151
+ id,
152
+ createdAt,
153
+ updatedAt,
154
+ lastActiveAt: updatedAt,
155
+ workspace: defaultWorkspacePath()
156
+ };
157
+ const sessionDir = this.getSessionDir(id);
158
+ await fs.ensureDir(sessionDir);
159
+ await fs.writeJson(this.getMessagesFile(id), messages, { spaces: 2 });
160
+ metas.push(meta);
161
+ }
162
+ catch (e) {
163
+ console.error(`[ShortTermMemory] Legacy migration failed for ${file}:`, e);
164
+ }
165
+ }
166
+ if (metas.length > 0) {
167
+ metas.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
168
+ for (const m of metas) {
169
+ this.sessions.set(m.id, m);
170
+ }
171
+ await this.persistIndex();
172
+ }
173
+ }
174
+ async createSession(input) {
175
+ await this.ensureReady();
176
+ const id = input?.id || uuidv4();
177
+ if (this.sessions.has(id)) {
178
+ // If session already exists, return it (idempotent for external channels)
179
+ const session = await this.getSession(id);
180
+ if (session)
181
+ return session;
182
+ }
183
+ console.log(`[ShortTermMemory:${this.instanceId}] Creating session: ${id}`);
184
+ const now = Date.now();
185
+ const meta = {
186
+ id,
187
+ alias: (input?.alias ?? '').trim() || undefined,
188
+ workspace: normalizeWorkspace(input?.workspace),
189
+ createdAt: now,
190
+ updatedAt: now,
191
+ lastActiveAt: now,
192
+ projectId: input?.projectId,
193
+ projectIds: input?.projectIds,
194
+ notebookId: input?.notebookId,
195
+ lastArchivedAt: now,
196
+ };
197
+ await fs.ensureDir(meta.workspace);
198
+ const sessionDir = this.getSessionDir(id);
199
+ await fs.ensureDir(sessionDir);
200
+ await fs.writeJson(this.getMessagesFile(id), [], { spaces: 2 });
201
+ // IMPORTANT: Reload from disk BEFORE adding new session to avoid overwriting other processes' changes
202
+ await this.loadSessionsFromDisk();
203
+ // 更新内存
204
+ this.sessions.set(id, meta);
205
+ // 异步持久化
206
+ await this.persistIndex();
207
+ console.log(`[ShortTermMemory:${this.instanceId}] Session created and persisted: ${id}`);
208
+ return { meta, messages: [] };
209
+ }
210
+ async listSessions() {
211
+ await this.ensureReady();
212
+ const metas = Array.from(this.sessions.values());
213
+ metas.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
214
+ return metas;
215
+ }
216
+ async getSessionMeta(id) {
217
+ await this.ensureReady();
218
+ let meta = this.sessions.get(id) || null;
219
+ // 如果内存中没有,尝试重新加载磁盘(可能是其他进程创建的)
220
+ if (!meta) {
221
+ console.log(`[ShortTermMemory:${this.instanceId}] Cache miss for ${id}, reloading from disk...`);
222
+ await this.loadSessionsFromDisk();
223
+ meta = this.sessions.get(id) || null;
224
+ }
225
+ if (!meta) {
226
+ console.log(`[ShortTermMemory:${this.instanceId}] Session not found in memory after reload: ${id}. Total sessions: ${this.sessions.size}`);
227
+ // Log all available IDs for debugging
228
+ // console.log(`[ShortTermMemory:${this.instanceId}] Available IDs: ${Array.from(this.sessions.keys()).join(', ')}`);
229
+ }
230
+ return meta;
231
+ }
232
+ async updateSessionMeta(id, patch) {
233
+ await this.ensureReady();
234
+ const current = this.sessions.get(id);
235
+ if (!current)
236
+ throw new Error('Session not found');
237
+ const next = {
238
+ ...current,
239
+ alias: patch.alias === null ? undefined : (patch.alias !== undefined ? (String(patch.alias).trim() || undefined) : current.alias),
240
+ workspace: patch.workspace !== undefined ? normalizeWorkspace(String(patch.workspace)) : current.workspace,
241
+ projectId: patch.projectId === null ? undefined : (patch.projectId !== undefined ? patch.projectId : current.projectId),
242
+ projectIds: patch.projectIds === null ? undefined : (patch.projectIds !== undefined ? patch.projectIds : current.projectIds),
243
+ notebookId: patch.notebookId === null ? undefined : (patch.notebookId !== undefined ? patch.notebookId : current.notebookId),
244
+ lastArchivedAt: patch.lastArchivedAt !== undefined ? patch.lastArchivedAt : current.lastArchivedAt,
245
+ updatedAt: Date.now()
246
+ };
247
+ await fs.ensureDir(next.workspace);
248
+ if (patch.context !== undefined) {
249
+ const ctxFile = this.getContextFile(id);
250
+ const val = String(patch.context ?? '').trim();
251
+ if (!val) {
252
+ try {
253
+ await fs.remove(ctxFile);
254
+ }
255
+ catch { /* ignore */ }
256
+ delete next.context;
257
+ }
258
+ else {
259
+ await fs.ensureDir(this.getSessionDir(id));
260
+ await fs.writeFile(ctxFile, val, 'utf-8');
261
+ next.context = val;
262
+ }
263
+ }
264
+ // 更新内存并持久化
265
+ this.sessions.set(id, next);
266
+ await this.persistIndex();
267
+ return next;
268
+ }
269
+ async persistSession(id) {
270
+ await this.ensureReady();
271
+ const current = this.sessions.get(id);
272
+ if (!current)
273
+ throw new Error('Session not found');
274
+ const now = Date.now();
275
+ let context = '';
276
+ try {
277
+ const ctxFile = this.getContextFile(id);
278
+ if (await fs.pathExists(ctxFile)) {
279
+ context = String(await fs.readFile(ctxFile, 'utf-8') || '').trim();
280
+ }
281
+ }
282
+ catch {
283
+ // ignore
284
+ }
285
+ const next = { ...current, context: context || undefined, updatedAt: now, lastActiveAt: now };
286
+ this.sessions.set(id, next);
287
+ await this.persistIndex();
288
+ await fs.ensureDir(this.getSessionDir(id));
289
+ await fs.ensureFile(this.getMessagesFile(id));
290
+ await fs.ensureFile(this.getRunsFile(id));
291
+ return next;
292
+ }
293
+ async getSession(id) {
294
+ const meta = await this.getSessionMeta(id);
295
+ if (!meta)
296
+ return null;
297
+ const msgFile = this.getMessagesFile(id);
298
+ let messages = [];
299
+ try {
300
+ const data = await fs.readJson(msgFile);
301
+ messages = Array.isArray(data) ? data : [];
302
+ }
303
+ catch {
304
+ // 如果消息文件不存在或读取失败,尝试重新创建
305
+ await fs.ensureDir(this.getSessionDir(id));
306
+ await fs.writeJson(msgFile, [], { spaces: 2 });
307
+ messages = [];
308
+ }
309
+ let context = '';
310
+ try {
311
+ const ctxFile = this.getContextFile(id);
312
+ if (await fs.pathExists(ctxFile)) {
313
+ context = String(await fs.readFile(ctxFile, 'utf-8') || '').trim();
314
+ }
315
+ }
316
+ catch {
317
+ // ignore
318
+ }
319
+ return { meta: { ...meta, context: context || undefined }, messages };
320
+ }
321
+ async createRun(sessionId, userContent, runId) {
322
+ await this.ensureReady();
323
+ // Check if session exists, try reload if not
324
+ if (!this.sessions.has(sessionId)) {
325
+ console.log(`[ShortTermMemory:${this.instanceId}] Session ${sessionId} not found for createRun, reloading...`);
326
+ await this.loadSessionsFromDisk();
327
+ }
328
+ if (!this.sessions.has(sessionId))
329
+ throw new Error('Session not found');
330
+ const now = Date.now();
331
+ const id = runId || uuidv4();
332
+ const run = {
333
+ id,
334
+ sessionId,
335
+ status: 'queued',
336
+ userContent,
337
+ createdAt: now,
338
+ updatedAt: now
339
+ };
340
+ const file = this.getRunsFile(sessionId);
341
+ let runs = [];
342
+ try {
343
+ const existing = await fs.readJson(file);
344
+ runs = Array.isArray(existing) ? existing : [];
345
+ }
346
+ catch {
347
+ await fs.ensureDir(this.getSessionDir(sessionId));
348
+ }
349
+ runs.push(run);
350
+ await fs.writeJson(file, runs, { spaces: 2 });
351
+ return run;
352
+ }
353
+ async updateRun(sessionId, runId, patch) {
354
+ await this.ensureReady();
355
+ const file = this.getRunsFile(sessionId);
356
+ let runs = [];
357
+ try {
358
+ const existing = await fs.readJson(file);
359
+ runs = Array.isArray(existing) ? existing : [];
360
+ }
361
+ catch {
362
+ runs = [];
363
+ }
364
+ const idx = runs.findIndex(r => r.id === runId);
365
+ if (idx < 0)
366
+ throw new Error('Run not found');
367
+ const next = {
368
+ ...runs[idx],
369
+ ...patch,
370
+ status: patch.status || runs[idx].status,
371
+ updatedAt: patch.updatedAt ?? Date.now()
372
+ };
373
+ runs[idx] = next;
374
+ await fs.writeJson(file, runs, { spaces: 2 });
375
+ return next;
376
+ }
377
+ async listRuns(sessionId) {
378
+ await this.ensureReady();
379
+ const file = this.getRunsFile(sessionId);
380
+ try {
381
+ const existing = await fs.readJson(file);
382
+ const runs = Array.isArray(existing) ? existing : [];
383
+ runs.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
384
+ return runs;
385
+ }
386
+ catch {
387
+ return [];
388
+ }
389
+ }
390
+ async getMessages(sessionId) {
391
+ // 复用 getSession 逻辑,它包含了文件读取和错误处理
392
+ const session = await this.getSession(sessionId);
393
+ return session ? session.messages : [];
394
+ }
395
+ async addMessage(sessionId, message) {
396
+ await this.ensureReady();
397
+ let sessionMeta = this.sessions.get(sessionId);
398
+ // 如果内存中没有,尝试重载
399
+ if (!sessionMeta) {
400
+ console.log(`[ShortTermMemory:${this.instanceId}] Session ${sessionId} not found in memory for addMessage, reloading...`);
401
+ await this.loadSessionsFromDisk();
402
+ sessionMeta = this.sessions.get(sessionId);
403
+ }
404
+ // 如果仍然没有,尝试直接从 index.json 读取一次,绕过缓存逻辑
405
+ if (!sessionMeta) {
406
+ try {
407
+ const content = await fs.readFile(INDEX_FILE, 'utf-8');
408
+ const data = JSON.parse(content);
409
+ const found = Array.isArray(data) ? data.find((m) => m.id === sessionId) : null;
410
+ if (found) {
411
+ console.log(`[ShortTermMemory:${this.instanceId}] Session ${sessionId} found in fresh disk read, recovering...`);
412
+ this.sessions.set(found.id, found);
413
+ sessionMeta = found;
414
+ }
415
+ }
416
+ catch (e) {
417
+ console.error(`[ShortTermMemory:${this.instanceId}] Emergency disk read failed:`, e);
418
+ }
419
+ }
420
+ if (!sessionMeta) {
421
+ console.error(`[ShortTermMemory:${this.instanceId}] Session ${sessionId} not found even after emergency reload.`);
422
+ throw new Error(`Session ${sessionId} not found`);
423
+ }
424
+ const msg = { ...message };
425
+ if (!msg.timestamp)
426
+ msg.timestamp = Date.now();
427
+ // 使用队列确保对 messages.json 的串行写入
428
+ const queue = this.sessionQueues.get(sessionId) || Promise.resolve();
429
+ const nextTask = queue.then(async () => {
430
+ // 更新 messages.json
431
+ const msgFile = this.getMessagesFile(sessionId);
432
+ let messages = [];
433
+ try {
434
+ const data = await fs.readJson(msgFile);
435
+ messages = Array.isArray(data) ? data : [];
436
+ }
437
+ catch {
438
+ messages = [];
439
+ }
440
+ messages.push(msg);
441
+ await fs.writeJson(msgFile, messages, { spaces: 2 });
442
+ }).catch(e => {
443
+ console.error(`[ShortTermMemory] Failed to write message for session ${sessionId}:`, e);
444
+ });
445
+ this.sessionQueues.set(sessionId, nextTask);
446
+ // 等待本次写入完成
447
+ await nextTask;
448
+ // 清理已完成的队列(可选,防止 Map 无限增长,但需小心并发)
449
+ // 简单策略:不清理,或者定期清理。由于 Session 数量有限,暂时保留。
450
+ // 更新 Session Meta
451
+ const nextMeta = {
452
+ ...sessionMeta,
453
+ updatedAt: Date.now(),
454
+ lastActiveAt: Date.now()
455
+ };
456
+ this.sessions.set(sessionId, nextMeta);
457
+ await this.persistIndex();
458
+ }
459
+ async clearMessages(sessionId) {
460
+ await this.ensureReady();
461
+ const sessionMeta = this.sessions.get(sessionId);
462
+ if (!sessionMeta)
463
+ throw new Error(`Session ${sessionId} not found`);
464
+ await fs.writeJson(this.getMessagesFile(sessionId), [], { spaces: 2 });
465
+ // Update timestamp
466
+ const nextMeta = {
467
+ ...sessionMeta,
468
+ updatedAt: Date.now(),
469
+ lastActiveAt: Date.now()
470
+ };
471
+ this.sessions.set(sessionId, nextMeta);
472
+ await this.persistIndex();
473
+ }
474
+ async deleteSession(id) {
475
+ await this.ensureReady();
476
+ if (!this.sessions.has(id))
477
+ throw new Error('Session not found');
478
+ this.sessions.delete(id);
479
+ await this.persistIndex();
480
+ try {
481
+ await fs.remove(this.getSessionDir(id));
482
+ }
483
+ catch (e) {
484
+ console.error(`[ShortTermMemory] Failed to delete session dir ${id}:`, e);
485
+ }
486
+ }
487
+ async cleanupOldSessions(maxAgeDays) {
488
+ await this.ensureReady();
489
+ const now = Date.now();
490
+ const msPerDay = 24 * 60 * 60 * 1000;
491
+ const toDelete = [];
492
+ for (const meta of this.sessions.values()) {
493
+ const updatedAt = typeof meta.updatedAt === 'number' ? meta.updatedAt : 0;
494
+ const ageDays = (now - updatedAt) / msPerDay;
495
+ if (ageDays > maxAgeDays)
496
+ toDelete.push(meta.id);
497
+ }
498
+ for (const id of toDelete) {
499
+ this.sessions.delete(id);
500
+ try {
501
+ await fs.remove(this.getSessionDir(id));
502
+ }
503
+ catch (e) {
504
+ console.error(`[ShortTermMemory] Failed to delete session ${id}:`, e);
505
+ }
506
+ }
507
+ if (toDelete.length > 0) {
508
+ await this.persistIndex();
509
+ }
510
+ return toDelete.length;
511
+ }
512
+ }