evolclaw 2.0.7 → 2.1.1
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/README.md +9 -4
- package/data/evolclaw.sample.json +3 -2
- package/dist/channels/feishu.js +39 -15
- package/dist/cli.js +33 -15
- package/dist/config.js +4 -1
- package/dist/core/agent-runner.js +56 -21
- package/dist/core/command-handler.js +271 -66
- package/dist/core/message-processor.js +117 -59
- package/dist/core/session-manager.js +156 -130
- package/dist/index.js +18 -14
- package/dist/index.js.bak +340 -0
- package/dist/utils/session-file-health.js +4 -3
- package/dist/utils/stream-flusher.js +42 -32
- package/package.json +1 -1
|
@@ -18,6 +18,16 @@ export class MessageProcessor {
|
|
|
18
18
|
channels = new Map();
|
|
19
19
|
currentFlusher;
|
|
20
20
|
currentIsGroup = false;
|
|
21
|
+
shouldSuppressActivities = false;
|
|
22
|
+
/** 判断是否为后台会话(仅主会话参与判断,话题会话独立) */
|
|
23
|
+
async isBackgroundSession(session, channel, channelId) {
|
|
24
|
+
// 话题会话独立运行,不是后台任务
|
|
25
|
+
if (session.threadId)
|
|
26
|
+
return false;
|
|
27
|
+
// 主会话:与当前活跃会话比对
|
|
28
|
+
const active = await this.sessionManager.getActiveSession(channel, channelId);
|
|
29
|
+
return active ? session.id !== active.id : false;
|
|
30
|
+
}
|
|
21
31
|
constructor(agentRunner, sessionManager, config, messageCache, commandHandler) {
|
|
22
32
|
this.agentRunner = agentRunner;
|
|
23
33
|
this.sessionManager = sessionManager;
|
|
@@ -35,7 +45,7 @@ export class MessageProcessor {
|
|
|
35
45
|
* 处理 compact 开始事件
|
|
36
46
|
*/
|
|
37
47
|
handleCompactStart() {
|
|
38
|
-
if (this.currentFlusher && !this.currentIsGroup) {
|
|
48
|
+
if (this.currentFlusher && !this.currentIsGroup && !this.shouldSuppressActivities) {
|
|
39
49
|
this.currentFlusher.addActivity('⏳ 会话压缩中...');
|
|
40
50
|
}
|
|
41
51
|
}
|
|
@@ -45,7 +55,7 @@ export class MessageProcessor {
|
|
|
45
55
|
async processMessage(message) {
|
|
46
56
|
const isGroup = message.isGroup ?? false;
|
|
47
57
|
this.currentIsGroup = isGroup;
|
|
48
|
-
const idleMs = this.config.idleMonitor?.timeout ??
|
|
58
|
+
const idleMs = (this.config.idleMonitor?.timeout ?? 120) * 1000;
|
|
49
59
|
const streamKey = `${message.channel}-${message.channelId}`;
|
|
50
60
|
const channelInfo = this.channels.get(message.channel);
|
|
51
61
|
const monitorEnabled = this.config.idleMonitor?.enabled !== false;
|
|
@@ -53,6 +63,20 @@ export class MessageProcessor {
|
|
|
53
63
|
const isOwnerUser = isOwner(this.config, message.channel, message.userId || '');
|
|
54
64
|
// 非主人(群聊或单聊):空闲监控静默/简短
|
|
55
65
|
const quietMode = isGroup || !isOwnerUser;
|
|
66
|
+
// 计算是否抑制中间输出(工具活动 + 流式文本)
|
|
67
|
+
const shouldSuppress = () => {
|
|
68
|
+
const mode = this.config.showActivities ?? 'all';
|
|
69
|
+
if (mode === 'all')
|
|
70
|
+
return false;
|
|
71
|
+
if (mode === 'dm-only')
|
|
72
|
+
return isGroup;
|
|
73
|
+
if (mode === 'owner-dm-only')
|
|
74
|
+
return isGroup || !isOwnerUser;
|
|
75
|
+
if (mode === 'none')
|
|
76
|
+
return true;
|
|
77
|
+
return false;
|
|
78
|
+
};
|
|
79
|
+
this.shouldSuppressActivities = shouldSuppress();
|
|
56
80
|
let monitor;
|
|
57
81
|
let monitorInterval;
|
|
58
82
|
let rejectFn;
|
|
@@ -94,7 +118,7 @@ export class MessageProcessor {
|
|
|
94
118
|
else {
|
|
95
119
|
// notify or warn: send diagnostic message, task continues(非主人时静默)
|
|
96
120
|
logger.info(`[MessageProcessor] Idle monitor: ${result.action} after ${result.idleSec}s idle, stream: ${streamKey}`);
|
|
97
|
-
if (channelInfo && !quietMode) {
|
|
121
|
+
if (channelInfo && !quietMode && !shouldSuppress()) {
|
|
98
122
|
try {
|
|
99
123
|
await channelInfo.adapter.sendText(message.channelId, result.message);
|
|
100
124
|
}
|
|
@@ -109,7 +133,7 @@ export class MessageProcessor {
|
|
|
109
133
|
});
|
|
110
134
|
try {
|
|
111
135
|
await Promise.race([
|
|
112
|
-
this._processMessageInternal(message, resetTimer, isGroup),
|
|
136
|
+
this._processMessageInternal(message, resetTimer, isGroup, shouldSuppress),
|
|
113
137
|
timeoutPromise
|
|
114
138
|
]);
|
|
115
139
|
}
|
|
@@ -119,7 +143,7 @@ export class MessageProcessor {
|
|
|
119
143
|
// 记录错误到健康状态(仅主人的错误累计触发安全模式)
|
|
120
144
|
if (channelInfo) {
|
|
121
145
|
try {
|
|
122
|
-
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, this.config.projects?.defaultPath || process.cwd());
|
|
146
|
+
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, this.config.projects?.defaultPath || process.cwd(), message.threadId);
|
|
123
147
|
const errorType = classifyError(error);
|
|
124
148
|
// 上下文过长是可恢复错误,不累计触发安全模式
|
|
125
149
|
if (errorType === ErrorType.CONTEXT_TOO_LONG) {
|
|
@@ -131,7 +155,7 @@ export class MessageProcessor {
|
|
|
131
155
|
}
|
|
132
156
|
else {
|
|
133
157
|
const newCount = await this.sessionManager.recordError(session.id, errorType, error.message);
|
|
134
|
-
await this.checkSafeMode(session
|
|
158
|
+
await this.checkSafeMode(session, message.channelId, channelInfo.adapter, safeModeThreshold, newCount);
|
|
135
159
|
}
|
|
136
160
|
}
|
|
137
161
|
catch (statusError) {
|
|
@@ -145,17 +169,27 @@ export class MessageProcessor {
|
|
|
145
169
|
clearInterval(monitorInterval);
|
|
146
170
|
}
|
|
147
171
|
}
|
|
172
|
+
/** 从 session 提取话题回复选项 */
|
|
173
|
+
getThreadSendOpts(session) {
|
|
174
|
+
const rootId = session.metadata?.feishu?.rootId;
|
|
175
|
+
return rootId ? { replyToMessageId: rootId, replyInThread: true } : undefined;
|
|
176
|
+
}
|
|
148
177
|
/**
|
|
149
178
|
* 检查是否需要进入安全模式(safeModeThreshold 为 0 时跳过)
|
|
150
179
|
* 仅单聊主人会话调用(群聊和非主人已在调用侧过滤)
|
|
151
180
|
*/
|
|
152
|
-
async checkSafeMode(
|
|
181
|
+
async checkSafeMode(session, channelId, adapter, safeModeThreshold, consecutiveErrors) {
|
|
153
182
|
if (safeModeThreshold <= 0)
|
|
154
183
|
return;
|
|
155
|
-
const health = await this.sessionManager.getHealthStatus(
|
|
184
|
+
const health = await this.sessionManager.getHealthStatus(session.id);
|
|
185
|
+
const sendOpts = this.getThreadSendOpts(session);
|
|
186
|
+
const isThread = !!session.threadId;
|
|
156
187
|
if (consecutiveErrors >= safeModeThreshold && !health.safeMode) {
|
|
157
|
-
await this.sessionManager.setSafeMode(
|
|
158
|
-
logger.warn(`[MessageProcessor] Session ${
|
|
188
|
+
await this.sessionManager.setSafeMode(session.id, true);
|
|
189
|
+
logger.warn(`[MessageProcessor] Session ${session.id} entered safe mode after ${consecutiveErrors} errors`);
|
|
190
|
+
const suggestions = isThread
|
|
191
|
+
? `1. /repair - 检查并修复会话(推荐,保留历史)\n2. /clear - 清空会话历史\n3. /status - 查看详细状态`
|
|
192
|
+
: `1. /repair - 检查并修复会话(推荐,保留历史)\n2. /new [名称] - 创建新会话(清空历史)\n3. /status - 查看详细状态`;
|
|
159
193
|
await adapter.sendText(channelId, `⚠️ 安全模式已启用(连续 ${consecutiveErrors} 次异常)
|
|
160
194
|
|
|
161
195
|
当前限制:
|
|
@@ -163,16 +197,13 @@ export class MessageProcessor {
|
|
|
163
197
|
- 每次提问需要提供完整上下文
|
|
164
198
|
|
|
165
199
|
建议操作:
|
|
166
|
-
|
|
167
|
-
2. /new [名称] - 创建新会话(清空历史)
|
|
168
|
-
3. /status - 查看详细状态`);
|
|
200
|
+
${suggestions}`, sendOpts);
|
|
169
201
|
}
|
|
170
202
|
else if (safeModeThreshold >= 2 && consecutiveErrors === safeModeThreshold - 1) {
|
|
171
|
-
|
|
172
|
-
await adapter.sendText(channelId, `⚠️ 检测到异常(${consecutiveErrors}/${safeModeThreshold})\n\n如果问题持续,系统将自动进入安全模式。建议使用 /status 查看状态。`);
|
|
203
|
+
await adapter.sendText(channelId, `⚠️ 检测到异常(${consecutiveErrors}/${safeModeThreshold})\n\n如果问题持续,系统将自动进入安全模式。建议使用 /status 查看状态。`, sendOpts);
|
|
173
204
|
}
|
|
174
205
|
}
|
|
175
|
-
async _processMessageInternal(message, resetTimer, isGroup) {
|
|
206
|
+
async _processMessageInternal(message, resetTimer, isGroup, shouldSuppress) {
|
|
176
207
|
const messageId = `${message.channel}_${message.channelId}_${message.timestamp || Date.now()}`;
|
|
177
208
|
const channelInfo = this.channels.get(message.channel);
|
|
178
209
|
if (!channelInfo) {
|
|
@@ -183,17 +214,19 @@ export class MessageProcessor {
|
|
|
183
214
|
try {
|
|
184
215
|
// 检查是否为命令
|
|
185
216
|
if (this.commandHandler) {
|
|
186
|
-
const cmdResult = await this.commandHandler(message.content, message.channel, message.channelId, message.userId);
|
|
217
|
+
const cmdResult = await this.commandHandler(message.content, message.channel, message.channelId, message.userId, message.threadId);
|
|
187
218
|
if (cmdResult) {
|
|
188
|
-
|
|
219
|
+
// 话题消息:通过 rootId 回复到话题内
|
|
220
|
+
const session = message.threadId ? await this.sessionManager.getOrCreateSession(message.channel, message.channelId, this.config.projects?.defaultPath || process.cwd(), message.threadId) : undefined;
|
|
221
|
+
const rootId = session?.metadata?.feishu?.rootId;
|
|
222
|
+
const sendOpts = rootId ? { replyToMessageId: rootId, replyInThread: true } : undefined;
|
|
223
|
+
await adapter.sendText(message.channelId, cmdResult, sendOpts);
|
|
189
224
|
return;
|
|
190
225
|
}
|
|
191
226
|
}
|
|
192
227
|
// 解析会话和项目路径
|
|
193
228
|
const { session, absoluteProjectPath } = await this.resolveSession(message);
|
|
194
|
-
|
|
195
|
-
const activeSession = await this.sessionManager.getActiveSession(message.channel, message.channelId);
|
|
196
|
-
const isBackground = activeSession ? session.id !== activeSession.id : false;
|
|
229
|
+
const isBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
197
230
|
// 记录收到消息
|
|
198
231
|
logger.message({
|
|
199
232
|
msgId: messageId,
|
|
@@ -217,43 +250,47 @@ export class MessageProcessor {
|
|
|
217
250
|
let firstReply = true;
|
|
218
251
|
const messageIsGroup = isGroup; // 捕获 isGroup 供闭包使用
|
|
219
252
|
const flusher = new StreamFlusher(async (text, isFinal) => {
|
|
220
|
-
|
|
221
|
-
const currentActiveSession = await this.sessionManager.getActiveSession(message.channel, message.channelId);
|
|
222
|
-
const isCurrentlyBackground = currentActiveSession ? session.id !== currentActiveSession.id : false;
|
|
253
|
+
const isCurrentlyBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
223
254
|
if (!isCurrentlyBackground) {
|
|
224
255
|
const opts = {};
|
|
225
256
|
if (isFinal)
|
|
226
257
|
opts.title = '最终回复:';
|
|
227
|
-
//
|
|
228
|
-
|
|
258
|
+
// 话题会话:所有回复指向 rootId + reply_in_thread(确保消息进入话题)
|
|
259
|
+
const rootId = session.metadata?.feishu?.rootId;
|
|
260
|
+
if (rootId) {
|
|
261
|
+
opts.replyToMessageId = rootId;
|
|
262
|
+
opts.replyInThread = true;
|
|
263
|
+
}
|
|
264
|
+
else if (firstReply && message.messageId) {
|
|
265
|
+
// 主会话:首条消息引用回复用户原消息
|
|
229
266
|
opts.replyToMessageId = message.messageId;
|
|
230
267
|
firstReply = false;
|
|
231
268
|
}
|
|
232
269
|
await adapter.sendText(message.channelId, text, Object.keys(opts).length ? opts : undefined);
|
|
233
270
|
}
|
|
234
271
|
// 后台任务:静默,不发送输出
|
|
235
|
-
}, this.config.flushDelay
|
|
272
|
+
}, (this.config.flushDelay || 4) * 1000, options?.fileMarkerPattern, this.config.debug?.flusherDiag);
|
|
236
273
|
// 保存当前 flusher,用于 compact 事件
|
|
237
274
|
this.currentFlusher = flusher;
|
|
238
275
|
// 调用 AgentRunner(含上下文过长自动 compact 重试)
|
|
239
276
|
const streamKey = `${message.channel}-${message.channelId}`;
|
|
240
277
|
try {
|
|
241
|
-
const stream = await this.agentRunner.runQuery(session.id, message.content, absoluteProjectPath, session.
|
|
278
|
+
const stream = await this.agentRunner.runQuery(session.id, message.content, absoluteProjectPath, session.agentSessionId, message.images, options?.systemPromptAppend, this.sessionManager);
|
|
242
279
|
this.agentRunner.registerStream(streamKey, stream);
|
|
243
|
-
await this.processEventStream(stream, session, message.channelId, adapter, options, flusher, isBackground, resetTimer);
|
|
280
|
+
await this.processEventStream(stream, session, message.channelId, adapter, options, flusher, isBackground, resetTimer, shouldSuppress);
|
|
244
281
|
}
|
|
245
282
|
catch (error) {
|
|
246
|
-
if (classifyError(error) === ErrorType.CONTEXT_TOO_LONG && session.
|
|
283
|
+
if (classifyError(error) === ErrorType.CONTEXT_TOO_LONG && session.agentSessionId) {
|
|
247
284
|
// 尝试 compact 压缩会话
|
|
248
285
|
flusher.addActivity('⚠️ 上下文过长,正在压缩会话...');
|
|
249
286
|
await flusher.flush();
|
|
250
|
-
const compacted = await this.agentRunner.compactSession(session.id, session.
|
|
287
|
+
const compacted = await this.agentRunner.compactSession(session.id, session.agentSessionId, absoluteProjectPath);
|
|
251
288
|
if (compacted) {
|
|
252
289
|
// compact 成功,带 resume 重试
|
|
253
290
|
flusher.addActivity('✅ 压缩完成,正在重试...');
|
|
254
|
-
const retryStream = await this.agentRunner.runQuery(session.id, message.content, absoluteProjectPath, session.
|
|
291
|
+
const retryStream = await this.agentRunner.runQuery(session.id, message.content, absoluteProjectPath, session.agentSessionId, message.images, options?.systemPromptAppend, this.sessionManager);
|
|
255
292
|
this.agentRunner.registerStream(streamKey, retryStream);
|
|
256
|
-
await this.processEventStream(retryStream, session, message.channelId, adapter, options, flusher, isBackground, resetTimer);
|
|
293
|
+
await this.processEventStream(retryStream, session, message.channelId, adapter, options, flusher, isBackground, resetTimer, shouldSuppress);
|
|
257
294
|
}
|
|
258
295
|
else {
|
|
259
296
|
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
@@ -278,7 +315,7 @@ export class MessageProcessor {
|
|
|
278
315
|
// 文件存在性检查:真实路径但文件不存在,告知用户
|
|
279
316
|
if (!fs.existsSync(resolvedPath)) {
|
|
280
317
|
logger.warn(`[${adapter.name}] File not found: ${resolvedPath}`);
|
|
281
|
-
await adapter.sendText(message.channelId, `⚠️ 文件未找到: ${filePath}
|
|
318
|
+
await adapter.sendText(message.channelId, `⚠️ 文件未找到: ${filePath}`, this.getThreadSendOpts(session));
|
|
282
319
|
continue;
|
|
283
320
|
}
|
|
284
321
|
logger.info(`[${adapter.name}] Sending file: ${resolvedPath}`);
|
|
@@ -287,7 +324,7 @@ export class MessageProcessor {
|
|
|
287
324
|
}
|
|
288
325
|
catch (error) {
|
|
289
326
|
logger.error(`[${adapter.name}] Failed to send file: ${resolvedPath}`, error);
|
|
290
|
-
await adapter.sendText(message.channelId, `❌ 文件发送失败: ${filePath}
|
|
327
|
+
await adapter.sendText(message.channelId, `❌ 文件发送失败: ${filePath}`, this.getThreadSendOpts(session));
|
|
291
328
|
}
|
|
292
329
|
}
|
|
293
330
|
}
|
|
@@ -296,15 +333,16 @@ export class MessageProcessor {
|
|
|
296
333
|
// 安全模式尾部提示:如果当前会话处于安全模式,追加提醒
|
|
297
334
|
const healthStatus = await this.sessionManager.getHealthStatus(session.id);
|
|
298
335
|
if (healthStatus.safeMode) {
|
|
299
|
-
|
|
336
|
+
const hint = session.threadId
|
|
337
|
+
? '\n\n⚠️ 当前处于安全模式(无上下文记忆)。使用 /repair 修复 或 /clear 清空会话'
|
|
338
|
+
: '\n\n⚠️ 当前处于安全模式(无上下文记忆)。使用 /repair 修复 或 /new 新建会话';
|
|
339
|
+
await adapter.sendText(message.channelId, hint, this.getThreadSendOpts(session));
|
|
300
340
|
}
|
|
301
341
|
// 清理 activeStreams(正常完成)
|
|
302
342
|
this.agentRunner.cleanupStream(streamKey);
|
|
303
343
|
// 记录成功响应(重置错误计数)
|
|
304
344
|
await this.sessionManager.recordSuccess(session.id);
|
|
305
|
-
|
|
306
|
-
const currentActive = await this.sessionManager.getActiveSession(message.channel, message.channelId);
|
|
307
|
-
const isFinallyBackground = currentActive ? session.id !== currentActive.id : false;
|
|
345
|
+
const isFinallyBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
308
346
|
if (isFinallyBackground) {
|
|
309
347
|
const projectName = path.basename(session.projectPath);
|
|
310
348
|
const count = this.messageCache.getCount(session.id);
|
|
@@ -346,7 +384,14 @@ export class MessageProcessor {
|
|
|
346
384
|
}
|
|
347
385
|
else {
|
|
348
386
|
const userMessage = getErrorMessage(error);
|
|
349
|
-
|
|
387
|
+
// 获取 session 用于话题回复(如果 resolveSession 已执行)
|
|
388
|
+
let sendOpts;
|
|
389
|
+
try {
|
|
390
|
+
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, this.config.projects?.defaultPath || process.cwd(), message.threadId);
|
|
391
|
+
sendOpts = this.getThreadSendOpts(session);
|
|
392
|
+
}
|
|
393
|
+
catch { }
|
|
394
|
+
await adapter.sendText(message.channelId, userMessage, sendOpts);
|
|
350
395
|
}
|
|
351
396
|
}
|
|
352
397
|
}
|
|
@@ -354,7 +399,11 @@ export class MessageProcessor {
|
|
|
354
399
|
* 解析会话和项目路径
|
|
355
400
|
*/
|
|
356
401
|
async resolveSession(message) {
|
|
357
|
-
|
|
402
|
+
// 话题会话:传入 rootId metadata(首条消息的 messageId 作为 rootId)
|
|
403
|
+
const metadata = message.threadId && message.messageId
|
|
404
|
+
? { feishu: { rootId: message.messageId } }
|
|
405
|
+
: undefined;
|
|
406
|
+
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, this.config.projects?.defaultPath || process.cwd(), message.threadId, metadata);
|
|
358
407
|
const absoluteProjectPath = path.isAbsolute(session.projectPath)
|
|
359
408
|
? session.projectPath
|
|
360
409
|
: path.resolve(process.cwd(), session.projectPath);
|
|
@@ -363,7 +412,7 @@ export class MessageProcessor {
|
|
|
363
412
|
/**
|
|
364
413
|
* 处理事件流
|
|
365
414
|
*/
|
|
366
|
-
async processEventStream(stream, session, channelId, adapter, options, flusher, isBackground, resetTimer) {
|
|
415
|
+
async processEventStream(stream, session, channelId, adapter, options, flusher, isBackground, resetTimer, shouldSuppress) {
|
|
367
416
|
let hasTextDelta = false;
|
|
368
417
|
let hasReceivedText = false;
|
|
369
418
|
let lastSessionId;
|
|
@@ -382,20 +431,20 @@ export class MessageProcessor {
|
|
|
382
431
|
this.agentRunner.updateSessionId(session.id, event.session_id);
|
|
383
432
|
lastSessionId = event.session_id;
|
|
384
433
|
}
|
|
385
|
-
|
|
386
|
-
const currentActive = await this.sessionManager.getActiveSession(session.channel, session.channelId);
|
|
387
|
-
const isCurrentlyBackground = currentActive ? session.id !== currentActive.id : false;
|
|
434
|
+
const isCurrentlyBackground = await this.isBackgroundSession(session, session.channel, session.channelId);
|
|
388
435
|
// === 前台任务:正常处理所有事件 ===
|
|
389
436
|
if (!isCurrentlyBackground) {
|
|
390
|
-
//
|
|
437
|
+
// 流式文本事件(抑制时跳过,只累积到 allText)
|
|
391
438
|
if (event.type === 'text_delta' && event.text) {
|
|
392
439
|
hasTextDelta = true;
|
|
393
440
|
hasReceivedText = true;
|
|
394
|
-
|
|
441
|
+
if (!shouldSuppress()) {
|
|
442
|
+
flusher.addText(event.text);
|
|
443
|
+
}
|
|
395
444
|
}
|
|
396
445
|
// 系统事件:compact_boundary(群聊时静默)
|
|
397
446
|
if (event.type === 'system' && event.subtype === 'compact_boundary') {
|
|
398
|
-
if (!this.currentIsGroup) {
|
|
447
|
+
if (!this.currentIsGroup && !shouldSuppress()) {
|
|
399
448
|
const preTokens = event.compact_metadata?.pre_tokens || 0;
|
|
400
449
|
flusher.addActivity(`💡 会话压缩完成,继续执行...(压缩前 tokens: ${preTokens})`);
|
|
401
450
|
}
|
|
@@ -406,10 +455,10 @@ export class MessageProcessor {
|
|
|
406
455
|
const duration = event.duration_ms ? `${Math.round(event.duration_ms / 1000)}s` : '';
|
|
407
456
|
const summary = event.summary;
|
|
408
457
|
const stats = [tools > 0 ? `${tools}次工具调用` : '', duration].filter(Boolean).join(', ');
|
|
409
|
-
if (summary) {
|
|
458
|
+
if (summary && !shouldSuppress()) {
|
|
410
459
|
flusher.addActivity(`⏳ 子任务: ${summary}${stats ? ` (${stats})` : ''}`);
|
|
411
460
|
}
|
|
412
|
-
else if (stats) {
|
|
461
|
+
else if (stats && !shouldSuppress()) {
|
|
413
462
|
flusher.addActivity(`⏳ 子任务进行中: ${stats}`);
|
|
414
463
|
}
|
|
415
464
|
}
|
|
@@ -417,33 +466,42 @@ export class MessageProcessor {
|
|
|
417
466
|
if (event.type === 'assistant' && event.message?.content) {
|
|
418
467
|
for (const content of event.message.content) {
|
|
419
468
|
if (content.type === 'tool_use') {
|
|
420
|
-
|
|
421
|
-
|
|
469
|
+
if (!shouldSuppress()) {
|
|
470
|
+
const desc = this.formatToolDescription(content);
|
|
471
|
+
flusher.addActivity(`🔧 ${content.name}${desc ? ': ' + desc : ''}`);
|
|
472
|
+
}
|
|
422
473
|
}
|
|
423
474
|
else if (content.type === 'text' && content.text && !hasTextDelta) {
|
|
424
475
|
// 仅在没有 text_delta 事件时从 assistant 事件提取文本,避免重复
|
|
425
476
|
hasReceivedText = true;
|
|
426
|
-
|
|
477
|
+
if (!shouldSuppress()) {
|
|
478
|
+
flusher.addTextBlock(content.text);
|
|
479
|
+
}
|
|
427
480
|
}
|
|
428
481
|
}
|
|
429
482
|
}
|
|
430
483
|
// 工具结果事件:显示失败信息(包括权限拒绝、执行失败等所有场景)
|
|
431
484
|
if (event.type === 'tool_result') {
|
|
432
485
|
logger.debug(`[MessageProcessor] tool_result: is_error=${event.is_error}, error=${event.error}, content=${typeof event.content}`);
|
|
433
|
-
if (event.is_error) {
|
|
486
|
+
if (event.is_error && !shouldSuppress()) {
|
|
434
487
|
const toolName = event.tool_name || '工具';
|
|
435
488
|
const errorMsg = event.error || (typeof event.content === 'string' ? event.content : JSON.stringify(event.content)) || '执行失败';
|
|
436
489
|
flusher.addActivity(`⚠️ ${toolName}: ${errorMsg}`);
|
|
437
490
|
}
|
|
438
491
|
}
|
|
439
|
-
// Result
|
|
492
|
+
// Result 事件:最终输出
|
|
440
493
|
if (event.type === 'result' && event.result) {
|
|
441
|
-
logger.debug(`[MessageProcessor] result event: hasReceivedText=${hasReceivedText}, result="${event.result}"`);
|
|
442
|
-
if (
|
|
443
|
-
//
|
|
494
|
+
logger.debug(`[MessageProcessor] result event: hasReceivedText=${hasReceivedText}, shouldSuppress=${shouldSuppress()}, result="${event.result}"`);
|
|
495
|
+
if (shouldSuppress()) {
|
|
496
|
+
// 抑制模式:直接发送 result(跳过中间输出)
|
|
497
|
+
flusher.addText(event.result);
|
|
498
|
+
}
|
|
499
|
+
else if (!hasReceivedText) {
|
|
500
|
+
// 非抑制模式 + 无流式文本:使用 result 作为兜底
|
|
444
501
|
flusher.addText(event.result);
|
|
445
502
|
}
|
|
446
|
-
|
|
503
|
+
// 非抑制模式 + 有流式文本:已通过 text_delta 累积,无需再添加
|
|
504
|
+
await flusher.flush(true); // isFinal=true 标记最终输出
|
|
447
505
|
}
|
|
448
506
|
continue;
|
|
449
507
|
}
|