evolclaw 3.0.0 → 3.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 +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +47 -12
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +42 -1
- package/dist/channels/aun.js +427 -146
- package/dist/channels/dingtalk.js +3 -1
- package/dist/channels/feishu.js +128 -7
- package/dist/channels/qqbot.js +3 -1
- package/dist/channels/wechat.js +4 -1
- package/dist/channels/wecom.js +3 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +418 -40
- package/dist/cli/init.js +3 -4
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +666 -0
- package/dist/config-store.js +82 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +127 -99
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +93 -48
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +11 -2
- package/dist/core/message/message-log.js +8 -1
- package/dist/core/message/message-processor.js +194 -127
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +103 -65
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +7 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +186 -19
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +27 -15
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/utils/stats.js +216 -2
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +73 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
|
@@ -10,7 +10,8 @@ import { getErrorMessage, classifyError, ErrorType, ERROR_PREFIX, isInfraError,
|
|
|
10
10
|
import { summarizeToolInput } from '../permission.js';
|
|
11
11
|
import { DEFAULT_PERMISSION_MODE } from '../../types.js';
|
|
12
12
|
import { getPackageRoot, resolveRoot } from '../../paths.js';
|
|
13
|
-
import {
|
|
13
|
+
import { renderKitSections } from '../../agents/kit-renderer.js';
|
|
14
|
+
import { normalizeBaseagent } from '../../agents/baseagent-normalize.js';
|
|
14
15
|
import { renderActionAsText, renderCommandCardAsText } from '../interaction-router.js';
|
|
15
16
|
/**
|
|
16
17
|
* 构造 OutboundEnvelope —— 出站三件套的信封部分。
|
|
@@ -124,7 +125,8 @@ export class MessageProcessor {
|
|
|
124
125
|
const agent = this.agentRegistry.resolveByChannel(channelName);
|
|
125
126
|
if (!agent)
|
|
126
127
|
return null;
|
|
127
|
-
|
|
128
|
+
// chatmode 解析优先级:agent.config.chatmode > globalSettings.chatmode
|
|
129
|
+
const globalCm = agent.config?.chatmode ?? this.globalSettings.chatmode;
|
|
128
130
|
return agent.getContext(channelName, chatType, globalCm);
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
@@ -182,7 +184,7 @@ export class MessageProcessor {
|
|
|
182
184
|
'/model', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork',
|
|
183
185
|
'/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check',
|
|
184
186
|
'/p ', '/s ', '/name ', '/rewind', '/rw', '/rw ', '/activity', '/chatmode',
|
|
185
|
-
'/aid', '/agentmd',
|
|
187
|
+
'/aid', '/agentmd', '/upgrade',
|
|
186
188
|
];
|
|
187
189
|
/** 判断消息内容是否为已知命令 */
|
|
188
190
|
isKnownCommand(content) {
|
|
@@ -345,6 +347,8 @@ export class MessageProcessor {
|
|
|
345
347
|
// 为本次任务处理生成唯一 task_id(客户端生成,格式 task-{10hex})
|
|
346
348
|
const taskId = `task-${crypto.randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
|
347
349
|
const chatmode = session.sessionMode ?? 'interactive';
|
|
350
|
+
// 诊断日志:记录 inbound message_id 和生成的 task_id 的对应关系
|
|
351
|
+
logger.info(`[MessageProcessor] Task created: inboundMsgId=${message.messageId ?? 'none'} taskId=${taskId} sessionId=${session.id} chatmode=${chatmode}`);
|
|
348
352
|
// 构建带 taskId/chatmode 的 ReplyContext(本次任务所有出站消息共用)
|
|
349
353
|
const taskReplyContext = () => {
|
|
350
354
|
const base = this.getReplyContext(message);
|
|
@@ -392,7 +396,8 @@ export class MessageProcessor {
|
|
|
392
396
|
const peerLabel = peerName && peerName !== peerShort ? `${peerShort}(${peerName})` : peerShort;
|
|
393
397
|
logger.info(`[MessageProcessor] session=${session.id} task=${taskId} peer=${peerLabel} chatType=${session.chatType} sessionMode=${session.sessionMode} agentId=${session.agentId} msgChatType=${message.chatType ?? 'n/a'}`);
|
|
394
398
|
// 记录开始处理
|
|
395
|
-
|
|
399
|
+
const taskEncrypt = message.replyContext?.metadata?.encrypted != null ? !!(message.replyContext.metadata.encrypted) : undefined;
|
|
400
|
+
this.eventBus.publish({ type: 'task:started', sessionId: session.id, agentName: agentNameForStats, encrypt: taskEncrypt, chatmode: session.sessionMode || 'interactive' });
|
|
396
401
|
// 触发器消息不发 processing status(无需通知用户)
|
|
397
402
|
if (message.source !== 'trigger') {
|
|
398
403
|
adapter.send(envelope, { kind: 'status.started' }).catch(() => { });
|
|
@@ -416,6 +421,10 @@ export class MessageProcessor {
|
|
|
416
421
|
send: async (payload) => {
|
|
417
422
|
if (isAutonomous)
|
|
418
423
|
return; // autonomous session: never send to channel
|
|
424
|
+
// proactive 模式:activity.batch 是 thought 协议内容,只发给支持 thought 的 channel
|
|
425
|
+
// (不支持 thought 的 channel 静默丢弃,避免降级为普通消息)
|
|
426
|
+
if (isProactive && payload.kind === 'activity.batch' && !adapter.capabilities?.thought)
|
|
427
|
+
return;
|
|
419
428
|
const isCurrentlyBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
420
429
|
if (isCurrentlyBackground)
|
|
421
430
|
return;
|
|
@@ -431,7 +440,7 @@ export class MessageProcessor {
|
|
|
431
440
|
}
|
|
432
441
|
}
|
|
433
442
|
if (payload.kind === 'result.text' && payload.isFinal) {
|
|
434
|
-
opts.title = '\
|
|
443
|
+
opts.title = '\u2705 \u6700\u7ec8\u56de\u590d:';
|
|
435
444
|
}
|
|
436
445
|
opts.metadata = { ...(opts.metadata ?? {}), taskId, chatmode };
|
|
437
446
|
const enrichedEnvelope = { ...envelope, replyContext: opts };
|
|
@@ -488,32 +497,15 @@ export class MessageProcessor {
|
|
|
488
497
|
// 动态构建运行时上下文提示
|
|
489
498
|
const contextParts = [];
|
|
490
499
|
const currentChannelType = options?.channelType || message.channel;
|
|
491
|
-
//
|
|
492
|
-
const peerName = message.peerName || session.metadata?.peerName;
|
|
493
|
-
const peerType = message.peerType;
|
|
494
|
-
const peerId = message.peerId;
|
|
500
|
+
// 提取 self 信息
|
|
495
501
|
const adapterAny = channelInfo.adapter;
|
|
496
502
|
const selfAid = typeof adapterAny._selfAid === 'function' ? adapterAny._selfAid() : undefined;
|
|
497
503
|
const selfName = typeof adapterAny._selfName === 'function' ? adapterAny._selfName() : undefined;
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
return `${name} (${id})`;
|
|
501
|
-
return name || id || undefined;
|
|
502
|
-
};
|
|
503
|
-
const selfIdentity = formatIdentity(selfName, selfAid);
|
|
504
|
-
const peerIdentity = formatIdentity(peerName, peerId);
|
|
505
|
-
// 文件发送能力(按 channelType 去重)
|
|
506
|
-
let crossChannelTypes = [];
|
|
504
|
+
const peerName = message.peerName || session.metadata?.peerName;
|
|
505
|
+
// 文件发送能力
|
|
507
506
|
let currentCanSend = false;
|
|
508
507
|
if (!isProactive) {
|
|
509
|
-
const fileChannelTypes = new Set();
|
|
510
508
|
currentCanSend = !!(channelInfo.adapter.capabilities?.file);
|
|
511
|
-
for (const [, info] of this.channels) {
|
|
512
|
-
if (info.adapter.capabilities?.file) {
|
|
513
|
-
fileChannelTypes.add(info.options?.channelType || info.adapter.channelName);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
crossChannelTypes = [...fileChannelTypes].filter(t => t !== currentChannelType);
|
|
517
509
|
}
|
|
518
510
|
// 通道能力
|
|
519
511
|
const capParts = [];
|
|
@@ -523,49 +515,53 @@ export class MessageProcessor {
|
|
|
523
515
|
capParts.push('图片输出');
|
|
524
516
|
if (channelInfo.adapter.capabilities?.file)
|
|
525
517
|
capParts.push('文件发送');
|
|
526
|
-
// Personal layer
|
|
518
|
+
// Personal layer
|
|
527
519
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelKey);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
520
|
+
const persona = owningAgent?.getPersona?.() || undefined;
|
|
521
|
+
const working = owningAgent?.getWorkingMemory?.() || undefined;
|
|
522
|
+
if (persona)
|
|
523
|
+
contextParts.push(persona);
|
|
524
|
+
if (working)
|
|
525
|
+
contextParts.push(`[当前关注]\n${working}`);
|
|
526
|
+
// 计算 peerKey: <channel>#<urlEncode(peerId)>
|
|
527
|
+
const peerIdRaw = message.peerId;
|
|
528
|
+
const peerKey = (currentChannelType && peerIdRaw)
|
|
529
|
+
? `${currentChannelType}#${encodeURIComponent(peerIdRaw)}`
|
|
530
|
+
: undefined;
|
|
531
|
+
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
532
|
+
// Kit renderer: 组装上下文
|
|
533
|
+
const kitCtx = {
|
|
534
|
+
vars: {
|
|
535
|
+
EVOLCLAW_HOME: resolveRoot(),
|
|
536
|
+
PACKAGE_ROOT: getPackageRoot(),
|
|
537
|
+
CURRENT_PROJECT: absoluteProjectPath,
|
|
538
|
+
selfAid: selfAid || undefined,
|
|
539
|
+
selfName: selfName || undefined,
|
|
540
|
+
hasPersona: !!persona,
|
|
541
|
+
hasWorkingMemory: !!working,
|
|
542
|
+
peerId: peerIdRaw || undefined,
|
|
543
|
+
peerKey,
|
|
544
|
+
peerName: peerName || undefined,
|
|
545
|
+
peerRole: session.identity?.role || 'unknown',
|
|
546
|
+
groupId: session.metadata?.groupId || undefined,
|
|
547
|
+
scene: session.chatType ? (session.chatType === 'group' ? 'group' : 'private') : 'coding',
|
|
548
|
+
chatType: session.chatType || null,
|
|
549
|
+
channel: currentChannelType || null,
|
|
550
|
+
venueUid: undefined,
|
|
551
|
+
project: path.basename(absoluteProjectPath),
|
|
552
|
+
sessionName: session.name || undefined,
|
|
553
|
+
sessionMode: isProactive ? 'proactive' : 'interactive',
|
|
554
|
+
readonly: session.metadata?.permissionMode === 'readonly',
|
|
555
|
+
canSendFile: !isProactive && currentCanSend,
|
|
556
|
+
capabilities: capParts.length > 0 ? capParts.join('、') : undefined,
|
|
557
|
+
baseAgent: normalizedBaseagent.canonical,
|
|
558
|
+
baseAgentName: normalizedBaseagent.displayName,
|
|
559
|
+
},
|
|
560
|
+
sessionId: session.id,
|
|
561
|
+
};
|
|
562
|
+
const kitContext = renderKitSections(kitCtx);
|
|
563
|
+
if (kitContext)
|
|
564
|
+
contextParts.push(kitContext);
|
|
569
565
|
effectiveSystemPrompt = [options?.systemPromptAppend, ...contextParts].filter(Boolean).join('\n') || undefined;
|
|
570
566
|
// 可重试错误(403/429/5xx)指数退避重试,最多 3 次
|
|
571
567
|
const MAX_RETRIES = 3;
|
|
@@ -586,7 +582,7 @@ export class MessageProcessor {
|
|
|
586
582
|
if (attempt < MAX_RETRIES && isRetryableError(retryError)) {
|
|
587
583
|
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s
|
|
588
584
|
logger.warn(`[MessageProcessor] Retryable error (attempt ${attempt}/${MAX_RETRIES}), retrying in ${delay}ms:`, retryError);
|
|
589
|
-
renderer.addNotice(
|
|
585
|
+
renderer.addNotice(`API 暂时不可用,${delay / 1000}秒后重试 (${attempt}/${MAX_RETRIES})...`, 'warn', 'retry', true);
|
|
590
586
|
await renderer.flush();
|
|
591
587
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
592
588
|
continue;
|
|
@@ -598,12 +594,12 @@ export class MessageProcessor {
|
|
|
598
594
|
catch (error) {
|
|
599
595
|
if (classifyError(error) === ErrorType.CONTEXT_TOO_LONG && session.agentSessionId && hasCompact(agent)) {
|
|
600
596
|
// 尝试 compact 压缩会话
|
|
601
|
-
renderer.addNotice('
|
|
597
|
+
renderer.addNotice('上下文过长,正在压缩会话...', 'warn', 'compact-trigger', true);
|
|
602
598
|
await renderer.flush();
|
|
603
599
|
const compacted = await agent.compact(session.id, session.agentSessionId, absoluteProjectPath);
|
|
604
600
|
if (compacted) {
|
|
605
601
|
// compact 成功,带 resume 重试(不重复原始消息,让 Agent 继续未完成的工作)
|
|
606
|
-
renderer.addNotice('
|
|
602
|
+
renderer.addNotice('✅ 压缩完成,继续处理...', 'info', 'compact-retry', true);
|
|
607
603
|
const retryStream = await agent.runQuery(session.id, '上下文已自动压缩,请继续之前未完成的任务。', absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager);
|
|
608
604
|
agent.registerStream(streamKey, retryStream);
|
|
609
605
|
streamResult = await this.processEventStream(retryStream, session, renderer, resetTimer, shouldSuppress);
|
|
@@ -616,6 +612,35 @@ export class MessageProcessor {
|
|
|
616
612
|
throw error;
|
|
617
613
|
}
|
|
618
614
|
}
|
|
615
|
+
// prompt_too_long:SDK 以 complete 事件(非异常)返回,需在此处触发 compact
|
|
616
|
+
// 检测条件:terminalReason 明确为 prompt_too_long,或文本/errors 包含相关错误文本
|
|
617
|
+
const contextTooLongPattern = /prompt is too long|input is too long|上下文过长/i;
|
|
618
|
+
const errorsText = streamResult.errors?.join(' ') || '';
|
|
619
|
+
const isPromptTooLong = streamResult.isError && session.agentSessionId && hasCompact(agent) && (streamResult.terminalReason === 'prompt_too_long' ||
|
|
620
|
+
contextTooLongPattern.test(streamResult.lastReplyText) ||
|
|
621
|
+
contextTooLongPattern.test(errorsText) ||
|
|
622
|
+
contextTooLongPattern.test(streamResult.fullText));
|
|
623
|
+
if (isPromptTooLong) {
|
|
624
|
+
renderer.addNotice('上下文过长,正在压缩会话...', 'warn', 'compact-trigger', true);
|
|
625
|
+
await renderer.flush();
|
|
626
|
+
const compacted = await agent.compact(session.id, session.agentSessionId, absoluteProjectPath);
|
|
627
|
+
if (compacted) {
|
|
628
|
+
renderer.addNotice('✅ 压缩完成,继续处理...', 'info', 'compact-retry', true);
|
|
629
|
+
const retryStream = await agent.runQuery(session.id, '上下文已自动压缩,请继续之前未完成的任务。', absoluteProjectPath, session.agentSessionId, undefined, effectiveSystemPrompt, this.sessionManager);
|
|
630
|
+
agent.registerStream(streamKey, retryStream);
|
|
631
|
+
streamResult = await this.processEventStream(retryStream, session, renderer, resetTimer, shouldSuppress);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
throw new Error('CONTEXT_COMPACT_FAILED');
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else if (streamResult.isError && !isPromptTooLong && (streamResult.terminalReason === 'prompt_too_long' ||
|
|
638
|
+
contextTooLongPattern.test(streamResult.lastReplyText) ||
|
|
639
|
+
contextTooLongPattern.test(errorsText) ||
|
|
640
|
+
contextTooLongPattern.test(streamResult.fullText))) {
|
|
641
|
+
// 上下文过长但无法 auto-compact(无 session ID 或 agent 不支持),显示友好提示
|
|
642
|
+
renderer.addNotice('上下文过长,请精简提问或使用 /compact 压缩上下文', 'warn', 'context-too-long', true);
|
|
643
|
+
}
|
|
619
644
|
// 处理文件标记 - 支持 [SEND_FILE:path] 和 [SEND_FILE:channel:path]
|
|
620
645
|
// 注意:始终扫描全部文本(含中间轮),因为文件标记可能出现在任意轮次
|
|
621
646
|
// suppressed 模式下 renderer 只有最后一轮文本,需要用 streamResult.fullText(SDK 全文)兜底
|
|
@@ -698,43 +723,35 @@ export class MessageProcessor {
|
|
|
698
723
|
}
|
|
699
724
|
}
|
|
700
725
|
} // end of !isProactive
|
|
701
|
-
//
|
|
702
|
-
// suppressed 模式:中间流式文本未推送,使用最后一轮回复(回退到全文)
|
|
703
|
-
// 非 suppressed 且无流式文本:同上
|
|
704
|
-
// 非 suppressed 且有流式文本:已经逐步推送过了,不重复添加
|
|
705
|
-
// 但如果 renderer 既未发送过内容也没有 pending 内容(如 text 事件全为空),仍需兜底
|
|
726
|
+
// 最终回复文本:suppressed 模式或无 text 事件时需要兜底添加
|
|
706
727
|
const finalReplyText = streamResult.lastReplyText || streamResult.fullText;
|
|
707
|
-
// 识别 Claude SDK 本地预处理兜底(如 "Unknown skill: xxx"):
|
|
708
|
-
// 特征:无流式 text + complete.result 匹配已知模式
|
|
709
|
-
// 这类输出不是 agent 的回复意图,而是 SDK 本地拦截到的"未知斜杠命令"提示。
|
|
710
|
-
// Proactive 模式下 renderer silent,需要兜底发出以告知用户,否则用户完全无反馈。
|
|
711
|
-
const isSdkFallbackMessage = !!finalReplyText
|
|
712
|
-
&& !streamResult.hasReceivedText
|
|
713
|
-
&& /^Unknown skill:\s+\S+/i.test(finalReplyText.trim());
|
|
714
728
|
if (finalReplyText) {
|
|
715
|
-
if (isProactive &&
|
|
716
|
-
// Proactive 模式 + SDK
|
|
729
|
+
if (isProactive && !streamResult.hasReceivedText && /^Unknown skill:\s+\S+/i.test(finalReplyText.trim())) {
|
|
730
|
+
// Proactive 模式 + SDK 本地兜底:直接发送绕过 silent renderer
|
|
717
731
|
const isCurrentlyBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
718
732
|
if (!isCurrentlyBackground) {
|
|
719
733
|
await adapter.send({ ...envelope, replyContext: capturedReplyContext }, { kind: 'result.text', text: finalReplyText, isFinal: true });
|
|
720
734
|
logger.info(`[MessageProcessor] proactive SDK fallback replied task=${taskId} text="${finalReplyText.slice(0, 60)}"`);
|
|
721
735
|
}
|
|
722
736
|
}
|
|
723
|
-
else if (shouldSuppress()) {
|
|
724
|
-
renderer.addText(finalReplyText);
|
|
725
|
-
}
|
|
726
|
-
else if (!streamResult.hasReceivedText || (!renderer.hasSentContent() && !renderer.hasContent())) {
|
|
737
|
+
else if (shouldSuppress() || !streamResult.hasReceivedText) {
|
|
727
738
|
renderer.addText(finalReplyText);
|
|
728
739
|
}
|
|
729
740
|
}
|
|
730
|
-
//
|
|
731
|
-
await renderer.flush(true);
|
|
732
|
-
// 清理 activeStreams(正常完成)
|
|
741
|
+
// 先清理流和处理中状态(保证即使 flush 卡住,session 也不会永久处于"处理中")
|
|
733
742
|
agent.cleanupStream(streamKey);
|
|
734
743
|
logger.info(`[MessageProcessor] agent.cleanupStream ok: session=${session.id} task=${taskId}`);
|
|
735
|
-
// 清除处理中状态
|
|
736
744
|
this.sessionManager.clearProcessing(session.id);
|
|
737
745
|
logger.info(`[MessageProcessor] session ${session.id} processing cleared task=${taskId}`);
|
|
746
|
+
// 被用户中断(新消息打断)时跳过 flush — 新 task 已接管渠道,旧 task 的 flush 无意义且可能卡住
|
|
747
|
+
const preFlushInterrupt = this.interruptedSessions.get(session.id);
|
|
748
|
+
if (preFlushInterrupt === 'new_message' || preFlushInterrupt === 'stop' || preFlushInterrupt === 'recalled') {
|
|
749
|
+
logger.info(`[MessageProcessor] Skipping flush for interrupted task=${taskId} reason=${preFlushInterrupt}`);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
// Flush 剩余内容(文件标记已在 flush 时自动移除)
|
|
753
|
+
await renderer.flush(true);
|
|
754
|
+
}
|
|
738
755
|
// 更新 EvolAgent.lastActivity
|
|
739
756
|
if (this.agentRegistry) {
|
|
740
757
|
const owningAgent = this.agentRegistry.resolveByChannel(channelKey);
|
|
@@ -788,7 +805,7 @@ export class MessageProcessor {
|
|
|
788
805
|
adapter.send(envelope, { kind: 'status.interrupted', metadata: { reason: interruptReason } }).catch(() => { });
|
|
789
806
|
}
|
|
790
807
|
else {
|
|
791
|
-
adapter.send(envelope, { kind: 'status.completed', metadata: { durationMs } }).catch(() => { });
|
|
808
|
+
adapter.send(envelope, { kind: 'status.completed', metadata: { durationMs, numTurns: streamResult.numTurns, usage: streamResult.usage } }).catch(() => { });
|
|
792
809
|
}
|
|
793
810
|
}
|
|
794
811
|
if (message.triggerMeta) {
|
|
@@ -813,6 +830,7 @@ export class MessageProcessor {
|
|
|
813
830
|
finalText: streamResult.lastReplyText || undefined,
|
|
814
831
|
durationMs: Date.now() - startTime,
|
|
815
832
|
agentName: agentNameForStats,
|
|
833
|
+
numTurns: streamResult.numTurns,
|
|
816
834
|
timestamp: Date.now()
|
|
817
835
|
});
|
|
818
836
|
// 记录处理完成
|
|
@@ -823,22 +841,8 @@ export class MessageProcessor {
|
|
|
823
841
|
status: 'completed',
|
|
824
842
|
duration: Date.now() - startTime
|
|
825
843
|
});
|
|
826
|
-
//
|
|
827
|
-
|
|
828
|
-
const chatDir = this.sessionManager.getChatDir(session);
|
|
829
|
-
appendMessageLog(chatDir, buildOutboundEntry({
|
|
830
|
-
from: message.selfId || session.selfId || 'self',
|
|
831
|
-
to: message.peerId || message.channelId,
|
|
832
|
-
chatType: (message.chatType || session.chatType || 'private'),
|
|
833
|
-
groupId: session.metadata?.groupId ?? null,
|
|
834
|
-
msgId: `${messageId}_reply`,
|
|
835
|
-
content: streamResult.lastReplyText || streamResult.fullText,
|
|
836
|
-
replyTo: message.messageId ?? null,
|
|
837
|
-
agent: session.agentId || null,
|
|
838
|
-
model: agent.getModel?.() || null,
|
|
839
|
-
durationMs: Date.now() - startTime,
|
|
840
|
-
}));
|
|
841
|
-
}
|
|
844
|
+
// 写入消息记录(出方向)已下沉到 aun.ts:deliverTextEntry,
|
|
845
|
+
// 所有 message.send 成功后统一写入 messages.jsonl,此处不再重复写入。
|
|
842
846
|
}
|
|
843
847
|
const isFinallyBackground = await this.isBackgroundSession(session, message.channel, message.channelId);
|
|
844
848
|
if (isFinallyBackground && session.sessionMode !== 'autonomous') {
|
|
@@ -961,7 +965,20 @@ export class MessageProcessor {
|
|
|
961
965
|
: path.resolve(process.cwd(), session.projectPath);
|
|
962
966
|
return { session, absoluteProjectPath };
|
|
963
967
|
}
|
|
964
|
-
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, projectPath, message.threadId, metadata, undefined, message.peerId);
|
|
968
|
+
const session = await this.sessionManager.getOrCreateSession(message.channel, message.channelId, projectPath, message.threadId, metadata, undefined, message.peerId, undefined, undefined, undefined, undefined, message.peerType);
|
|
969
|
+
// 兜底纠正1:群聊强制 proactive
|
|
970
|
+
if (message.chatType === 'group' && session.sessionMode !== 'proactive') {
|
|
971
|
+
logger.info(`[MessageProcessor] group proactive upgrade: sessionId=${session.id} ${session.sessionMode} -> proactive`);
|
|
972
|
+
session.sessionMode = 'proactive';
|
|
973
|
+
await this.sessionManager.updateSession(session.id, { sessionMode: 'proactive' });
|
|
974
|
+
}
|
|
975
|
+
// 兜底纠正2:旧 session 创建时没传 peerType(建为 interactive),后续非 human 消息进来时升级为 proactive。
|
|
976
|
+
// 新建场景已由 getOrCreateSession 内部 resolveDefaultSessionMode 处理,这里只兜底历史会话。
|
|
977
|
+
if (message.peerType && message.peerType !== 'human' && message.peerType !== 'unknown' && session.sessionMode !== 'proactive') {
|
|
978
|
+
logger.info(`[MessageProcessor] proactive upgrade: sessionId=${session.id} ${session.sessionMode} -> proactive (peerType=${message.peerType})`);
|
|
979
|
+
session.sessionMode = 'proactive';
|
|
980
|
+
await this.sessionManager.updateSession(session.id, { sessionMode: 'proactive' });
|
|
981
|
+
}
|
|
965
982
|
// replyContext 不再写入 session.metadata(跟着 message 走,避免群聊多人覆盖)
|
|
966
983
|
const absoluteProjectPath = path.isAbsolute(session.projectPath)
|
|
967
984
|
? session.projectPath
|
|
@@ -982,6 +999,8 @@ export class MessageProcessor {
|
|
|
982
999
|
let completeResult = { isError: false, lastReplyText: '', fullText: '', hasReceivedText: false };
|
|
983
1000
|
// 追踪最后一轮 assistant 回复文本(tool_use 之后的纯文本)
|
|
984
1001
|
let lastReplyText = '';
|
|
1002
|
+
// callId → description 映射,用于 tool_result 回显描述
|
|
1003
|
+
const toolDescByCallId = new Map();
|
|
985
1004
|
try {
|
|
986
1005
|
for await (const event of stream) {
|
|
987
1006
|
// 每收到事件重置空闲超时
|
|
@@ -1049,7 +1068,7 @@ export class MessageProcessor {
|
|
|
1049
1068
|
lastReplyText += event.text;
|
|
1050
1069
|
this.eventBus.publish({ type: 'message:text', sessionId: session.id, text: event.text, isFinal: false });
|
|
1051
1070
|
if (!shouldSuppress()) {
|
|
1052
|
-
renderer.addText(event.text);
|
|
1071
|
+
renderer.addText(event.text, event.outputTokens, event.turn);
|
|
1053
1072
|
}
|
|
1054
1073
|
}
|
|
1055
1074
|
// compact 完成
|
|
@@ -1073,7 +1092,11 @@ export class MessageProcessor {
|
|
|
1073
1092
|
}
|
|
1074
1093
|
// 工具调用
|
|
1075
1094
|
if (event.type === 'tool_use') {
|
|
1076
|
-
//
|
|
1095
|
+
// 工具调用意味着当前 turn 结束,flush 已累积的文本作为独立消息
|
|
1096
|
+
if (renderer.hasTextPending()) {
|
|
1097
|
+
await renderer.flushText();
|
|
1098
|
+
}
|
|
1099
|
+
// 重置最后回复追踪
|
|
1077
1100
|
lastReplyText = '';
|
|
1078
1101
|
this.eventBus.publish({
|
|
1079
1102
|
type: 'tool:use',
|
|
@@ -1084,7 +1107,10 @@ export class MessageProcessor {
|
|
|
1084
1107
|
});
|
|
1085
1108
|
if (!shouldSuppress()) {
|
|
1086
1109
|
const desc = summarizeToolInput(event.name, event.input || {});
|
|
1087
|
-
|
|
1110
|
+
if (event.callId) {
|
|
1111
|
+
toolDescByCallId.set(event.callId, desc);
|
|
1112
|
+
}
|
|
1113
|
+
renderer.addToolCall(event.name, event.input, event.callId, desc, event.turn, event.outputTokens);
|
|
1088
1114
|
}
|
|
1089
1115
|
}
|
|
1090
1116
|
// 工具结果
|
|
@@ -1098,47 +1124,80 @@ export class MessageProcessor {
|
|
|
1098
1124
|
agentName: agentNameForStats,
|
|
1099
1125
|
timestamp: Date.now()
|
|
1100
1126
|
});
|
|
1127
|
+
// 从 tool_use 阶段缓存的描述中回溯
|
|
1128
|
+
const cachedDesc = event.callId ? toolDescByCallId.get(event.callId) : undefined;
|
|
1101
1129
|
if (event.isError && !shouldSuppress()) {
|
|
1102
1130
|
hasErrorResult = true;
|
|
1103
1131
|
let errorMsg = event.error || (typeof event.result === 'string' ? event.result : JSON.stringify(event.result)) || '\u6267\u884c\u5931\u8d25';
|
|
1104
1132
|
// 移除 XML 风格的错误标签
|
|
1105
1133
|
errorMsg = errorMsg.replace(/<tool_use_error>(.*?)<\/tool_use_error>/gs, '$1');
|
|
1106
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId);
|
|
1134
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', false, undefined, errorMsg, event.callId, undefined, cachedDesc);
|
|
1107
1135
|
}
|
|
1108
1136
|
else if (!event.isError && !shouldSuppress()) {
|
|
1109
|
-
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId);
|
|
1137
|
+
renderer.addToolResult(event.name || '\u5de5\u5177', true, event.result, undefined, event.callId, undefined, cachedDesc);
|
|
1110
1138
|
}
|
|
1111
1139
|
}
|
|
1112
1140
|
// 运行时错误(Codex: turn.failed / item error)
|
|
1113
1141
|
if (event.type === 'error') {
|
|
1114
1142
|
logger.warn(`[MessageProcessor] error event: ${event.errorType}: ${event.error}`);
|
|
1115
|
-
|
|
1143
|
+
// 记录错误文本到 lastReplyText,供后续 isPromptTooLong 检测
|
|
1144
|
+
lastReplyText += event.error || '';
|
|
1145
|
+
// 上下文过长的错误不在此处输出 notice,留给外层 isPromptTooLong 触发 auto-compact
|
|
1146
|
+
const isContextError = /prompt is too long|input is too long|上下文过长/i.test(event.error || '');
|
|
1147
|
+
if (!isContextError && !hasErrorResult && !shouldSuppress()) {
|
|
1116
1148
|
hasErrorResult = true;
|
|
1117
|
-
renderer.addNotice(
|
|
1149
|
+
renderer.addNotice(`${event.error}`, 'warn', 'runtime-error', true);
|
|
1118
1150
|
}
|
|
1119
1151
|
}
|
|
1120
1152
|
// 完成事件
|
|
1121
1153
|
// SDK 可能产生多个 complete 事件(如 subagent 或 auto-compact 二次查询),
|
|
1122
1154
|
// 仅记录状态,最终 flush(true) 在流结束后统一执行
|
|
1123
1155
|
if (event.type === 'complete') {
|
|
1124
|
-
logger.
|
|
1156
|
+
logger.info(`[MessageProcessor] complete event: isError=${event.isError} terminalReason=${event.terminalReason ?? 'none'} subtype=${event.subtype ?? 'none'} hasReceivedText=${hasReceivedText}`);
|
|
1125
1157
|
// 自动回填会话名称
|
|
1126
1158
|
if (event.sessionTitle && session.name === '默认会话') {
|
|
1127
1159
|
await this.sessionManager.renameSession(session.id, event.sessionTitle);
|
|
1128
1160
|
logger.info(`[MessageProcessor] Auto-filled session name: ${event.sessionTitle}`);
|
|
1129
1161
|
}
|
|
1130
1162
|
// 记录完成状态 + 最后一轮回复文本(后续 complete 覆盖前序)
|
|
1131
|
-
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText };
|
|
1163
|
+
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, usage: event.usage };
|
|
1164
|
+
// proactive 模式:每轮 LLM 调用完成后写一条 thought 到 messages.jsonl
|
|
1165
|
+
// 这样 thought 数 = LLM 调用轮数,而不是 chunk 数
|
|
1166
|
+
if (session.sessionMode === 'proactive' && lastReplyText) {
|
|
1167
|
+
try {
|
|
1168
|
+
const chatDir = this.sessionManager.getChatDir(session);
|
|
1169
|
+
const sessionEncrypt = this.sessionManager.getSessionEncrypt(session.id);
|
|
1170
|
+
appendMessageLog(chatDir, buildOutboundEntry({
|
|
1171
|
+
from: session.selfId || 'self',
|
|
1172
|
+
to: session.metadata?.peerId ?? session.channelId,
|
|
1173
|
+
chatType: (session.chatType ?? 'private'),
|
|
1174
|
+
groupId: session.metadata?.groupId ?? null,
|
|
1175
|
+
msgId: `thought-${session.id}-${Date.now()}`,
|
|
1176
|
+
content: lastReplyText,
|
|
1177
|
+
agent: session.agentId || null,
|
|
1178
|
+
model: null,
|
|
1179
|
+
durationMs: null,
|
|
1180
|
+
encrypt: sessionEncrypt ?? undefined,
|
|
1181
|
+
chatmode: 'proactive',
|
|
1182
|
+
msgType: 'thought',
|
|
1183
|
+
}));
|
|
1184
|
+
}
|
|
1185
|
+
catch { }
|
|
1186
|
+
}
|
|
1132
1187
|
// 失败且无前置错误输出:显示 errors 摘要
|
|
1133
1188
|
// 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
|
|
1189
|
+
// 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
|
|
1134
1190
|
const interruptReason = this.interruptedSessions.get(session.id);
|
|
1135
1191
|
const isUserInterrupt = interruptReason === 'new_message' || interruptReason === 'stop' || interruptReason === 'recalled';
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1192
|
+
const isContextTooLong = event.terminalReason === 'prompt_too_long'
|
|
1193
|
+
|| /prompt is too long|input is too long|上下文过长/i.test(event.errors?.join(' ') || '')
|
|
1194
|
+
|| /prompt is too long|input is too long|上下文过长/i.test(lastReplyText);
|
|
1195
|
+
if (event.isError && !hasErrorResult && !shouldSuppress() && !isUserInterrupt && !isContextTooLong) {
|
|
1196
|
+
const errorSummary = event.errors?.join('; ') || '任务执行失败';
|
|
1197
|
+
// 使用 terminalReason 提供更友好的错误提示(不带 emoji,由 formatter 统一加)
|
|
1139
1198
|
const userFriendlyMessage = event.terminalReason
|
|
1140
|
-
? getErrorMessage(null, event.terminalReason)
|
|
1141
|
-
:
|
|
1199
|
+
? getErrorMessage(null, event.terminalReason, false)
|
|
1200
|
+
: errorSummary;
|
|
1142
1201
|
renderer.addNotice(userFriendlyMessage, 'warn', 'task-error', true);
|
|
1143
1202
|
}
|
|
1144
1203
|
// 中间 complete:flush 掉已有 activities(不带 isFinal),让中间结果及时显示
|
|
@@ -1165,7 +1224,7 @@ export class MessageProcessor {
|
|
|
1165
1224
|
logger.info(`[MessageProcessor] Auto-filled session name: ${event.sessionTitle}`);
|
|
1166
1225
|
}
|
|
1167
1226
|
// 记录完成状态
|
|
1168
|
-
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText };
|
|
1227
|
+
completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, usage: event.usage };
|
|
1169
1228
|
if (event.subtype === 'success') {
|
|
1170
1229
|
this.messageCache.addEvent(session.id, {
|
|
1171
1230
|
type: 'completed',
|
|
@@ -1185,6 +1244,7 @@ export class MessageProcessor {
|
|
|
1185
1244
|
finalText: lastReplyText || event.result || undefined,
|
|
1186
1245
|
durationMs: event.durationMs,
|
|
1187
1246
|
agentName: agentNameForStats,
|
|
1247
|
+
numTurns: event.numTurns,
|
|
1188
1248
|
timestamp: Date.now()
|
|
1189
1249
|
});
|
|
1190
1250
|
}
|
|
@@ -1212,9 +1272,16 @@ export class MessageProcessor {
|
|
|
1212
1272
|
catch (error) {
|
|
1213
1273
|
// User interrupt (AbortError) is expected, log at info level
|
|
1214
1274
|
const catchInterruptReason = this.interruptedSessions.get(session.id);
|
|
1215
|
-
const catchIsUserInterrupt = catchInterruptReason === 'new_message' || catchInterruptReason === 'stop';
|
|
1275
|
+
const catchIsUserInterrupt = catchInterruptReason === 'new_message' || catchInterruptReason === 'stop' || catchInterruptReason === 'recalled';
|
|
1216
1276
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
1217
1277
|
logger.info('[MessageProcessor] Stream interrupted (AbortError)');
|
|
1278
|
+
// User-initiated interrupt: skip flush — new task takes over the channel,
|
|
1279
|
+
// flushing here would send a spurious "最终回复" before the new task's output
|
|
1280
|
+
if (catchIsUserInterrupt) {
|
|
1281
|
+
completeResult.isError = false;
|
|
1282
|
+
completeResult.hasReceivedText = hasReceivedText;
|
|
1283
|
+
return completeResult;
|
|
1284
|
+
}
|
|
1218
1285
|
}
|
|
1219
1286
|
else if (catchIsUserInterrupt) {
|
|
1220
1287
|
// SDK telemetry noise after user-initiated interrupt — not a real error
|
|
@@ -1231,7 +1298,7 @@ export class MessageProcessor {
|
|
|
1231
1298
|
logger.error('[MessageProcessor] Stream processing error:', error);
|
|
1232
1299
|
}
|
|
1233
1300
|
if (error instanceof Error && error.message.includes('process exited')) {
|
|
1234
|
-
renderer.addNotice('
|
|
1301
|
+
renderer.addNotice('Claude Code 进程异常退出,请重试', 'warn', 'process-exit', true);
|
|
1235
1302
|
}
|
|
1236
1303
|
// Flush any pending error activities before re-throwing,
|
|
1237
1304
|
// and mark the error so outer catch won't send a duplicate message
|
|
@@ -80,7 +80,8 @@ export class MessageQueue {
|
|
|
80
80
|
}
|
|
81
81
|
const queueKey = this.getQueueKey(sessionKey, projectPath);
|
|
82
82
|
const agentName = options?.agentName || DEFAULT_AGENT_NAME;
|
|
83
|
-
|
|
83
|
+
const isProcessing = this.processing.has(queueKey);
|
|
84
|
+
logger.info(`[Queue] enqueue: key=${queueKey} processing=${isProcessing} queueLen=${this.queues.get(queueKey)?.length ?? 0} agent=${agentName}`);
|
|
84
85
|
return new Promise((resolve, reject) => {
|
|
85
86
|
if (!this.queues.has(queueKey)) {
|
|
86
87
|
this.queues.set(queueKey, []);
|
|
@@ -104,6 +105,12 @@ export class MessageQueue {
|
|
|
104
105
|
else {
|
|
105
106
|
// 群聊:FIFO,不打断
|
|
106
107
|
logger.debug(`[Queue] ${queueKey} is processing, message queued (FIFO)`);
|
|
108
|
+
this.eventBus?.publish({
|
|
109
|
+
type: 'task:queued',
|
|
110
|
+
channel: message.channel,
|
|
111
|
+
channelId: message.channelId,
|
|
112
|
+
replyContext: message.replyContext,
|
|
113
|
+
});
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
else {
|
|
@@ -114,7 +121,7 @@ export class MessageQueue {
|
|
|
114
121
|
}
|
|
115
122
|
async processNext(queueKey) {
|
|
116
123
|
this.processing.add(queueKey);
|
|
117
|
-
logger.
|
|
124
|
+
logger.info(`[Queue] processNext: start key=${queueKey}`);
|
|
118
125
|
while (true) {
|
|
119
126
|
// 等待外部锁释放(/compact, /clear 等快速命令)
|
|
120
127
|
const lock = this.getExternalLock(queueKey);
|
|
@@ -124,7 +131,7 @@ export class MessageQueue {
|
|
|
124
131
|
}
|
|
125
132
|
const queue = this.queues.get(queueKey);
|
|
126
133
|
if (!queue || queue.length === 0) {
|
|
127
|
-
logger.
|
|
134
|
+
logger.info(`[Queue] processNext: queue empty, releasing key=${queueKey}`);
|
|
128
135
|
this.processing.delete(queueKey);
|
|
129
136
|
this.processingAgent.delete(queueKey);
|
|
130
137
|
this.currentSessionKey = undefined;
|