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.
- package/.agent/memory/feedback/mocnx1wx-qtxxlh.md +9 -0
- package/.agent/sessions/cli_default.json +547 -0
- package/.claude/settings.local.json +4 -1
- package/cli/src/ui/chat-ui.js +29 -1
- package/package.json +1 -1
- package/plugins/feishu-plugin.js +36 -3
- package/plugins/telegram-plugin.js +43 -7
- package/plugins/weixin-plugin.js +75 -0
- package/src/core/agent.js +5 -4
- package/src/core/context-compressor.js +6 -1
- package/src/core/context-manager.js +4 -93
- package/src/core/framework.js +14 -9
- package/src/core/session-context.js +5 -0
- package/src/core/session-storage.js +40 -19
|
@@ -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
|
}
|
package/cli/src/ui/chat-ui.js
CHANGED
|
@@ -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
package/plugins/feishu-plugin.js
CHANGED
|
@@ -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/
|
|
195
|
+
'👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/history - 查看历史消息数',
|
|
196
196
|
originalMsg)
|
|
197
197
|
break
|
|
198
198
|
case 'clear':
|
|
199
|
-
|
|
200
|
-
await this.
|
|
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/
|
|
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.
|
|
311
|
-
await this._sendMessage(chatId, '✅
|
|
312
|
+
await this._clearSessionContext(chatId)
|
|
313
|
+
await this._sendMessage(chatId, '✅ 对话上下文已清除', msg.message_id)
|
|
312
314
|
break
|
|
313
315
|
case 'history':
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
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
|
|
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 })
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -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 =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 =
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
//
|
|
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
|
}
|
package/src/core/framework.js
CHANGED
|
@@ -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 = [
|
|
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
|
-
|
|
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
|
|
916
|
-
this.toolRegistry.off(
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
+
try {
|
|
155
|
+
await fsPromises.access(filePath);
|
|
156
|
+
} catch {
|
|
150
157
|
return null;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
try {
|
|
154
|
-
const content =
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
215
|
+
try {
|
|
216
|
+
await fsPromises.access(dir);
|
|
217
|
+
} catch {
|
|
206
218
|
return [];
|
|
207
219
|
}
|
|
208
220
|
|
|
209
221
|
try {
|
|
210
|
-
const files =
|
|
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
|
}
|