foliko 1.1.63 → 1.1.64
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/sessions/cli_default.json +119 -301
- package/cli/bin/foliko.js +2 -2
- package/cli/src/commands/chat.js +15 -26
- package/cli/src/ui/chat-ui.js +102 -165
- package/cli/src/ui/footer-bar.js +7 -32
- package/cli/src/ui/message-bubble.js +24 -2
- package/cli/src/ui/status-bar.js +177 -0
- package/package.json +1 -2
- package/plugins/qq-plugin.js +1 -1
- package/src/core/agent-chat.js +50 -17
- package/src/core/agent.js +17 -27
- package/src/core/chat-session.js +7 -161
- package/src/core/constants.js +198 -0
- package/src/core/context-compressor.js +6 -181
- package/src/core/framework.js +125 -6
- package/src/core/plugin-base.js +7 -5
- package/src/core/provider.js +6 -0
- package/src/core/subagent.js +16 -135
- package/src/core/tool-executor.js +2 -70
- package/src/executors/mcp-executor.js +1 -1
- package/src/utils/chat-queue.js +11 -22
- package/src/utils/download.js +5 -4
- package/src/utils/message-validator.js +283 -0
- package/src/utils/retry.js +168 -22
- package/src/utils/sandbox.js +60 -207
- package/cli/src/utils/debounce.js +0 -106
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusBar — 状态栏组件
|
|
3
|
+
*
|
|
4
|
+
* 封装三个状态槽位:通知(notifier) / 工具调用(tooler) / 思考中(loader)
|
|
5
|
+
* 每个槽位通过子 Container 管理显示/隐藏
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Container, Spacer,Loader } = require('@earendil-works/pi-tui');
|
|
9
|
+
const chalk = require('chalk').default;
|
|
10
|
+
|
|
11
|
+
// Foliko 配色
|
|
12
|
+
const folikoPrimary = chalk.hex('#2A9D8F');
|
|
13
|
+
const folikoGold = chalk.hex('#E9C46A');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* NoMarginLoader — 去掉 Loader 默认的上方空行
|
|
17
|
+
*/
|
|
18
|
+
class NoMarginLoader extends Loader {
|
|
19
|
+
render(width) {
|
|
20
|
+
return super.render(width).slice(1); // 去掉 Loader 自动追加的空行
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class StatusBar {
|
|
25
|
+
constructor(tui, editor) {
|
|
26
|
+
this.tui = tui;
|
|
27
|
+
this.editor = editor;
|
|
28
|
+
|
|
29
|
+
// 顶层容器
|
|
30
|
+
this.container = new Container();
|
|
31
|
+
|
|
32
|
+
// 三个子 Container 槽位
|
|
33
|
+
this._notifierSlot = new Container();
|
|
34
|
+
this._toolerSlot = new Container();
|
|
35
|
+
this._loaderSlot = new Container();
|
|
36
|
+
|
|
37
|
+
// 工具调用 Loader
|
|
38
|
+
const tooler = new NoMarginLoader(
|
|
39
|
+
tui,
|
|
40
|
+
(s) => chalk.green(s),
|
|
41
|
+
(s) => chalk.yellow(s),
|
|
42
|
+
'工具调用...',
|
|
43
|
+
{ frames: ['|', '/', '-', '\\'].map((str) => folikoGold(str)) },
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// 思考中 Loader
|
|
47
|
+
const loader = new NoMarginLoader(
|
|
48
|
+
tui,
|
|
49
|
+
(s) => chalk.cyan(s),
|
|
50
|
+
(s) => chalk.dim(s),
|
|
51
|
+
'正在思考中...',
|
|
52
|
+
{ frames: ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'].map((str) => folikoPrimary(str)) },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Spacer 占位(隐藏时填充)
|
|
56
|
+
const toolerSpacer = new Spacer(0);
|
|
57
|
+
const loaderSpacer = new Spacer(0);
|
|
58
|
+
const notifierSpacer = new Spacer(0);
|
|
59
|
+
|
|
60
|
+
// 初始化所有槽位为 Spacer
|
|
61
|
+
this._notifierSlot.addChild(notifierSpacer);
|
|
62
|
+
this._toolerSlot.addChild(toolerSpacer);
|
|
63
|
+
this._loaderSlot.addChild(loaderSpacer);
|
|
64
|
+
|
|
65
|
+
// 挂载到顶层容器
|
|
66
|
+
this.container.addChild(this._notifierSlot);
|
|
67
|
+
this.container.addChild(this._toolerSlot);
|
|
68
|
+
this.container.addChild(this._loaderSlot);
|
|
69
|
+
|
|
70
|
+
// ——— 公开 API ———
|
|
71
|
+
const self = this;
|
|
72
|
+
|
|
73
|
+
/** 工具调用状态 */
|
|
74
|
+
this.tooler = {
|
|
75
|
+
dom: tooler,
|
|
76
|
+
_spacer: toolerSpacer,
|
|
77
|
+
_showed: false,
|
|
78
|
+
show(text) {
|
|
79
|
+
this._showed = true;
|
|
80
|
+
self._toolerSlot.clear();
|
|
81
|
+
self._toolerSlot.addChild(this.dom);
|
|
82
|
+
this.dom.setMessage(text);
|
|
83
|
+
return this.dom;
|
|
84
|
+
},
|
|
85
|
+
hide() {
|
|
86
|
+
this._showed = false;
|
|
87
|
+
self._toolerSlot.clear();
|
|
88
|
+
self._toolerSlot.addChild(this._spacer);
|
|
89
|
+
self.tui.requestRender();
|
|
90
|
+
},
|
|
91
|
+
setText(text) {
|
|
92
|
+
if (!this._showed) {
|
|
93
|
+
this.show(text);
|
|
94
|
+
} else {
|
|
95
|
+
this.dom.setMessage(text);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** 通知提示 */
|
|
101
|
+
this.notifier = {
|
|
102
|
+
dom: null,
|
|
103
|
+
_spacer: notifierSpacer,
|
|
104
|
+
_showed: false,
|
|
105
|
+
_timeout: null,
|
|
106
|
+
show(text, duration = 5000) {
|
|
107
|
+
if (this._timeout) clearTimeout(this._timeout);
|
|
108
|
+
|
|
109
|
+
if (!this.dom) {
|
|
110
|
+
this.dom = new NoMarginLoader(tui, (s) => chalk.cyan(s), (s) => chalk.yellow(s), text);
|
|
111
|
+
}
|
|
112
|
+
this.dom.setMessage(text);
|
|
113
|
+
self._notifierSlot.clear();
|
|
114
|
+
self._notifierSlot.addChild(this.dom);
|
|
115
|
+
this._showed = true;
|
|
116
|
+
|
|
117
|
+
this._timeout = setTimeout(() => this.hide(), duration);
|
|
118
|
+
},
|
|
119
|
+
hide() {
|
|
120
|
+
if (this._timeout) {
|
|
121
|
+
clearTimeout(this._timeout);
|
|
122
|
+
this._timeout = null;
|
|
123
|
+
}
|
|
124
|
+
this._showed = false;
|
|
125
|
+
self._notifierSlot.clear();
|
|
126
|
+
self._notifierSlot.addChild(this._spacer);
|
|
127
|
+
self.tui.requestRender();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** 思考中 Loader(带计时) */
|
|
132
|
+
this.loader = {
|
|
133
|
+
dom: loader,
|
|
134
|
+
_spacer: loaderSpacer,
|
|
135
|
+
_startTime: null,
|
|
136
|
+
_timer: null,
|
|
137
|
+
show() {
|
|
138
|
+
self._loaderSlot.clear();
|
|
139
|
+
self._loaderSlot.addChild(this.dom);
|
|
140
|
+
if (self.editor) self.editor.disableSubmit = true;
|
|
141
|
+
|
|
142
|
+
this._startTime = Date.now();
|
|
143
|
+
this.dom.setMessage('正在思考中... (0秒)');
|
|
144
|
+
this.dom.start();
|
|
145
|
+
|
|
146
|
+
this._timer = setInterval(() => {
|
|
147
|
+
const elapsed = Math.floor((Date.now() - this._startTime) / 1000);
|
|
148
|
+
const seconds = elapsed % 60;
|
|
149
|
+
const minutes = Math.floor(elapsed / 60);
|
|
150
|
+
const timeStr = minutes > 0 ? `${minutes}分${seconds}秒` : `${seconds}秒`;
|
|
151
|
+
this.dom.setMessage(`正在思考中... (${timeStr})`);
|
|
152
|
+
}, 1000);
|
|
153
|
+
|
|
154
|
+
return this.dom;
|
|
155
|
+
},
|
|
156
|
+
hide() {
|
|
157
|
+
if (self.editor) self.editor.disableSubmit = false;
|
|
158
|
+
if (this._timer) {
|
|
159
|
+
clearInterval(this._timer);
|
|
160
|
+
this._timer = null;
|
|
161
|
+
}
|
|
162
|
+
this.dom.stop();
|
|
163
|
+
self._loaderSlot.clear();
|
|
164
|
+
self._loaderSlot.addChild(this._spacer);
|
|
165
|
+
self.tui.requestRender();
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
invalidate() {
|
|
171
|
+
for (const child of this.container.children) {
|
|
172
|
+
child.invalidate?.();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { StatusBar };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.64",
|
|
4
4
|
"description": "简约的插件化 Agent 框架",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -83,7 +83,6 @@
|
|
|
83
83
|
"nodemailer": "^6.10.0",
|
|
84
84
|
"qrcode-terminal": "^0.12.0",
|
|
85
85
|
"remove-markdown": "^0.6.3",
|
|
86
|
-
"vm2": "^3.10.5",
|
|
87
86
|
"zod": "^3.25.76"
|
|
88
87
|
},
|
|
89
88
|
"devDependencies": {
|
package/plugins/qq-plugin.js
CHANGED
package/src/core/agent-chat.js
CHANGED
|
@@ -14,20 +14,23 @@ const {
|
|
|
14
14
|
tool: aiTool,
|
|
15
15
|
ToolLoopAgent,
|
|
16
16
|
isLoopFinished,
|
|
17
|
-
generateText,
|
|
18
|
-
streamText,
|
|
19
|
-
RetryError,
|
|
20
|
-
APICallError,
|
|
21
17
|
} = require('ai');
|
|
22
|
-
const {
|
|
18
|
+
const { cleanResponse } = require('../utils');
|
|
23
19
|
const { ChatQueueManager } = require('../utils/chat-queue');
|
|
24
20
|
const { TokenCounter } = require('./token-counter');
|
|
25
21
|
const { isThinkingModel } = require('./provider');
|
|
26
|
-
const fs = require('fs/promises');
|
|
27
22
|
// 新模块
|
|
28
23
|
const { ChatSession } = require('./chat-session');
|
|
29
24
|
const { ToolExecutor } = require('./tool-executor');
|
|
30
25
|
const { ContextCompressor } = require('./context-compressor');
|
|
26
|
+
const {
|
|
27
|
+
DEFAULT_MAX_OUTPUT_TOKENS,
|
|
28
|
+
DEFAULT_TEMPERATURE,
|
|
29
|
+
DEFAULT_MAX_STEPS,
|
|
30
|
+
DEFAULT_MAX_CONCURRENT,
|
|
31
|
+
DEFAULT_RETRY_ATTEMPTS,
|
|
32
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
33
|
+
} = require('./constants');
|
|
31
34
|
|
|
32
35
|
class AgentChatHandler extends EventEmitter {
|
|
33
36
|
/**
|
|
@@ -104,9 +107,9 @@ class AgentChatHandler extends EventEmitter {
|
|
|
104
107
|
|
|
105
108
|
// ChatQueueManager: 队列管理
|
|
106
109
|
this.queueManager = new ChatQueueManager({
|
|
107
|
-
maxConcurrent: config.maxConcurrent ||
|
|
108
|
-
retryAttempts: config.retryAttempts ||
|
|
109
|
-
retryDelay: config.retryDelay ||
|
|
110
|
+
maxConcurrent: config.maxConcurrent || DEFAULT_MAX_CONCURRENT,
|
|
111
|
+
retryAttempts: config.retryAttempts || DEFAULT_RETRY_ATTEMPTS,
|
|
112
|
+
retryDelay: config.retryDelay || DEFAULT_RETRY_DELAY_MS,
|
|
110
113
|
});
|
|
111
114
|
|
|
112
115
|
// AI client
|
|
@@ -183,10 +186,15 @@ class AgentChatHandler extends EventEmitter {
|
|
|
183
186
|
|
|
184
187
|
// ==================== 工具管理(委托给 ToolExecutor) ====================
|
|
185
188
|
|
|
189
|
+
/** AI tools 格式缓存 (避免每次 chat 重建) */
|
|
190
|
+
_aiToolsCache = null;
|
|
191
|
+
_aiToolsCacheKey = ''; // 工具列表的哈希摘要
|
|
192
|
+
|
|
186
193
|
registerTool(tool) {
|
|
187
194
|
this._toolExecutor.registerTool(tool);
|
|
188
195
|
// 工具列表变了,清除缓存
|
|
189
196
|
this._toolsTokensCacheVersion = 0;
|
|
197
|
+
this._aiToolsCache = null; // 清除 AI tools 缓存
|
|
190
198
|
return this;
|
|
191
199
|
}
|
|
192
200
|
|
|
@@ -463,8 +471,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
463
471
|
throw new Error('AI client not configured.');
|
|
464
472
|
}
|
|
465
473
|
|
|
466
|
-
const systemPrompt = framework.
|
|
467
|
-
// await fs.writeFile('system.md',systemPrompt)
|
|
474
|
+
const systemPrompt = framework.getSystemPrompt();
|
|
468
475
|
const tools = this._getAITools(aiTool);
|
|
469
476
|
const agent = new ToolLoopAgent({
|
|
470
477
|
model: this._aiClient,
|
|
@@ -576,7 +583,9 @@ class AgentChatHandler extends EventEmitter {
|
|
|
576
583
|
yield { type: 'error', error: friendlyMessage };
|
|
577
584
|
} finally {
|
|
578
585
|
const messageStore = this._getSessionMessageStore(sessionId);
|
|
579
|
-
messageStore.save()
|
|
586
|
+
messageStore.save().catch((err) => {
|
|
587
|
+
logger.error(`[${sessionId}] Failed to save message store: ${err.message}`);
|
|
588
|
+
});
|
|
580
589
|
}
|
|
581
590
|
}
|
|
582
591
|
|
|
@@ -602,10 +611,14 @@ class AgentChatHandler extends EventEmitter {
|
|
|
602
611
|
}
|
|
603
612
|
|
|
604
613
|
/**
|
|
605
|
-
*
|
|
614
|
+
* 入队消息(走共享的 ChatQueueManager,与 sendMessage 共用同一队列)
|
|
606
615
|
*/
|
|
607
616
|
enqueue(sessionId, message, options = {}) {
|
|
608
|
-
|
|
617
|
+
const requestId = this.generateRequestId();
|
|
618
|
+
return this.queueManager.enqueue(requestId, sessionId, message, {
|
|
619
|
+
...options,
|
|
620
|
+
executeFunction: this.chatStream.bind(this),
|
|
621
|
+
});
|
|
609
622
|
}
|
|
610
623
|
|
|
611
624
|
/**
|
|
@@ -621,7 +634,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
621
634
|
if (!this._aiClient) {
|
|
622
635
|
throw new Error('AI client not configured.');
|
|
623
636
|
}
|
|
624
|
-
const systemPrompt = framework.
|
|
637
|
+
const systemPrompt = framework.getSystemPrompt();
|
|
625
638
|
const tools = this._getAITools(aiTool);
|
|
626
639
|
|
|
627
640
|
// DeepSeek thinking mode: 处理参数
|
|
@@ -688,7 +701,11 @@ class AgentChatHandler extends EventEmitter {
|
|
|
688
701
|
};
|
|
689
702
|
} finally {
|
|
690
703
|
const messageStore = this._getSessionMessageStore(sessionId);
|
|
691
|
-
|
|
704
|
+
try {
|
|
705
|
+
await messageStore.save();
|
|
706
|
+
} catch (err) {
|
|
707
|
+
logger.error(`[${sessionId}] Failed to save message store: ${err.message}`);
|
|
708
|
+
}
|
|
692
709
|
}
|
|
693
710
|
}
|
|
694
711
|
|
|
@@ -799,11 +816,25 @@ class AgentChatHandler extends EventEmitter {
|
|
|
799
816
|
}
|
|
800
817
|
|
|
801
818
|
/**
|
|
802
|
-
*
|
|
819
|
+
* 计算工具列表缓存 key
|
|
820
|
+
* @private
|
|
821
|
+
*/
|
|
822
|
+
_computeToolsCacheKey() {
|
|
823
|
+
const allTools = this.agent.framework.getTools();
|
|
824
|
+
return allTools.map((t) => `${t.name}:${t.description ? t.description.length : 0}`).join('|');
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* 获取 AI 工具格式(带缓存)
|
|
803
829
|
* 使用 AI SDK 的 tool() 格式
|
|
804
830
|
* @private
|
|
805
831
|
*/
|
|
806
832
|
_getAITools(toolFn) {
|
|
833
|
+
const currentKey = this._computeToolsCacheKey();
|
|
834
|
+
if (this._aiToolsCache && this._aiToolsCacheKey === currentKey) {
|
|
835
|
+
return this._aiToolsCache;
|
|
836
|
+
}
|
|
837
|
+
|
|
807
838
|
const tools = {};
|
|
808
839
|
const allTools = this.agent.framework.getTools();
|
|
809
840
|
for (const toolDef of allTools) {
|
|
@@ -850,6 +881,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
850
881
|
tools[toolName] = toolFn(toolConfig);
|
|
851
882
|
}
|
|
852
883
|
|
|
884
|
+
this._aiToolsCache = tools;
|
|
885
|
+
this._aiToolsCacheKey = currentKey;
|
|
853
886
|
return tools;
|
|
854
887
|
}
|
|
855
888
|
|
package/src/core/agent.js
CHANGED
|
@@ -9,39 +9,21 @@ const { SystemPromptBuilder } = require('./system-prompt-builder');
|
|
|
9
9
|
const { NotificationManager } = require('./notification-manager');
|
|
10
10
|
const { Logger, LOG_LEVELS } = require('../utils/logger');
|
|
11
11
|
const os = require('os');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const PROMPT_PRIORITY = {
|
|
18
|
-
DATETIME: 50,
|
|
19
|
-
ORIGINAL_PROMPT: 100,
|
|
20
|
-
SHARED_PROMPT: 200,
|
|
21
|
-
METADATA: 300,
|
|
22
|
-
TOOLS: 400,
|
|
23
|
-
SKILLS: 500,
|
|
24
|
-
SUB_AGENTS: 600,
|
|
25
|
-
CAPABILITIES: 700,
|
|
26
|
-
MCP_TOOLS: 750,
|
|
27
|
-
EXTENSION_TOOLS: 800,
|
|
28
|
-
TOOL_CORE_RULES: 1000,
|
|
29
|
-
};
|
|
12
|
+
const {
|
|
13
|
+
PROMPT_PRIORITY,
|
|
14
|
+
DEFAULT_MAX_OUTPUT_TOKENS,
|
|
15
|
+
DEFAULT_TEMPERATURE,
|
|
16
|
+
} = require('./constants');
|
|
30
17
|
|
|
31
18
|
/**
|
|
32
19
|
* Agent 配置常量
|
|
33
|
-
* 统一管理配置参数,避免魔法数字
|
|
34
20
|
*/
|
|
35
21
|
const AGENT_CONFIG = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
TEMPERATURE: 0.3,
|
|
39
|
-
// 通知数量限制
|
|
22
|
+
MAX_OUTPUT_TOKENS: DEFAULT_MAX_OUTPUT_TOKENS,
|
|
23
|
+
TEMPERATURE: DEFAULT_TEMPERATURE,
|
|
40
24
|
MAX_NOTIFICATIONS: 5,
|
|
41
|
-
// 元数据提取配置
|
|
42
25
|
CAPABILITY_MAX_LENGTH: 50,
|
|
43
26
|
CAPABILITY_MAX_PARTS: 3,
|
|
44
|
-
// 错误重试配置
|
|
45
27
|
MAX_RETRIES: 3,
|
|
46
28
|
};
|
|
47
29
|
|
|
@@ -77,8 +59,8 @@ class Agent extends EventEmitter {
|
|
|
77
59
|
this.provider = config.provider || 'deepseek';
|
|
78
60
|
this.providerOptions = {
|
|
79
61
|
...config.providerOptions,
|
|
80
|
-
maxOutputTokens: config.providerOptions?.maxOutputTokens ??
|
|
81
|
-
temperature: config.providerOptions?.temperature ??
|
|
62
|
+
maxOutputTokens: config.providerOptions?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
63
|
+
temperature: config.providerOptions?.temperature ?? DEFAULT_TEMPERATURE,
|
|
82
64
|
};
|
|
83
65
|
// 原始 system prompt
|
|
84
66
|
this._originalPrompt =
|
|
@@ -439,6 +421,14 @@ class Agent extends EventEmitter {
|
|
|
439
421
|
this._syncTools();
|
|
440
422
|
}
|
|
441
423
|
|
|
424
|
+
/**
|
|
425
|
+
* 获取原始系统提示文本
|
|
426
|
+
* @returns {string}
|
|
427
|
+
*/
|
|
428
|
+
getOriginalPrompt() {
|
|
429
|
+
return this._originalPrompt;
|
|
430
|
+
}
|
|
431
|
+
|
|
442
432
|
/**
|
|
443
433
|
* 设置系统提示
|
|
444
434
|
*/
|
package/src/core/chat-session.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const { EventEmitter } = require('../utils/event-emitter');
|
|
12
|
-
const { ChatQueueManager } = require('../utils/chat-queue');
|
|
13
12
|
const { logger } = require('../utils/logger');
|
|
14
13
|
|
|
15
14
|
/**
|
|
@@ -113,21 +112,8 @@ class ChatSession extends EventEmitter {
|
|
|
113
112
|
// Session 消息存储 Map: sessionId -> { messages: [], historyLoaded: false, compressionState: {} }
|
|
114
113
|
this._sessionMessageStores = new Map();
|
|
115
114
|
|
|
116
|
-
// Session 队列: sessionId -> Queue of {message, options, resolve, reject}
|
|
117
|
-
this._sessionQueues = new Map();
|
|
118
|
-
this._processingSessions = new Set();
|
|
119
|
-
|
|
120
115
|
// Session 事件作用域 Map: sessionId -> Set<{event, handler}>
|
|
121
116
|
this._sessionScopes = new Map();
|
|
122
|
-
|
|
123
|
-
// 队列管理器
|
|
124
|
-
this.queueManager = new ChatQueueManager({
|
|
125
|
-
maxConcurrent: config.maxConcurrent || 1,
|
|
126
|
-
retryAttempts: config.retryAttempts || 3,
|
|
127
|
-
retryDelay: config.retryDelay || 1000,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
this._setupQueueEvents();
|
|
131
117
|
}
|
|
132
118
|
|
|
133
119
|
/**
|
|
@@ -138,29 +124,7 @@ class ChatSession extends EventEmitter {
|
|
|
138
124
|
this._messageProcessor = processor;
|
|
139
125
|
}
|
|
140
126
|
|
|
141
|
-
/**
|
|
142
|
-
* 设置队列事件转发
|
|
143
|
-
* @private
|
|
144
|
-
*/
|
|
145
|
-
_setupQueueEvents() {
|
|
146
|
-
const events = [
|
|
147
|
-
'queue:added',
|
|
148
|
-
'queue:processing',
|
|
149
|
-
'queue:completed',
|
|
150
|
-
'queue:failed',
|
|
151
|
-
'queue:retry',
|
|
152
|
-
'queue:empty',
|
|
153
|
-
'queue:cleared',
|
|
154
|
-
'queue:session-removed',
|
|
155
|
-
'stream:chunk',
|
|
156
|
-
];
|
|
157
127
|
|
|
158
|
-
events.forEach((eventName) => {
|
|
159
|
-
this.queueManager.on(eventName, (data) => {
|
|
160
|
-
this.emit(eventName, data);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
128
|
|
|
165
129
|
/**
|
|
166
130
|
* 获取或创建 SessionScope
|
|
@@ -219,6 +183,7 @@ class ChatSession extends EventEmitter {
|
|
|
219
183
|
},
|
|
220
184
|
save: () => {
|
|
221
185
|
this.saveHistory(sessionId, store.messages);
|
|
186
|
+
return Promise.resolve();
|
|
222
187
|
},
|
|
223
188
|
};
|
|
224
189
|
this._sessionMessageStores.set(sessionId, store);
|
|
@@ -310,123 +275,24 @@ class ChatSession extends EventEmitter {
|
|
|
310
275
|
}
|
|
311
276
|
|
|
312
277
|
/**
|
|
313
|
-
*
|
|
278
|
+
* 入队消息(直接调用消息处理器,排队由上层 ChatQueueManager 处理)
|
|
314
279
|
* @param {string} sessionId - Session ID
|
|
315
280
|
* @param {Object} message - 消息
|
|
316
281
|
* @param {Object} options - 选项
|
|
317
282
|
* @returns {Promise}
|
|
318
283
|
*/
|
|
319
284
|
enqueue(sessionId, message, options = {}) {
|
|
320
|
-
|
|
321
|
-
this._sessionQueues.set(sessionId, []);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return new Promise((resolve, reject) => {
|
|
325
|
-
const item = {
|
|
326
|
-
message,
|
|
327
|
-
options,
|
|
328
|
-
resolve,
|
|
329
|
-
reject,
|
|
330
|
-
timestamp: Date.now(),
|
|
331
|
-
executeFunction: options.executeFunction,
|
|
332
|
-
};
|
|
333
|
-
this._sessionQueues.get(sessionId).push(item);
|
|
334
|
-
this.emit('queue:added', {
|
|
335
|
-
sessionId,
|
|
336
|
-
message,
|
|
337
|
-
queueSize: this._sessionQueues.get(sessionId).length,
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// 触发队列处理
|
|
341
|
-
this._processNext(sessionId);
|
|
342
|
-
});
|
|
285
|
+
return this._processMessageDirect(sessionId, message, options);
|
|
343
286
|
}
|
|
344
287
|
|
|
345
288
|
/**
|
|
346
|
-
*
|
|
347
|
-
* @param {string} sessionId - Session ID
|
|
289
|
+
* 直接处理消息(不经过额外的队列)
|
|
348
290
|
* @private
|
|
349
291
|
*/
|
|
350
|
-
async
|
|
351
|
-
if (this._processingSessions.has(sessionId)) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const queue = this._sessionQueues.get(sessionId);
|
|
356
|
-
if (!queue || queue.length === 0) {
|
|
357
|
-
this.emit('queue:empty', { sessionId });
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
this._processingSessions.add(sessionId);
|
|
362
|
-
this.emit('queue:processing', { sessionId });
|
|
363
|
-
|
|
364
|
-
const item = queue.shift();
|
|
365
|
-
try {
|
|
366
|
-
// 如果有 executeFunction,使用流式处理
|
|
367
|
-
if (item.executeFunction) {
|
|
368
|
-
const chunks = [];
|
|
369
|
-
const stream = item.executeFunction(item.message, { ...item.options, sessionId });
|
|
370
|
-
let hasError = false;
|
|
371
|
-
let errorMessage = '';
|
|
372
|
-
|
|
373
|
-
for await (const chunk of stream) {
|
|
374
|
-
// 检查是否是错误 chunk,如果是立即拒绝并终止
|
|
375
|
-
if (chunk.type === 'error') {
|
|
376
|
-
hasError = true;
|
|
377
|
-
errorMessage = chunk.error || 'Unknown error';
|
|
378
|
-
this.emit('message:error', { sessionId, error: errorMessage });
|
|
379
|
-
item.reject(new Error(errorMessage));
|
|
380
|
-
return; // 立即返回,不继续迭代
|
|
381
|
-
}
|
|
382
|
-
chunks.push(chunk);
|
|
383
|
-
this.emit('stream:chunk', {
|
|
384
|
-
sessionId,
|
|
385
|
-
chunk,
|
|
386
|
-
accumulated: chunks.length,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (hasError) {
|
|
391
|
-
// 有错误 chunk,拒绝 Promise
|
|
392
|
-
this.emit('message:error', { sessionId, error: errorMessage });
|
|
393
|
-
item.reject(new Error(errorMessage));
|
|
394
|
-
} else {
|
|
395
|
-
const fullText = chunks
|
|
396
|
-
.filter((a) => a.type === 'text')
|
|
397
|
-
.map((item) => item.text)
|
|
398
|
-
.join('');
|
|
399
|
-
|
|
400
|
-
item.resolve({ text: fullText, chunks });
|
|
401
|
-
}
|
|
402
|
-
} else {
|
|
403
|
-
const result = await this._processMessage(sessionId, item.message, item.options);
|
|
404
|
-
item.resolve(result);
|
|
405
|
-
}
|
|
406
|
-
} catch (err) {
|
|
407
|
-
this.emit('message:error', { sessionId, error: err.message });
|
|
408
|
-
item.reject(err);
|
|
409
|
-
} finally {
|
|
410
|
-
this._processingSessions.delete(sessionId);
|
|
411
|
-
// 处理下一条
|
|
412
|
-
setImmediate(() => this._processNext(sessionId));
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* 处理单条消息(子类实现)
|
|
418
|
-
* @param {string} sessionId - Session ID
|
|
419
|
-
* @param {Object} message - 消息
|
|
420
|
-
* @param {Object} options - 选项
|
|
421
|
-
* @returns {Promise}
|
|
422
|
-
* @protected
|
|
423
|
-
*/
|
|
424
|
-
async _processMessage(sessionId, message, options) {
|
|
425
|
-
// 如果设置了处理器,使用处理器
|
|
292
|
+
async _processMessageDirect(sessionId, message, options) {
|
|
426
293
|
if (this._messageProcessor) {
|
|
427
294
|
return this._messageProcessor(sessionId, message, options);
|
|
428
295
|
}
|
|
429
|
-
// 否则使用 agent 的处理逻辑
|
|
430
296
|
if (this.agent?._chatHandler) {
|
|
431
297
|
return this.agent._chatHandler._processMessage(sessionId, message, options);
|
|
432
298
|
}
|
|
@@ -434,18 +300,10 @@ class ChatSession extends EventEmitter {
|
|
|
434
300
|
}
|
|
435
301
|
|
|
436
302
|
/**
|
|
437
|
-
*
|
|
303
|
+
* 取消会话队列(委托给 ChatQueueManager)
|
|
438
304
|
* @param {string} sessionId - Session ID
|
|
439
305
|
*/
|
|
440
306
|
cancelSession(sessionId) {
|
|
441
|
-
const queue = this._sessionQueues.get(sessionId);
|
|
442
|
-
if (queue) {
|
|
443
|
-
queue.forEach((item) => {
|
|
444
|
-
item.reject(new Error('Session cancelled'));
|
|
445
|
-
});
|
|
446
|
-
queue.length = 0;
|
|
447
|
-
}
|
|
448
|
-
this._processingSessions.delete(sessionId);
|
|
449
307
|
this.emit('queue:session-removed', { sessionId });
|
|
450
308
|
}
|
|
451
309
|
|
|
@@ -455,12 +313,7 @@ class ChatSession extends EventEmitter {
|
|
|
455
313
|
* @returns {Object}
|
|
456
314
|
*/
|
|
457
315
|
getQueueStatus(sessionId) {
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
sessionId,
|
|
461
|
-
size: queue.length,
|
|
462
|
-
processing: this._processingSessions.has(sessionId),
|
|
463
|
-
};
|
|
316
|
+
return { sessionId, size: 0, processing: false };
|
|
464
317
|
}
|
|
465
318
|
|
|
466
319
|
/**
|
|
@@ -468,13 +321,6 @@ class ChatSession extends EventEmitter {
|
|
|
468
321
|
* @param {string} sessionId - Session ID
|
|
469
322
|
*/
|
|
470
323
|
clearQueue(sessionId) {
|
|
471
|
-
const queue = this._sessionQueues.get(sessionId);
|
|
472
|
-
if (queue) {
|
|
473
|
-
queue.forEach((item) => {
|
|
474
|
-
item.reject(new Error('Queue cleared'));
|
|
475
|
-
});
|
|
476
|
-
queue.length = 0;
|
|
477
|
-
}
|
|
478
324
|
this.emit('queue:cleared', { sessionId });
|
|
479
325
|
}
|
|
480
326
|
|