foliko 1.1.11 → 1.1.13
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/data/default.json +4 -0
- package/.agent/memory/feedback/mnvzgvtd-0o2900.md +9 -0
- package/.agent/memory/feedback/mnvzhajn-swbx61.md +15 -0
- package/.agent/memory/feedback/mnvzhgsp-p5vog3.md +9 -0
- package/.agent/memory/feedback/mnvzho0c-fgql7q.md +14 -0
- package/.agent/memory/feedback/mnvzhtzq-ufr5at.md +9 -0
- package/.agent/memory/feedback/mnvzhyb3-9byq2z.md +9 -0
- package/.agent/memory/feedback/mnvzi7hp-hyeafp.md +9 -0
- package/.agent/memory/feedback/mnvzibph-z7rwp5.md +9 -0
- package/.agent/memory/feedback/mnvzilys-7h176w.md +14 -0
- package/.agent/memory/feedback/mnvziuh5-zjshci.md +9 -0
- package/.agent/memory/feedback/mnw07wde-6zqsc8.md +9 -0
- package/.agent/memory/feedback/mnw084bp-j0ba2a.md +9 -0
- package/.agent/sessions/cli_default.json +366 -100
- package/.agent/sessions/test.json +16 -0
- package/.claude/settings.local.json +2 -1
- package/cli/src/ui/chat-ui.js +35 -13
- package/package.json +1 -1
- package/plugins/extension-executor-plugin.js +1 -1
- package/plugins/python-plugin-loader.js +11 -11
- package/plugins/scheduler-plugin.js +1 -6
- package/plugins/subagent-plugin.js +26 -15
- package/plugins/web-plugin.js +1 -1
- package/plugins/weixin-plugin.js +38 -45
- package/src/core/agent-chat.js +352 -218
- package/src/core/agent.js +16 -8
- package/src/core/framework.js +6 -3
- package/src/utils/chat-queue.js +244 -0
- package/system.md +11 -79
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +0 -211
package/src/core/agent-chat.js
CHANGED
|
@@ -6,25 +6,10 @@
|
|
|
6
6
|
const { EventEmitter } = require('../utils/event-emitter');
|
|
7
7
|
const { logger } = require('../utils/logger');
|
|
8
8
|
const { generateText, stepCountIs, isLoopFinished } = require('ai');
|
|
9
|
-
const { prepareMessagesForAPI } = require('../utils');
|
|
9
|
+
const { prepareMessagesForAPI, cleanResponse } = require('../utils');
|
|
10
|
+
const { ChatQueueManager } = require('../utils/chat-queue');
|
|
10
11
|
const fs = require('fs/promises');
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* 从消息中提取 toolCallId(兼容两种格式)
|
|
14
|
-
* 格式1: msg.toolCallId (旧格式)
|
|
15
|
-
* 格式2: msg.content[0].toolCallId (AI SDK CoreMessage 格式)
|
|
16
|
-
* @param {Object} msg - 消息对象
|
|
17
|
-
* @returns {string|null}
|
|
18
|
-
*/
|
|
19
|
-
function extractToolCallId(msg) {
|
|
20
|
-
if (msg.toolCallId) return msg.toolCallId;
|
|
21
|
-
if (Array.isArray(msg.content) && msg.content.length > 0) {
|
|
22
|
-
const firstContent = msg.content[0];
|
|
23
|
-
if (firstContent && firstContent.toolCallId) return firstContent.toolCallId;
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
13
|
// 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
|
|
29
14
|
const MODEL_CONTEXT_LIMITS = {
|
|
30
15
|
// DeepSeek
|
|
@@ -96,102 +81,6 @@ const _globalTokenizer = new SimpleTokenizer();
|
|
|
96
81
|
// 压缩超时时间(毫秒)
|
|
97
82
|
const COMPRESSION_TIMEOUT = 120000;
|
|
98
83
|
|
|
99
|
-
/**
|
|
100
|
-
* ChatStreamQueue - 流式消息队列
|
|
101
|
-
* 按顺序处理消息,每条消息使用 chatStream 流式输出
|
|
102
|
-
*/
|
|
103
|
-
class ChatStreamQueue {
|
|
104
|
-
constructor(chatHandler) {
|
|
105
|
-
this.chatHandler = chatHandler;
|
|
106
|
-
this.queue = []; // 消息队列
|
|
107
|
-
this.isProcessing = false;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 添加消息到队列,返回 Promise
|
|
111
|
-
enqueue(message, options = {}) {
|
|
112
|
-
const { onChunk, onComplete, onError, sessionId } = options;
|
|
113
|
-
|
|
114
|
-
return new Promise((resolve, reject) => {
|
|
115
|
-
this.queue.push({
|
|
116
|
-
message,
|
|
117
|
-
sessionId,
|
|
118
|
-
onChunk,
|
|
119
|
-
onComplete,
|
|
120
|
-
onError,
|
|
121
|
-
resolve,
|
|
122
|
-
reject,
|
|
123
|
-
});
|
|
124
|
-
this.process();
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// 推送消息到队列(供回调中使用)
|
|
129
|
-
push(message, options = {}) {
|
|
130
|
-
return this.enqueue(message, options);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 按顺序处理
|
|
134
|
-
async process() {
|
|
135
|
-
if (this.isProcessing || this.queue.length === 0) return;
|
|
136
|
-
|
|
137
|
-
this.isProcessing = true;
|
|
138
|
-
const task = this.queue.shift();
|
|
139
|
-
|
|
140
|
-
// 创建 push 上下文,传递给回调
|
|
141
|
-
const ctx = {
|
|
142
|
-
push: (msg, opts = {}) => {
|
|
143
|
-
this.push(msg, { ...task.options, ...opts });
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
const result = await this.sendMessage(task);
|
|
149
|
-
if (result.type === 'error') {
|
|
150
|
-
task.reject(new Error(result.error));
|
|
151
|
-
if (task.onError) {
|
|
152
|
-
await task.onError(new Error(result.error), ctx);
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
task.resolve(result.text);
|
|
156
|
-
if (task.onComplete) {
|
|
157
|
-
await task.onComplete(result.text, ctx);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} catch (error) {
|
|
161
|
-
task.reject(error);
|
|
162
|
-
if (task.onError) {
|
|
163
|
-
await task.onError(error, ctx);
|
|
164
|
-
}
|
|
165
|
-
} finally {
|
|
166
|
-
this.isProcessing = false;
|
|
167
|
-
this.process(); // 处理下一个
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 发送消息,使用 chatStream 流式输出
|
|
172
|
-
async sendMessage(task) {
|
|
173
|
-
const { message, sessionId, onChunk } = task;
|
|
174
|
-
let fullText = '';
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
for await (const chunk of this.chatHandler.chatStream(message, { sessionId })) {
|
|
178
|
-
// 检查错误类型的 chunk
|
|
179
|
-
if (chunk.type === 'error') {
|
|
180
|
-
return { type: 'error', error: chunk.error, text: fullText };
|
|
181
|
-
}
|
|
182
|
-
fullText += chunk.text || '';
|
|
183
|
-
if (onChunk) {
|
|
184
|
-
onChunk(chunk);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} catch (err) {
|
|
188
|
-
return { type: 'error', error: err.message, text: fullText };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return { type: 'complete', text: fullText };
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
84
|
class AgentChatHandler extends EventEmitter {
|
|
196
85
|
/**
|
|
197
86
|
* @param {Agent} agent - Agent 实例
|
|
@@ -243,8 +132,20 @@ class AgentChatHandler extends EventEmitter {
|
|
|
243
132
|
this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
|
|
244
133
|
this._processingSessions = new Set(); // sessionId -> processing flag
|
|
245
134
|
|
|
246
|
-
//
|
|
247
|
-
this.
|
|
135
|
+
// 初始化队列管理器
|
|
136
|
+
this.queueManager = new ChatQueueManager({
|
|
137
|
+
maxConcurrent: config.maxConcurrent || 1,
|
|
138
|
+
retryAttempts: config.retryAttempts || 3,
|
|
139
|
+
retryDelay: config.retryDelay || 1000,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 转发队列事件到 AgentChatHandler
|
|
143
|
+
this.forwardQueueEvents();
|
|
144
|
+
|
|
145
|
+
// 可选:启用队列日志
|
|
146
|
+
// if (config.enableQueueLogging) {
|
|
147
|
+
// this.enableQueueLogging();
|
|
148
|
+
// }
|
|
248
149
|
|
|
249
150
|
// Session Memory prepareStep(从 memory 插件获取)
|
|
250
151
|
this._sessionMemoryPrepareStep = null;
|
|
@@ -257,6 +158,334 @@ class AgentChatHandler extends EventEmitter {
|
|
|
257
158
|
}
|
|
258
159
|
}
|
|
259
160
|
|
|
161
|
+
/**
|
|
162
|
+
* 转发队列事件到自身,方便外部监听
|
|
163
|
+
*/
|
|
164
|
+
forwardQueueEvents() {
|
|
165
|
+
const events = [
|
|
166
|
+
'queue:added',
|
|
167
|
+
'queue:processing',
|
|
168
|
+
'queue:completed',
|
|
169
|
+
'queue:failed',
|
|
170
|
+
'queue:retry',
|
|
171
|
+
'queue:empty',
|
|
172
|
+
'queue:cleared',
|
|
173
|
+
'queue:session-removed',
|
|
174
|
+
'stream:chunk',
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
events.forEach((eventName) => {
|
|
178
|
+
this.queueManager.on(eventName, (data) => {
|
|
179
|
+
// 转发事件,保持 this 指向 AgentChatHandler
|
|
180
|
+
this.emit(eventName, data);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Session 事件作用域管理
|
|
185
|
+
this._sessionScopes = new Map(); // sessionId -> Set<{event, handler}>
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 创建 Session 作用域的事件监听器
|
|
190
|
+
* 所有通过返回对象注册的事件监听器都只接收属于指定 sessionId 的事件
|
|
191
|
+
* @param {string} sessionId - Session ID
|
|
192
|
+
* @returns {Object} { on, once, off, removeAllListeners }
|
|
193
|
+
*/
|
|
194
|
+
createSessionScope(sessionId) {
|
|
195
|
+
const scope = this;
|
|
196
|
+
const handlers = new Set(); // 记录这个 scope 注册的所有 handler
|
|
197
|
+
|
|
198
|
+
// 创建包装 handler,自动过滤 sessionId
|
|
199
|
+
const wrapHandler = (handler) => {
|
|
200
|
+
return (data) => {
|
|
201
|
+
// 检查 data 中的 sessionId
|
|
202
|
+
if (data && data.sessionId !== sessionId) {
|
|
203
|
+
return; // 不是当前 scope 的 session,跳过
|
|
204
|
+
}
|
|
205
|
+
handler(data);
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const wrappedHandlers = new WeakMap(); // original -> wrapped
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
/**
|
|
213
|
+
* 监听事件(自动过滤 sessionId)
|
|
214
|
+
*/
|
|
215
|
+
on(event, handler) {
|
|
216
|
+
const wrapped = wrapHandler(handler);
|
|
217
|
+
wrappedHandlers.set(handler, wrapped);
|
|
218
|
+
handlers.add({ event, handler, wrapped });
|
|
219
|
+
scope.on(event, wrapped);
|
|
220
|
+
return this;
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 监听一次性事件
|
|
225
|
+
*/
|
|
226
|
+
once(event, handler) {
|
|
227
|
+
const wrapped = wrapHandler(handler);
|
|
228
|
+
wrappedHandlers.set(handler, wrapped);
|
|
229
|
+
handlers.add({ event, handler, wrapped });
|
|
230
|
+
scope.once(event, wrapped);
|
|
231
|
+
return this;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 取消监听(精确移除)
|
|
236
|
+
*/
|
|
237
|
+
off(event, handler) {
|
|
238
|
+
const wrapped = wrappedHandlers.get(handler);
|
|
239
|
+
if (wrapped) {
|
|
240
|
+
scope.off(event, wrapped);
|
|
241
|
+
wrappedHandlers.delete(handler);
|
|
242
|
+
}
|
|
243
|
+
// 从 handlers 中移除
|
|
244
|
+
for (const item of handlers) {
|
|
245
|
+
if (item.event === event && item.handler === handler) {
|
|
246
|
+
handlers.delete(item);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return this;
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 移除当前 scope 注册的所有监听器
|
|
255
|
+
*/
|
|
256
|
+
removeAllListeners() {
|
|
257
|
+
for (const { event, wrapped } of handlers) {
|
|
258
|
+
scope.off(event, wrapped);
|
|
259
|
+
}
|
|
260
|
+
handlers.clear();
|
|
261
|
+
return this;
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 获取当前绑定的 sessionId
|
|
266
|
+
*/
|
|
267
|
+
getSessionId() {
|
|
268
|
+
return sessionId;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 启用队列日志(可选)
|
|
275
|
+
*/
|
|
276
|
+
enableQueueLogging() {
|
|
277
|
+
this.queueManager.on('queue:added', (data) => {
|
|
278
|
+
console.log(`[Queue] Request ${data.requestId} added. Queue length: ${data.queueLength}`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this.queueManager.on('queue:processing', (data) => {
|
|
282
|
+
console.log(
|
|
283
|
+
`[Queue] Processing ${data.requestId}, Active: ${data.activeCount}, Remaining: ${data.remaining}`
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
this.queueManager.on('queue:completed', (data) => {
|
|
288
|
+
console.log(`[Queue] Request ${data.requestId} completed in ${data.duration}ms`);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
this.queueManager.on('queue:failed', (data) => {
|
|
292
|
+
console.error(`[Queue] Request ${data.requestId} failed: ${data.error}`);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this.queueManager.on('queue:empty', () => {
|
|
296
|
+
console.log('[Queue] Queue is empty');
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 发送消息(带队列)
|
|
302
|
+
*/
|
|
303
|
+
async sendMessage(message, options = {}) {
|
|
304
|
+
const sessionId = options.sessionId || this.getDefaultSessionId();
|
|
305
|
+
const requestId = this.generateRequestId();
|
|
306
|
+
|
|
307
|
+
this.emit('message:start', {
|
|
308
|
+
requestId,
|
|
309
|
+
sessionId,
|
|
310
|
+
message,
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const result = await this.queueManager.enqueue(requestId, sessionId, message, {
|
|
316
|
+
...options,
|
|
317
|
+
executeFunction: this.chatStream.bind(this),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
this.emit('message:complete', {
|
|
321
|
+
...result,
|
|
322
|
+
requestId,
|
|
323
|
+
sessionId,
|
|
324
|
+
duration: Date.now() - result.startTime,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return result;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.emit('message:error', {
|
|
330
|
+
requestId,
|
|
331
|
+
sessionId,
|
|
332
|
+
error: error.message,
|
|
333
|
+
});
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 流式发送消息(带队列,支持实时yield)
|
|
340
|
+
*/
|
|
341
|
+
async *sendMessageStream(message, options = {}) {
|
|
342
|
+
const sessionId = options.sessionId || this.getDefaultSessionId();
|
|
343
|
+
const requestId = this.generateRequestId();
|
|
344
|
+
|
|
345
|
+
// 用于存储流式数据
|
|
346
|
+
const chunkQueue = [];
|
|
347
|
+
let streamCompleted = false;
|
|
348
|
+
let streamError = null;
|
|
349
|
+
let resolveNext = null;
|
|
350
|
+
|
|
351
|
+
// 监听流式数据
|
|
352
|
+
const chunkHandler = (data) => {
|
|
353
|
+
if (data.requestId === requestId) {
|
|
354
|
+
//chunkQueue.push(data.chunk);
|
|
355
|
+
if (resolveNext) {
|
|
356
|
+
resolveNext();
|
|
357
|
+
resolveNext = null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const completeHandler = (data) => {
|
|
363
|
+
if (data.requestId === requestId) {
|
|
364
|
+
streamCompleted = true;
|
|
365
|
+
if (resolveNext) {
|
|
366
|
+
resolveNext();
|
|
367
|
+
resolveNext = null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const errorHandler = (data) => {
|
|
373
|
+
if (data.requestId === requestId) {
|
|
374
|
+
streamError = new Error(data.error);
|
|
375
|
+
if (resolveNext) {
|
|
376
|
+
resolveNext();
|
|
377
|
+
resolveNext = null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// 注册事件监听
|
|
383
|
+
this.on('stream:chunk', chunkHandler);
|
|
384
|
+
this.on('queue:completed', completeHandler);
|
|
385
|
+
this.on('queue:failed', errorHandler);
|
|
386
|
+
|
|
387
|
+
// 启动队列任务(不等待完成)
|
|
388
|
+
const queuePromise = this.queueManager
|
|
389
|
+
.enqueue(requestId, sessionId, message, {
|
|
390
|
+
...options,
|
|
391
|
+
executeFunction: this.chatStream.bind(this),
|
|
392
|
+
})
|
|
393
|
+
.catch((err) => {
|
|
394
|
+
if (!streamError) {
|
|
395
|
+
streamError = err;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
// 实时 yield 流式数据
|
|
401
|
+
while (!streamCompleted && !streamError) {
|
|
402
|
+
if (chunkQueue.length > 0) {
|
|
403
|
+
yield chunkQueue.shift();
|
|
404
|
+
} else {
|
|
405
|
+
await new Promise((resolve) => {
|
|
406
|
+
resolveNext = resolve;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (streamError) {
|
|
412
|
+
throw streamError;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 等待队列完成,获取完整结果
|
|
416
|
+
const result = await queuePromise;
|
|
417
|
+
this.emit('message:complete', { ...result, requestId, sessionId });
|
|
418
|
+
} finally {
|
|
419
|
+
// 清理事件监听
|
|
420
|
+
this.off('stream:chunk', chunkHandler);
|
|
421
|
+
this.off('queue:completed', completeHandler);
|
|
422
|
+
this.off('queue:failed', errorHandler);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 批量发送消息
|
|
428
|
+
*/
|
|
429
|
+
async sendBatch(messages, options = {}) {
|
|
430
|
+
const promises = messages.map((msg, index) => {
|
|
431
|
+
return this.sendMessage(msg, {
|
|
432
|
+
...options,
|
|
433
|
+
priority: options.priorities?.[index] || 0,
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
this.emit('batch:start', { count: messages.length });
|
|
438
|
+
|
|
439
|
+
const results = await Promise.allSettled(promises);
|
|
440
|
+
|
|
441
|
+
this.emit('batch:complete', {
|
|
442
|
+
total: results.length,
|
|
443
|
+
fulfilled: results.filter((r) => r.status === 'fulfilled').length,
|
|
444
|
+
rejected: results.filter((r) => r.status === 'rejected').length,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
return results;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 取消会话的所有请求
|
|
452
|
+
*/
|
|
453
|
+
cancelSession(sessionId) {
|
|
454
|
+
const count = this.queueManager.removeSessionRequests(sessionId);
|
|
455
|
+
this.emit('session:cancelled', { sessionId, count });
|
|
456
|
+
return count;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 获取队列状态
|
|
461
|
+
*/
|
|
462
|
+
getQueueStatus() {
|
|
463
|
+
return this.queueManager.getStatus();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 清空队列
|
|
468
|
+
*/
|
|
469
|
+
clearQueue() {
|
|
470
|
+
const count = this.queueManager.clear();
|
|
471
|
+
this.emit('queue:cleared-manually', { count });
|
|
472
|
+
return count;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* 生成请求ID
|
|
477
|
+
*/
|
|
478
|
+
generateRequestId() {
|
|
479
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 获取默认会话ID
|
|
484
|
+
*/
|
|
485
|
+
getDefaultSessionId() {
|
|
486
|
+
return 'default_session';
|
|
487
|
+
}
|
|
488
|
+
|
|
260
489
|
/**
|
|
261
490
|
* 获取 Per-Session 的消息存储
|
|
262
491
|
* @param {string} sessionId - 会话 ID
|
|
@@ -543,64 +772,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
543
772
|
}
|
|
544
773
|
}
|
|
545
774
|
|
|
546
|
-
/**
|
|
547
|
-
* 估算消息列表的 token 数(不带消息结构的简单估算)
|
|
548
|
-
* 用于快速检查
|
|
549
|
-
* @param {Array} messages
|
|
550
|
-
* @returns {number}
|
|
551
|
-
* @private
|
|
552
|
-
*/
|
|
553
|
-
_roughEstimateMessagesTokens(messages) {
|
|
554
|
-
let total = 0;
|
|
555
|
-
for (const msg of messages) {
|
|
556
|
-
if (!msg) continue;
|
|
557
|
-
if (typeof msg.content === 'string') {
|
|
558
|
-
total += this._countTokens(msg.content);
|
|
559
|
-
} else if (Array.isArray(msg.content)) {
|
|
560
|
-
for (const part of msg.content) {
|
|
561
|
-
if (part && part.text) {
|
|
562
|
-
total += this._countTokens(String(part.text));
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
return total;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* 基于 API usage 和新消息估算计算当前上下文 token 数
|
|
572
|
-
* 参考 Claude Code 的 tokenCountWithEstimation 实现
|
|
573
|
-
* @param {Array} messages - 消息数组
|
|
574
|
-
* @param {Object} lastUsage - 上一次 API 返回的 usage
|
|
575
|
-
* @param {number} baseMessageCount - 基准消息数量(usage 对应时)
|
|
576
|
-
* @returns {number} 估算的总 token 数
|
|
577
|
-
* @private
|
|
578
|
-
*/
|
|
579
|
-
_tokenCountWithEstimation(messages, lastUsage, baseMessageCount = 0) {
|
|
580
|
-
// 如果有真实的 usage 数据,使用它作为基准
|
|
581
|
-
if (lastUsage && typeof lastUsage === 'object') {
|
|
582
|
-
const inputTokens = lastUsage.inputTokens || 0;
|
|
583
|
-
const outputTokens = lastUsage.outputTokens || 0;
|
|
584
|
-
const cacheReadTokens = lastUsage.inputTokenDetails?.cacheReadTokens || 0;
|
|
585
|
-
const cacheWriteTokens = lastUsage.inputTokenDetails?.cacheWriteTokens || 0;
|
|
586
|
-
|
|
587
|
-
// 计算已有消息的真实 token 数
|
|
588
|
-
const baseTokens = inputTokens + cacheReadTokens + outputTokens;
|
|
589
|
-
|
|
590
|
-
// 估算新增消息的 token 数
|
|
591
|
-
if (messages.length > baseMessageCount) {
|
|
592
|
-
const newMessages = messages.slice(baseMessageCount);
|
|
593
|
-
const newMessagesTokens = this._countMessagesTokens(newMessages);
|
|
594
|
-
return baseTokens + newMessagesTokens;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
return baseTokens;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// 没有 usage 数据,使用纯估算
|
|
601
|
-
return this._countMessagesTokens(messages);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
775
|
/**
|
|
605
776
|
* 更新 messageStore 的 lastUsage
|
|
606
777
|
* @param {Object} messageStore
|
|
@@ -1274,33 +1445,6 @@ ${truncatedContent}${truncatedNote}
|
|
|
1274
1445
|
return this.enqueue(sessionId, message, options);
|
|
1275
1446
|
}
|
|
1276
1447
|
|
|
1277
|
-
/**
|
|
1278
|
-
* 继续完成之前的响应
|
|
1279
|
-
* 当流式响应提前结束时,调用此方法设置 continue 标志
|
|
1280
|
-
* @param {string} prompt - 继续时的提示文本
|
|
1281
|
-
* @param {string} sessionId - 会话 ID
|
|
1282
|
-
* @param {Object} options - 选项 { onChunk, onComplete, onError }
|
|
1283
|
-
*/
|
|
1284
|
-
continue(prompt, sessionId = null, options = {}) {
|
|
1285
|
-
const queue = this._chatStreamQueue;
|
|
1286
|
-
const finalOptions = { sessionId, ...options };
|
|
1287
|
-
|
|
1288
|
-
// enqueue 返回 Promise
|
|
1289
|
-
const promise = queue.enqueue(
|
|
1290
|
-
prompt || '请继续完成你的回答,输出之前的完整内容。',
|
|
1291
|
-
finalOptions
|
|
1292
|
-
);
|
|
1293
|
-
|
|
1294
|
-
// 返回包含 push 方法的上下文
|
|
1295
|
-
return {
|
|
1296
|
-
push: (msg, opts = {}) => {
|
|
1297
|
-
queue.push(msg, { sessionId, ...opts });
|
|
1298
|
-
},
|
|
1299
|
-
then: promise.then.bind(promise),
|
|
1300
|
-
catch: promise.catch.bind(promise),
|
|
1301
|
-
};
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
1448
|
/**
|
|
1305
1449
|
* 准备聊天会话:刷新系统提示词、添加用户消息、压缩上下文
|
|
1306
1450
|
* @param {string|Object} message - 消息
|
|
@@ -1419,7 +1563,6 @@ ${truncatedContent}${truncatedNote}
|
|
|
1419
1563
|
}
|
|
1420
1564
|
|
|
1421
1565
|
const tools = this._getAITools(tool);
|
|
1422
|
-
|
|
1423
1566
|
const agent = new ToolLoopAgent({
|
|
1424
1567
|
model: this._aiClient,
|
|
1425
1568
|
instructions: this._systemPrompt,
|
|
@@ -1441,28 +1584,19 @@ ${truncatedContent}${truncatedNote}
|
|
|
1441
1584
|
|
|
1442
1585
|
return {
|
|
1443
1586
|
success: true,
|
|
1444
|
-
message: result.text || '',
|
|
1587
|
+
message: cleanResponse(result.text || ''),
|
|
1445
1588
|
stepCount: result.stepCount || 1,
|
|
1446
1589
|
};
|
|
1447
1590
|
} catch (err) {
|
|
1591
|
+
console.log(err);
|
|
1448
1592
|
const errorMsg = err.message || String(err);
|
|
1449
|
-
const isTransientError =
|
|
1450
|
-
errorMsg.includes('Invalid JSON response') ||
|
|
1451
|
-
errorMsg.includes('负载较高') ||
|
|
1452
|
-
errorMsg.includes('Failed after') ||
|
|
1453
|
-
err.name === 'AI_RetryError' ||
|
|
1454
|
-
err.name === 'AI_APICallError';
|
|
1455
1593
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
throw err;
|
|
1594
|
+
return {
|
|
1595
|
+
success: false,
|
|
1596
|
+
message: 'AI 服务暂时不可用,请稍后重试。',
|
|
1597
|
+
error: errorMsg,
|
|
1598
|
+
stepCount: 0,
|
|
1599
|
+
};
|
|
1466
1600
|
} finally {
|
|
1467
1601
|
const messageStore = this._getSessionMessageStore(sessionId);
|
|
1468
1602
|
messageStore.save();
|
package/src/core/agent.js
CHANGED
|
@@ -107,6 +107,7 @@ class Agent extends EventEmitter {
|
|
|
107
107
|
|
|
108
108
|
this._chatHandler = null;
|
|
109
109
|
this._tools = new Map();
|
|
110
|
+
this._skipSyncTools = config.skipSyncTools || false;
|
|
110
111
|
this._status = 'idle';
|
|
111
112
|
|
|
112
113
|
// 子Agent管理
|
|
@@ -1074,7 +1075,10 @@ class Agent extends EventEmitter {
|
|
|
1074
1075
|
enhancedMessage = `【系统通知】\n${notifications.join('\n\n')}\n\n---\n用户消息: ${message}`;
|
|
1075
1076
|
}
|
|
1076
1077
|
|
|
1077
|
-
|
|
1078
|
+
// 子agent跳过工具同步,使用createSubAgent时已配置的工具
|
|
1079
|
+
if (!this._skipSyncTools) {
|
|
1080
|
+
this._syncTools();
|
|
1081
|
+
}
|
|
1078
1082
|
|
|
1079
1083
|
// 通过 handler 的 enqueue 处理(自动排队)
|
|
1080
1084
|
const result = await this._chatHandler.chat(enhancedMessage, options);
|
|
@@ -1134,7 +1138,10 @@ class Agent extends EventEmitter {
|
|
|
1134
1138
|
enhancedMessage = `【系统通知】\n${notifications.join('\n\n')}\n\n---\n用户消息: ${message}`;
|
|
1135
1139
|
}
|
|
1136
1140
|
|
|
1137
|
-
|
|
1141
|
+
// 子agent跳过工具同步,使用createSubAgent时已配置的工具
|
|
1142
|
+
if (!this._skipSyncTools) {
|
|
1143
|
+
this._syncTools();
|
|
1144
|
+
}
|
|
1138
1145
|
|
|
1139
1146
|
yield* this._chatHandler.chatStream(enhancedMessage, options);
|
|
1140
1147
|
this._status = 'idle';
|
|
@@ -1148,14 +1155,15 @@ class Agent extends EventEmitter {
|
|
|
1148
1155
|
}
|
|
1149
1156
|
}
|
|
1150
1157
|
|
|
1158
|
+
createSessionScope(sessionid) {
|
|
1159
|
+
return this._chatHandler.createSessionScope(sessionid);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1151
1162
|
/**
|
|
1152
|
-
*
|
|
1153
|
-
* @param {string} prompt - 继续提示
|
|
1154
|
-
* @param {string} sessionId - 会话ID
|
|
1155
|
-
* @param {Object} options - 选项 { onChunk, onComplete, onError }
|
|
1163
|
+
* 发送消息(带队列)
|
|
1156
1164
|
*/
|
|
1157
|
-
|
|
1158
|
-
return this._chatHandler.
|
|
1165
|
+
async sendMessage(message, options = {}) {
|
|
1166
|
+
return this._chatHandler.sendMessage(message, options);
|
|
1159
1167
|
}
|
|
1160
1168
|
|
|
1161
1169
|
/**
|
package/src/core/framework.js
CHANGED
|
@@ -614,6 +614,7 @@ class Framework extends EventEmitter {
|
|
|
614
614
|
name: `subagent_${name}`,
|
|
615
615
|
role: role,
|
|
616
616
|
systemPrompt: role, // 使用 role 作为 systemPrompt
|
|
617
|
+
skipSyncTools: true, // 子agent跳过工具同步,使用createSubAgent时已配置的工具
|
|
617
618
|
...(llmConfig || {}), // 如果提供了独立LLM配置,合并
|
|
618
619
|
};
|
|
619
620
|
|
|
@@ -634,7 +635,6 @@ class Framework extends EventEmitter {
|
|
|
634
635
|
});
|
|
635
636
|
}
|
|
636
637
|
}
|
|
637
|
-
|
|
638
638
|
// 从父Agent继承工具
|
|
639
639
|
if (this._mainAgent) {
|
|
640
640
|
const parentToolsDefs = this._mainAgent.getTools();
|
|
@@ -643,9 +643,12 @@ class Framework extends EventEmitter {
|
|
|
643
643
|
for (const toolDef of parentToolsDefs) {
|
|
644
644
|
agent.registerTool(toolDef);
|
|
645
645
|
}
|
|
646
|
-
} else if (Array.isArray(parentTools)
|
|
646
|
+
} else if (Array.isArray(parentTools)) {
|
|
647
647
|
// 指定了要继承的工具列表
|
|
648
|
-
|
|
648
|
+
const filteredTools = parentTools.filter((toolName) =>
|
|
649
|
+
parentToolsDefs.some((def) => def.name === toolName)
|
|
650
|
+
);
|
|
651
|
+
for (const toolName of filteredTools) {
|
|
649
652
|
const toolDef = parentToolsDefs.find((t) => t.name === toolName);
|
|
650
653
|
if (toolDef) {
|
|
651
654
|
agent.registerTool(toolDef);
|