foliko 1.1.35 → 1.1.36

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.
@@ -203,7 +203,10 @@
203
203
  "Bash(node -e \"\nconst poster = require\\('/d/code/foliko-plugins/poster-plugin'\\);\nconsole.log\\('poster.tools:', typeof poster.tools\\);\nconsole.log\\('poster.tools keys:', Object.keys\\(poster.tools || {}\\).slice\\(0, 5\\)\\);\n\" 2>&1)",
204
204
  "Bash(node -e \"require\\('./src/core/agent-chat.js'\\); console.log\\('OK'\\)\" 2>&1)",
205
205
  "Bash(node -e \"const ai = require\\('ai'\\); console.log\\('tool type:', typeof ai.tool\\)\")",
206
- "mcp__MiniMax__web_search"
206
+ "mcp__MiniMax__web_search",
207
+ "Bash(npm test:*)",
208
+ "Bash(node -e \"require\\('./src/index.js'\\); console.log\\('Module loaded successfully'\\)\")",
209
+ "Bash(node examples/basic.js 2>&1)"
207
210
  ]
208
211
  }
209
212
  }
@@ -95,7 +95,7 @@ class ChatUI {
95
95
  printWelcome() {
96
96
  console.log(`${colored('Foliko', CYAN)} - 持续对话聊天`);
97
97
  console.log(
98
- `${colored('Ctrl+C', DIM)} 退出 | ${colored('连续两次回车', DIM)} 发送多行 | ${colored('!!', DIM)} 立即发送\n`
98
+ `${colored('Ctrl+C', DIM)} 退出 | ${colored('/clean', DIM)} 清除上下文 | ${colored('连续两次回车', DIM)} 发送多行 | ${colored('!!', DIM)} 立即发送\n`
99
99
  );
100
100
  }
101
101
 
@@ -152,6 +152,34 @@ class ChatUI {
152
152
  return;
153
153
  }
154
154
 
155
+ // 清除上下文命令
156
+ if (input.toLowerCase() === '/clean' || input.toLowerCase() === '/clear') {
157
+ const { sessionId } = this;
158
+ // 清除 AgentChatHandler 的消息存储(需要传 sessionId)
159
+ if (this.agent._chatHandler) {
160
+ this.agent._chatHandler.clearHistory(sessionId);
161
+ }
162
+ // 清除 SessionContext 的消息和压缩计数
163
+ if (this.agent.framework) {
164
+ const sessionCtx = this.agent.framework.getSessionContext(sessionId);
165
+ if (sessionCtx) {
166
+ sessionCtx.clearMessages();
167
+ sessionCtx.compressionState.count = 0;
168
+ sessionCtx.metadata.compressionCount = 0;
169
+ }
170
+ // 同步清除 framework 缓存的 sessionContexts
171
+ const cachedCtx = this.agent.framework._sessionContexts?.get(sessionId);
172
+ if (cachedCtx) {
173
+ cachedCtx.clearMessages();
174
+ cachedCtx.compressionState.count = 0;
175
+ cachedCtx.metadata.compressionCount = 0;
176
+ }
177
+ }
178
+ console.log(`${colored('[提示]', CYAN)} 对话上下文已清除\n`);
179
+ await this.promptUser();
180
+ return;
181
+ }
182
+
155
183
  // 发送消息
156
184
  await this.sendMessage(input);
157
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.35",
3
+ "version": "1.1.36",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -192,12 +192,13 @@ class FeishuPlugin extends Plugin {
192
192
  case 'start':
193
193
  case 'help':
194
194
  await this._sendMessage(openId,
195
- '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
195
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/history - 查看历史消息数',
196
196
  originalMsg)
197
197
  break
198
198
  case 'clear':
199
- this._clearSession(openId)
200
- await this._sendMessage(openId, '✅ 对话历史已清除', originalMsg)
199
+ case 'clean':
200
+ await this._clearSessionContext(openId)
201
+ await this._sendMessage(openId, '✅ 对话上下文已清除', originalMsg)
201
202
  break
202
203
  case 'history':
203
204
  if (this._sessionPlugin) {
@@ -442,6 +443,38 @@ class FeishuPlugin extends Plugin {
442
443
  }
443
444
  }
444
445
 
446
+ /**
447
+ * 清除会话上下文(清除消息历史和压缩计数)
448
+ */
449
+ async _clearSessionContext(openId) {
450
+ const sessionId = `feishu_${openId}`
451
+
452
+ // 获取或创建 sessionAgent
453
+ const { agent } = this._getSessionAgent(openId)
454
+
455
+ // 清除 AgentChatHandler 的消息存储(需要传 sessionId)
456
+ if (agent._chatHandler) {
457
+ agent._chatHandler.clearHistory(sessionId)
458
+ }
459
+
460
+ // 清除 SessionContext 的消息和压缩计数
461
+ if (this._framework) {
462
+ const sessionCtx = this._framework.getSessionContext(sessionId)
463
+ if (sessionCtx) {
464
+ sessionCtx.clearMessages()
465
+ sessionCtx.compressionState.count = 0
466
+ sessionCtx.metadata.compressionCount = 0
467
+ }
468
+ // 同步清除 framework 缓存的 sessionContexts
469
+ const cachedCtx = this._framework._sessionContexts?.get(sessionId)
470
+ if (cachedCtx) {
471
+ cachedCtx.clearMessages()
472
+ cachedCtx.compressionState.count = 0
473
+ cachedCtx.metadata.compressionCount = 0
474
+ }
475
+ }
476
+ }
477
+
445
478
  getStatus() {
446
479
  let sessions = []
447
480
  if (this._sessionPlugin) {
@@ -302,20 +302,24 @@ class TelegramPlugin extends Plugin {
302
302
 
303
303
  switch (command.toLowerCase()) {
304
304
  case 'start':
305
+ case 'help':
305
306
  await this._sendMessage(chatId,
306
- '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clear - 清除对话历史\n/history - 查看历史消息数',
307
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/history - 查看历史消息数',
307
308
  msg.message_id)
308
309
  break
310
+ case 'clean':
309
311
  case 'clear':
310
- this._clearSession(chatId)
311
- await this._sendMessage(chatId, '✅ 对话历史已清除', msg.message_id)
312
+ await this._clearSessionContext(chatId)
313
+ await this._sendMessage(chatId, '✅ 对话上下文已清除', msg.message_id)
312
314
  break
313
315
  case 'history':
314
- if (this._sessionPlugin) {
315
- const session = this._sessionPlugin.getSession(`telegram_${chatId}`)
316
- const sessions = this._sessionPlugin.listSessions().filter(s => s.id.startsWith('telegram_'))
316
+ const sessionInfo = this._getSessionAgent(chatId)
317
+ if (sessionInfo && this._framework) {
318
+ const sessionCtx = this._framework.getSessionContext(sessionInfo.sessionId)
319
+ const count = sessionCtx?.messageStore?.messages?.length || 0
320
+ const compressionCount = sessionCtx?.metadata?.compressionCount || 0
317
321
  await this._sendMessage(chatId,
318
- `📊 当前状态\n\n会话数:${sessions.length}\n历史消息:${session?.messages?.length || 0}\n最后活跃:${session?.lastActive?.toLocaleString() || '无'}`,
322
+ `📊 当前状态\n\n历史消息:${count}\n压缩次数:${compressionCount}`,
319
323
  msg.message_id)
320
324
  } else {
321
325
  await this._sendMessage(chatId, '❌ 会话服务不可用', msg.message_id)
@@ -452,6 +456,38 @@ class TelegramPlugin extends Plugin {
452
456
  }
453
457
  }
454
458
 
459
+ /**
460
+ * 清除会话上下文(清除消息历史和压缩计数)
461
+ */
462
+ async _clearSessionContext(chatId) {
463
+ const sessionId = `telegram_${chatId}`
464
+
465
+ // 获取或创建 sessionAgent
466
+ const { agent } = this._getSessionAgent(chatId)
467
+
468
+ // 清除 AgentChatHandler 的消息存储(需要传 sessionId)
469
+ if (agent._chatHandler) {
470
+ agent._chatHandler.clearHistory(sessionId)
471
+ }
472
+
473
+ // 清除 SessionContext 的消息和压缩计数
474
+ if (this._framework) {
475
+ const sessionCtx = this._framework.getSessionContext(sessionId)
476
+ if (sessionCtx) {
477
+ sessionCtx.clearMessages()
478
+ sessionCtx.compressionState.count = 0
479
+ sessionCtx.metadata.compressionCount = 0
480
+ }
481
+ // 同步清除 framework 缓存的 sessionContexts
482
+ const cachedCtx = this._framework._sessionContexts?.get(sessionId)
483
+ if (cachedCtx) {
484
+ cachedCtx.clearMessages()
485
+ cachedCtx.compressionState.count = 0
486
+ cachedCtx.metadata.compressionCount = 0
487
+ }
488
+ }
489
+ }
490
+
455
491
  async _sendMessage(chatId, text, replyToMessageId = null) {
456
492
  if (!this._bot) return
457
493
  return this._bot.sendMessage(chatId, text, { reply_to_message_id: replyToMessageId })
@@ -535,6 +535,12 @@ class WeixinPlugin extends Plugin {
535
535
  * 处理对话
536
536
  */
537
537
  async _processChat(userId, text, originalMsg) {
538
+ // 处理命令
539
+ if (text.startsWith('/')) {
540
+ await this._handleCommand(userId, text, originalMsg)
541
+ return
542
+ }
543
+
538
544
  const sessionInfo = this._getSessionAgent(userId)
539
545
  if (!sessionInfo) {
540
546
  log.error(' No session agent available')
@@ -561,6 +567,75 @@ class WeixinPlugin extends Plugin {
561
567
  }
562
568
  }
563
569
 
570
+ /**
571
+ * 处理命令
572
+ */
573
+ async _handleCommand(userId, text, originalMsg) {
574
+ const parts = text.split(' ')
575
+ const command = parts[0].substring(1)
576
+ const args = parts.slice(1).join(' ')
577
+
578
+ switch (command.toLowerCase()) {
579
+ case 'start':
580
+ case 'help':
581
+ await this._sendMessageBatch(originalMsg, userId,
582
+ '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/history - 查看历史消息数',
583
+ true)
584
+ break
585
+ case 'clean':
586
+ case 'clear':
587
+ await this._clearSessionContext(userId)
588
+ await this._sendMessageBatch(originalMsg, userId, '✅ 对话上下文已清除', true)
589
+ break
590
+ case 'history':
591
+ const sessionInfo = this._getSessionAgent(userId)
592
+ if (sessionInfo && this._framework) {
593
+ const sessionCtx = this._framework.getSessionContext(sessionInfo.sessionId)
594
+ const count = sessionCtx?.messageStore?.messages?.length || 0
595
+ const compressionCount = sessionCtx?.metadata?.compressionCount || 0
596
+ await this._sendMessageBatch(originalMsg, userId,
597
+ `📊 当前状态\n\n历史消息:${count}\n压缩次数:${compressionCount}`,
598
+ true)
599
+ }
600
+ break
601
+ default:
602
+ // 未识别命令,当作普通消息处理
603
+ await this._processChat(userId, text, originalMsg)
604
+ }
605
+ }
606
+
607
+ /**
608
+ * 清除会话上下文(清除消息历史和压缩计数)
609
+ */
610
+ async _clearSessionContext(userId) {
611
+ const sessionId = `weixin_${userId}`
612
+
613
+ // 获取或创建 sessionAgent
614
+ const { agent } = this._getSessionAgent(userId)
615
+
616
+ // 清除 AgentChatHandler 的消息存储(需要传 sessionId)
617
+ if (agent._chatHandler) {
618
+ agent._chatHandler.clearHistory(sessionId)
619
+ }
620
+
621
+ // 清除 SessionContext 的消息和压缩计数
622
+ if (this._framework) {
623
+ const sessionCtx = this._framework.getSessionContext(sessionId)
624
+ if (sessionCtx) {
625
+ sessionCtx.clearMessages()
626
+ sessionCtx.compressionState.count = 0
627
+ sessionCtx.metadata.compressionCount = 0
628
+ }
629
+ // 同步清除 framework 缓存的 sessionContexts
630
+ const cachedCtx = this._framework._sessionContexts?.get(sessionId)
631
+ if (cachedCtx) {
632
+ cachedCtx.clearMessages()
633
+ cachedCtx.compressionState.count = 0
634
+ cachedCtx.metadata.compressionCount = 0
635
+ }
636
+ }
637
+ }
638
+
564
639
  /**
565
640
  * 权限检查
566
641
  */
package/src/core/agent.js CHANGED
@@ -90,10 +90,11 @@ class Agent extends EventEmitter {
90
90
  this.apiKey = config.apiKey;
91
91
  this.baseURL = config.baseURL;
92
92
  this.provider = config.provider || 'deepseek';
93
- this.providerOptions = config.providerOptions || {};
94
- // 使用常量替代硬编码数字
95
- this.providerOptions.maxOutputTokens = AGENT_CONFIG.MAX_OUTPUT_TOKENS;
96
- this.providerOptions.temperature = AGENT_CONFIG.TEMPERATURE;
93
+ this.providerOptions = {
94
+ ...config.providerOptions,
95
+ maxOutputTokens: config.providerOptions?.maxOutputTokens ?? AGENT_CONFIG.MAX_OUTPUT_TOKENS,
96
+ temperature: config.providerOptions?.temperature ?? AGENT_CONFIG.TEMPERATURE,
97
+ };
97
98
  // 原始 system prompt
98
99
  this._originalPrompt =
99
100
  config.systemPrompt ||
@@ -188,10 +188,15 @@ class ContextCompressor {
188
188
 
189
189
  // 更新压缩状态
190
190
  this._compressionCount++;
191
+ const tokenCount = this._countMessagesTokens(messages);
191
192
  if (messageStore.compressionState) {
192
193
  messageStore.compressionState.count++;
193
194
  messageStore.compressionState.lastCompressedAt = Date.now();
194
- messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
195
+ messageStore.compressionState.lastTokenCount = tokenCount;
196
+ }
197
+ // 同步更新 SessionContext.metadata.compressionCount(如果可用)
198
+ if (typeof messageStore.recordCompression === 'function') {
199
+ messageStore.recordCompression(tokenCount);
195
200
  }
196
201
 
197
202
  logger.info(
@@ -19,10 +19,6 @@ class ContextManager {
19
19
 
20
20
  // Context 存储
21
21
  this._requestContexts = new Map(); // requestId → RequestContext
22
- this._sessionContexts = new Map(); // sessionId → SessionContext
23
-
24
- // 引用计数(用于自动清理)
25
- this._refCounts = new Map();
26
22
 
27
23
  // 事件转发
28
24
  this._setupEventForwarding();
@@ -88,86 +84,15 @@ class ContextManager {
88
84
  this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
89
85
  }
90
86
 
91
- // ==================== Session Context ====================
92
-
93
- /**
94
- * 获取或创建 Session Context
95
- * @param {string} sessionId - 会话 ID
96
- * @param {Object} options - 配置选项
97
- * @returns {SessionContext}
98
- */
99
- getOrCreateSessionContext(sessionId, options = {}) {
100
- let ctx = this._sessionContexts.get(sessionId);
101
- if (!ctx) {
102
- ctx = new SessionContext(sessionId, this.framework, options);
103
- this._sessionContexts.set(sessionId, ctx);
104
- this._refCounts.set(`session:${sessionId}`, 1);
105
- } else {
106
- // 增加引用计数
107
- const key = `session:${sessionId}`;
108
- this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
109
- }
110
- return ctx;
111
- }
112
-
113
- /**
114
- * 获取 Session Context
115
- * @param {string} sessionId - 会话 ID
116
- * @returns {SessionContext|null}
117
- */
118
- getSessionContext(sessionId) {
119
- return this._sessionContexts.get(sessionId) || null;
120
- }
121
-
122
- /**
123
- * 释放 Session Context(减少引用计数)
124
- * @param {string} sessionId - 会话 ID
125
- * @param {boolean} [force=false] - 强制销毁
126
- */
127
- releaseSessionContext(sessionId, force = false) {
128
- const key = `session:${sessionId}`;
129
- const count = this._refCounts.get(key) || 0;
130
-
131
- if (force || count <= 1) {
132
- const ctx = this._sessionContexts.get(sessionId);
133
- if (ctx) {
134
- ctx.destroy();
135
- this.framework.emit('session:context-destroyed', ctx);
136
- }
137
- this._sessionContexts.delete(sessionId);
138
- this._refCounts.delete(key);
139
- } else {
140
- this._refCounts.set(key, count - 1);
141
- }
142
- }
143
-
144
- /**
145
- * 增加 Session Context 引用计数
146
- * @param {string} sessionId - 会话 ID
147
- */
148
- retainSessionContext(sessionId) {
149
- const key = `session:${sessionId}`;
150
- this._refCounts.set(key, (this._refCounts.get(key) || 0) + 1);
151
- }
152
-
153
- /**
154
- * 检查 Session Context 是否存在
155
- * @param {string} sessionId - 会话 ID
156
- * @returns {boolean}
157
- */
158
- hasSessionContext(sessionId) {
159
- return this._sessionContexts.has(sessionId);
160
- }
161
-
162
87
  // ==================== 批量操作 ====================
163
88
 
164
89
  /**
165
90
  * 销毁所有 Session Context
91
+ * @deprecated Use framework.destroySessionContext() instead - Framework manages sessions
166
92
  */
167
93
  destroyAllSessionContexts() {
168
- for (const [sessionId] of this._sessionContexts) {
169
- this.releaseSessionContext(sessionId, true);
170
- }
94
+ // No-op: Framework manages session contexts, not ContextManager
95
+ // This method exists for API compatibility but does nothing
171
96
  }
172
97
 
173
98
  /**
@@ -192,8 +117,7 @@ class ContextManager {
192
117
  this.releaseRequestContext(requestId);
193
118
  }
194
119
 
195
- // 清理 Session Contexts
196
- this.destroyAllSessionContexts();
120
+ // Session Contexts 由 Framework 管理,此处不需要清理
197
121
  }
198
122
 
199
123
  // ==================== 统计 ====================
@@ -205,8 +129,6 @@ class ContextManager {
205
129
  getStats() {
206
130
  return {
207
131
  activeRequests: this._requestContexts.size,
208
- activeSessions: this._sessionContexts.size,
209
- refCounts: Object.fromEntries(this._refCounts),
210
132
  };
211
133
  }
212
134
 
@@ -215,19 +137,8 @@ class ContextManager {
215
137
  * @returns {Object}
216
138
  */
217
139
  getMemoryUsage() {
218
- let sessionMessagesSize = 0;
219
- let sessionVariablesSize = 0;
220
-
221
- for (const ctx of this._sessionContexts.values()) {
222
- sessionMessagesSize += ctx.messageStore.messages.length;
223
- sessionVariablesSize += ctx.variables.size;
224
- }
225
-
226
140
  return {
227
141
  requestContexts: this._requestContexts.size,
228
- sessionContexts: this._sessionContexts.size,
229
- totalSessionMessages: sessionMessagesSize,
230
- totalSessionVariables: sessionVariablesSize,
231
142
  };
232
143
  }
233
144
  }
@@ -96,7 +96,12 @@ class Framework extends EventEmitter {
96
96
  this.toolRegistry.on('tool:call', toolCallHandler);
97
97
  this.toolRegistry.on('tool:result', toolResultHandler);
98
98
  this.toolRegistry.on('tool:error', toolErrorHandler);
99
- this._toolRegistryListeners = [toolRegisteredHandler, toolCallHandler, toolResultHandler, toolErrorHandler];
99
+ this._toolRegistryListeners = [
100
+ { event: 'tool:registered', handler: toolRegisteredHandler },
101
+ { event: 'tool:call', handler: toolCallHandler },
102
+ { event: 'tool:result', handler: toolResultHandler },
103
+ { event: 'tool:error', handler: toolErrorHandler },
104
+ ];
100
105
 
101
106
  // 事件转发 - Agent 事件
102
107
  this._setupAgentEventForwarding();
@@ -683,15 +688,18 @@ class Framework extends EventEmitter {
683
688
  // 只同步 Subagent
684
689
  if (!agent._isSubagent) continue;
685
690
 
691
+ // Build a Set of already registered parts for O(1) lookup
692
+ const registeredNames = new Set(agent._promptParts?.map(p => p.name) || []);
693
+
686
694
  for (const [name, part] of mainParts) {
687
695
  // 子 Agent 跳过这些不适合的部分
688
696
  if (['original-prompt', 'shared-prompt', 'datetime', 'metadata', 'capabilities'].includes(name)) {
689
697
  continue;
690
698
  }
691
- // 检查是否已注册
692
- const alreadyRegistered = agent._promptParts?.some(p => p.name === name);
693
- if (!alreadyRegistered) {
699
+ // O(1) lookup instead of O(m)
700
+ if (!registeredNames.has(name)) {
694
701
  agent.registerPromptPart(name, part.priority, part.provider);
702
+ registeredNames.add(name);
695
703
  }
696
704
  }
697
705
  }
@@ -912,11 +920,8 @@ class Framework extends EventEmitter {
912
920
 
913
921
  // 移除 toolRegistry 的事件监听器
914
922
  if (this._toolRegistryListeners) {
915
- for (const listener of this._toolRegistryListeners) {
916
- this.toolRegistry.off('tool:registered', listener);
917
- this.toolRegistry.off('tool:call', listener);
918
- this.toolRegistry.off('tool:result', listener);
919
- this.toolRegistry.off('tool:error', listener);
923
+ for (const { event, handler } of this._toolRegistryListeners) {
924
+ this.toolRegistry.off(event, handler);
920
925
  }
921
926
  this._toolRegistryListeners = [];
922
927
  }
@@ -52,6 +52,8 @@ class SessionContext {
52
52
  messages: options.initialMessages || [], // 对话历史
53
53
  historyLoaded: false, // 是否已从持久化加载
54
54
  systemPrompt: null, // 当前系统提示词
55
+ // 委托给 SessionContext.recordCompression
56
+ recordCompression: (tokenCount) => this.recordCompression(tokenCount),
55
57
  };
56
58
 
57
59
  // 压缩状态
@@ -59,6 +61,7 @@ class SessionContext {
59
61
  lastCompressedAt: null,
60
62
  lastTokenCount: 0,
61
63
  pendingCompression: false,
64
+ count: 0,
62
65
  };
63
66
 
64
67
  // 自定义数据插槽
@@ -200,6 +203,7 @@ class SessionContext {
200
203
  initialMessages: data.messages || [],
201
204
  variables: data.variables || {},
202
205
  metadata: data.metadata || {},
206
+ storage,
203
207
  });
204
208
 
205
209
  ctx.messageStore.historyLoaded = true;
@@ -285,6 +289,7 @@ class SessionContext {
285
289
  recordCompression(tokenCount) {
286
290
  this.compressionState.lastCompressedAt = Date.now();
287
291
  this.compressionState.lastTokenCount = tokenCount;
292
+ this.compressionState.count++;
288
293
  this.metadata.compressionCount++;
289
294
  }
290
295
 
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  const fs = require('fs');
11
+ const fsPromises = require('fs').promises;
11
12
  const path = require('path');
12
13
 
13
14
  class SessionStorageAdapter {
@@ -36,10 +37,12 @@ class SessionStorageAdapter {
36
37
  * 确保目录存在
37
38
  * @private
38
39
  */
39
- _ensureDirectory() {
40
+ async _ensureDirectory() {
40
41
  const dir = path.resolve(process.cwd(), this.baseDir);
41
- if (!fs.existsSync(dir)) {
42
- fs.mkdirSync(dir, { recursive: true });
42
+ try {
43
+ await fsPromises.access(dir);
44
+ } catch {
45
+ await fsPromises.mkdir(dir, { recursive: true });
43
46
  }
44
47
  }
45
48
 
@@ -121,12 +124,14 @@ class SessionStorageAdapter {
121
124
  try {
122
125
  // 确保目录存在
123
126
  const dir = path.dirname(filePath);
124
- if (!fs.existsSync(dir)) {
125
- fs.mkdirSync(dir, { recursive: true });
127
+ try {
128
+ await fsPromises.access(dir);
129
+ } catch {
130
+ await fsPromises.mkdir(dir, { recursive: true });
126
131
  }
127
132
 
128
133
  const content = JSON.stringify(data, null, 2);
129
- fs.writeFileSync(filePath, content, 'utf-8');
134
+ await fsPromises.writeFile(filePath, content, 'utf-8');
130
135
  } catch (err) {
131
136
  console.error(`[SessionStorage] Failed to write ${sessionId}:`, err.message);
132
137
  throw err;
@@ -146,12 +151,14 @@ class SessionStorageAdapter {
146
151
  if (this.type === 'file') {
147
152
  const filePath = this._getFilePath(sessionId);
148
153
 
149
- if (!fs.existsSync(filePath)) {
154
+ try {
155
+ await fsPromises.access(filePath);
156
+ } catch {
150
157
  return null;
151
158
  }
152
159
 
153
160
  try {
154
- const content = fs.readFileSync(filePath, 'utf-8');
161
+ const content = await fsPromises.readFile(filePath, 'utf-8');
155
162
  return JSON.parse(content);
156
163
  } catch (err) {
157
164
  console.error(`[SessionStorage] Failed to read ${sessionId}:`, err.message);
@@ -175,16 +182,19 @@ class SessionStorageAdapter {
175
182
  if (this.type === 'file') {
176
183
  const filePath = this._getFilePath(sessionId);
177
184
 
178
- if (fs.existsSync(filePath)) {
179
- try {
180
- fs.unlinkSync(filePath);
181
- return true;
182
- } catch (err) {
183
- console.error(`[SessionStorage] Failed to delete ${sessionId}:`, err.message);
184
- return false;
185
- }
185
+ try {
186
+ await fsPromises.access(filePath);
187
+ } catch {
188
+ return false;
189
+ }
190
+
191
+ try {
192
+ await fsPromises.unlink(filePath);
193
+ return true;
194
+ } catch (err) {
195
+ console.error(`[SessionStorage] Failed to delete ${sessionId}:`, err.message);
196
+ return false;
186
197
  }
187
- return false;
188
198
  }
189
199
 
190
200
  return false;
@@ -202,12 +212,14 @@ class SessionStorageAdapter {
202
212
  if (this.type === 'file') {
203
213
  const dir = path.resolve(process.cwd(), this.baseDir);
204
214
 
205
- if (!fs.existsSync(dir)) {
215
+ try {
216
+ await fsPromises.access(dir);
217
+ } catch {
206
218
  return [];
207
219
  }
208
220
 
209
221
  try {
210
- const files = fs.readdirSync(dir);
222
+ const files = await fsPromises.readdir(dir);
211
223
  return files.filter((f) => f.endsWith('.json')).map((f) => f.replace(/\.json$/, ''));
212
224
  } catch (err) {
213
225
  console.error(`[SessionStorage] Failed to list sessions:`, err.message);
@@ -255,6 +267,15 @@ let _defaultAdapter = null;
255
267
  function getDefaultAdapter(options = {}) {
256
268
  if (!_defaultAdapter) {
257
269
  _defaultAdapter = new SessionStorageAdapter(options);
270
+ } else if (Object.keys(options).length > 0) {
271
+ // 如果已存在但传入了新选项,用新选项更新默认适配器
272
+ // 注意:这会修改全局单例,需要确保调用方知道这一点
273
+ if (options.type && options.type !== _defaultAdapter.type) {
274
+ _defaultAdapter.type = options.type;
275
+ }
276
+ if (options.baseDir && options.baseDir !== _defaultAdapter.baseDir) {
277
+ _defaultAdapter.baseDir = options.baseDir;
278
+ }
258
279
  }
259
280
  return _defaultAdapter;
260
281
  }