evolclaw 3.0.0 → 3.1.0
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 +7 -9
- 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 +4 -1
- package/dist/channels/aun.js +353 -125
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +118 -5
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +3 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +279 -19
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +37 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +46 -22
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +50 -44
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +7 -2
- package/dist/core/message/message-log.js +2 -0
- package/dist/core/message/message-processor.js +150 -99
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/session/session-manager.js +98 -64
- package/dist/core/trigger/scheduler.js +1 -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 +137 -16
- 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 +10 -2
- 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/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 +25 -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 +43 -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
|
@@ -89,23 +89,36 @@ export class ChannelLoader {
|
|
|
89
89
|
}
|
|
90
90
|
return instances;
|
|
91
91
|
}
|
|
92
|
-
async connectAll(instances,
|
|
92
|
+
async connectAll(instances, { concurrency = 3, intervalMs = 50 } = {}) {
|
|
93
93
|
const connected = [];
|
|
94
94
|
const failed = [];
|
|
95
|
+
const inflight = new Set();
|
|
95
96
|
for (const inst of instances) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
failed.push(e);
|
|
97
|
+
// 等待并发数降到 concurrency 以下
|
|
98
|
+
while (inflight.size >= concurrency) {
|
|
99
|
+
await Promise.race(inflight);
|
|
102
100
|
}
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
const task = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
await inst.connect();
|
|
104
|
+
connected.push(inst.adapter.channelName);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
failed.push({ name: inst.adapter.channelName, error: e });
|
|
108
|
+
logger.warn(`[connectAll] ${inst.adapter.channelName} connect failed: ${e}`);
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
const tracked = task.then(() => { inflight.delete(tracked); });
|
|
112
|
+
inflight.add(tracked);
|
|
113
|
+
// 间隔发起,避免瞬间并发冲击网关
|
|
114
|
+
if (intervalMs > 0) {
|
|
115
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
105
116
|
}
|
|
106
117
|
}
|
|
118
|
+
// 等待所有剩余任务完成
|
|
119
|
+
await Promise.allSettled(inflight);
|
|
107
120
|
if (failed.length > 0) {
|
|
108
|
-
logger.warn(`
|
|
121
|
+
logger.warn(`[connectAll] ${failed.length} channel(s) failed initial connect (will retry in background): ${failed.map(f => f.name).join(', ')}`);
|
|
109
122
|
}
|
|
110
123
|
return connected;
|
|
111
124
|
}
|
|
@@ -10,6 +10,7 @@ import fs from 'fs';
|
|
|
10
10
|
import os from 'os';
|
|
11
11
|
import { parseTriggerSet } from './trigger/parser.js';
|
|
12
12
|
import { calcNextFireAt } from './trigger/scheduler.js';
|
|
13
|
+
import { checkLatestVersion, getLocalVersion, isLinkedInstall, compareVersions } from '../utils/npm-ops.js';
|
|
13
14
|
const allEfforts = ['low', 'medium', 'high', 'max'];
|
|
14
15
|
const nonMaxEfforts = allEfforts.filter(e => e !== 'max');
|
|
15
16
|
function getAvailableEfforts(agent, model) {
|
|
@@ -108,7 +109,7 @@ function formatIdleTime(ms) {
|
|
|
108
109
|
return '刚刚';
|
|
109
110
|
}
|
|
110
111
|
// 支持的命令列表
|
|
111
|
-
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/chatmode', '/dispatch', '/ask', '/resume', '/aid', '/rpc', '/storage', '/trigger'];
|
|
112
|
+
const commands = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/stop', '/clear', '/compact', '/repair', '/safe', '/fork', '/del', '/perm', '/file', '/check', '/rewind', '/activity', '/chatmode', '/dispatch', '/ask', '/resume', '/aid', '/rpc', '/storage', '/trigger', '/upgrade'];
|
|
112
113
|
// 命令别名映射
|
|
113
114
|
const aliases = {
|
|
114
115
|
'/p': '/project',
|
|
@@ -117,7 +118,7 @@ const aliases = {
|
|
|
117
118
|
'/rw': '/rewind'
|
|
118
119
|
};
|
|
119
120
|
// 命令快速路径前缀(所有命令都不进入消息队列)
|
|
120
|
-
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/dispatch', '/ask', '/resume', '/aid', '/rpc', '/storage', '/trigger'];
|
|
121
|
+
const quickCommandPrefixes = ['/new', '/pwd', '/plist', '/project', '/bind', '/help', '/evolhelp', '/status', '/restart', '/model', '/setmodel', '/effort', '/agent', '/slist', '/session', '/rename', '/repair', '/fork', '/stop', '/clear', '/compact', '/safe', '/del', '/perm', '/file', '/check', '/p ', '/s ', '/name', '/rewind', '/rw', '/rw ', '/activity', '/chatmode', '/dispatch', '/ask', '/resume', '/aid', '/rpc', '/storage', '/trigger', '/upgrade'];
|
|
121
122
|
export class CommandHandler {
|
|
122
123
|
sessionManager;
|
|
123
124
|
messageCache;
|
|
@@ -340,7 +341,6 @@ export class CommandHandler {
|
|
|
340
341
|
return renderCommandCardAsText(card);
|
|
341
342
|
if (!adapter?.send)
|
|
342
343
|
return renderCommandCardAsText(card);
|
|
343
|
-
// session 忙碌时降级到文本,避免并发触发带参写操作
|
|
344
344
|
if (this.isSessionBusy(opts.interaction.sessionId))
|
|
345
345
|
return renderCommandCardAsText(card);
|
|
346
346
|
try {
|
|
@@ -526,7 +526,7 @@ export class CommandHandler {
|
|
|
526
526
|
] } },
|
|
527
527
|
{ cmd: '/dispatch', label: '切换分发模式', desc: '控制群聊消息过滤(仅@提及或广播响应)', next: { type: 'select', items: [
|
|
528
528
|
{ value: 'mention', label: '@ 提及', desc: '仅在被 @ 提及时响应' },
|
|
529
|
-
{ value: '
|
|
529
|
+
{ value: 'broadcast', label: '广播', desc: '响应群内所有消息' },
|
|
530
530
|
] } },
|
|
531
531
|
]
|
|
532
532
|
});
|
|
@@ -694,14 +694,14 @@ export class CommandHandler {
|
|
|
694
694
|
return { data: { mode: arg } };
|
|
695
695
|
}
|
|
696
696
|
if (cmdBase === '/dispatch') {
|
|
697
|
-
const currentMode = session.metadata?.dispatchMode
|
|
697
|
+
const currentMode = session.metadata?.dispatchMode;
|
|
698
698
|
if (mode === 'query') {
|
|
699
|
-
return { data: { mode: currentMode } };
|
|
699
|
+
return { data: { mode: currentMode ?? null } };
|
|
700
700
|
}
|
|
701
701
|
// update
|
|
702
702
|
if (!arg)
|
|
703
703
|
return { error: '缺少目标模式' };
|
|
704
|
-
if (arg !== 'mention' && arg !== '
|
|
704
|
+
if (arg !== 'mention' && arg !== 'broadcast')
|
|
705
705
|
return { error: `无效模式: ${arg}` };
|
|
706
706
|
const identity = this.sessionManager.resolveIdentity(channel, userId);
|
|
707
707
|
const chatType = session.chatType || 'private';
|
|
@@ -893,7 +893,7 @@ export class CommandHandler {
|
|
|
893
893
|
'💬 聊天设置:',
|
|
894
894
|
' /activity [all|dm|owner|none] - 查看/控制中间输出显示模式',
|
|
895
895
|
' /chatmode [interactive|proactive] - 查看/切换会话模式(被动响应或主动推进)',
|
|
896
|
-
' /dispatch [mention|
|
|
896
|
+
' /dispatch [mention|broadcast] - 查看/切换群聊分发模式(仅@响应或广播响应,仅群聊)',
|
|
897
897
|
'',
|
|
898
898
|
'🔐 权限管理:',
|
|
899
899
|
' /perm - 查看当前权限模式',
|
|
@@ -970,7 +970,7 @@ export class CommandHandler {
|
|
|
970
970
|
// 聊天设置
|
|
971
971
|
if (isAdmin) {
|
|
972
972
|
cmds.push({ command: '/chatmode', args: '[interactive|proactive]', description: '查看/切换会话模式(被动响应或主动推进)', category: '聊天设置', roles: ['admin', 'owner'] });
|
|
973
|
-
cmds.push({ command: '/dispatch', args: '[mention|
|
|
973
|
+
cmds.push({ command: '/dispatch', args: '[mention|broadcast]', description: '查看/切换群聊分发模式(仅@响应或广播响应)', category: '聊天设置', roles: ['admin', 'owner'] });
|
|
974
974
|
}
|
|
975
975
|
// 交互
|
|
976
976
|
cmds.push({ command: '/ask', args: '<选项>', description: '回答 Agent 的交互式问题', category: '运维', roles: ['guest', 'admin', 'owner'] });
|
|
@@ -1750,7 +1750,7 @@ export class CommandHandler {
|
|
|
1750
1750
|
this.eventBus.publish({ type: 'session:chat-mode-changed', sessionId: chatmodeSession.id, mode: arg, timestamp: Date.now() });
|
|
1751
1751
|
return { kind: 'command.result', text: `✅ 会话模式已切换: ${arg}` };
|
|
1752
1752
|
}
|
|
1753
|
-
// /dispatch 命令:查看/切换群聊分发模式(mention |
|
|
1753
|
+
// /dispatch 命令:查看/切换群聊分发模式(mention | broadcast)
|
|
1754
1754
|
// 仅群聊可用;群聊中设置需管理员权限
|
|
1755
1755
|
if (normalizedContent === '/dispatch' || normalizedContent.startsWith('/dispatch ')) {
|
|
1756
1756
|
const dispatchResult = await this.ensureSession(channel, channelId, threadId, chatType);
|
|
@@ -1762,13 +1762,14 @@ export class CommandHandler {
|
|
|
1762
1762
|
return { kind: 'command.error', text: '❌ /dispatch 仅在群聊中可用' };
|
|
1763
1763
|
}
|
|
1764
1764
|
const arg = normalizedContent.slice(9).trim();
|
|
1765
|
-
const currentMode = dispatchSession.metadata?.dispatchMode
|
|
1765
|
+
const currentMode = dispatchSession.metadata?.dispatchMode;
|
|
1766
1766
|
if (!arg) {
|
|
1767
|
+
const displayMode = currentMode ?? '未设置(跟随群设置)';
|
|
1767
1768
|
// 尝试发送 CommandCard 卡片
|
|
1768
1769
|
if (isAdmin) {
|
|
1769
1770
|
const modes = [
|
|
1770
1771
|
{ key: 'mention', name: '提及模式', desc: '仅当被 @ 提及(含 @all)时响应群消息' },
|
|
1771
|
-
{ key: '
|
|
1772
|
+
{ key: 'broadcast', name: '广播模式', desc: '群内所有消息都触发响应' },
|
|
1772
1773
|
];
|
|
1773
1774
|
const interaction = {
|
|
1774
1775
|
type: 'interaction',
|
|
@@ -1796,19 +1797,19 @@ export class CommandHandler {
|
|
|
1796
1797
|
}
|
|
1797
1798
|
// 降级:文本
|
|
1798
1799
|
const lines = [];
|
|
1799
|
-
lines.push(`📋 分发模式: ${
|
|
1800
|
+
lines.push(`📋 分发模式: ${displayMode}`);
|
|
1800
1801
|
lines.push('');
|
|
1801
1802
|
lines.push('模式说明:');
|
|
1802
|
-
lines.push(' • mention
|
|
1803
|
-
lines.push(' •
|
|
1803
|
+
lines.push(' • mention — 提及模式:仅当被@提及时响应群消息(含@all)');
|
|
1804
|
+
lines.push(' • broadcast — 广播模式:群内所有消息都触发响应');
|
|
1804
1805
|
if (isAdmin) {
|
|
1805
1806
|
lines.push('');
|
|
1806
|
-
lines.push('用法: /dispatch <mention|
|
|
1807
|
+
lines.push('用法: /dispatch <mention|broadcast>');
|
|
1807
1808
|
}
|
|
1808
1809
|
return { kind: 'command.result', text: lines.join('\n') };
|
|
1809
1810
|
}
|
|
1810
|
-
if (arg !== 'mention' && arg !== '
|
|
1811
|
-
return { kind: 'command.error', text: `❌ 无效模式: ${arg}\n可选: mention /
|
|
1811
|
+
if (arg !== 'mention' && arg !== 'broadcast') {
|
|
1812
|
+
return { kind: 'command.error', text: `❌ 无效模式: ${arg}\n可选: mention / broadcast\n用法: /dispatch <模式>` };
|
|
1812
1813
|
}
|
|
1813
1814
|
if (!isAdmin) {
|
|
1814
1815
|
return { kind: 'command.error', text: '❌ 无权限:群聊中切换分发模式仅限管理员使用' };
|
|
@@ -1819,7 +1820,7 @@ export class CommandHandler {
|
|
|
1819
1820
|
const metadata = { ...(dispatchSession.metadata || {}), dispatchMode: arg };
|
|
1820
1821
|
await this.sessionManager.updateSession(dispatchSession.id, { metadata });
|
|
1821
1822
|
this.eventBus.publish({ type: 'session:dispatch-mode-changed', sessionId: dispatchSession.id, mode: arg, timestamp: Date.now() });
|
|
1822
|
-
return { kind: 'command.result', text: `✅ 分发模式已切换: ${currentMode} → ${arg}` };
|
|
1823
|
+
return { kind: 'command.result', text: `✅ 分发模式已切换: ${currentMode ?? '未设置'} → ${arg}` };
|
|
1823
1824
|
}
|
|
1824
1825
|
// /stop 命令:中断当前任务
|
|
1825
1826
|
if (normalizedContent === '/stop') {
|
|
@@ -1970,7 +1971,7 @@ export class CommandHandler {
|
|
|
1970
1971
|
}
|
|
1971
1972
|
const lines = [];
|
|
1972
1973
|
const sessionMode = session.sessionMode || 'interactive';
|
|
1973
|
-
const dispatchMode = session.metadata?.dispatchMode
|
|
1974
|
+
const dispatchMode = session.metadata?.dispatchMode ?? '未设置(跟随群设置)';
|
|
1974
1975
|
const chatModeLine = `会话模式: ${sessionMode}`;
|
|
1975
1976
|
const dispatchModeLine = session.chatType === 'group' ? `分发模式: ${dispatchMode}` : null;
|
|
1976
1977
|
if (isAdmin) {
|
|
@@ -2218,6 +2219,23 @@ export class CommandHandler {
|
|
|
2218
2219
|
await executeRestart();
|
|
2219
2220
|
return { kind: 'command.result', text: '🔄 服务正在重启,请稍候...(约 5 秒后恢复)' };
|
|
2220
2221
|
}
|
|
2222
|
+
// /upgrade 命令:检查版本更新,提示用户手动重启
|
|
2223
|
+
if (normalizedContent === '/upgrade') {
|
|
2224
|
+
if (!isAdmin)
|
|
2225
|
+
return { kind: 'command.error', text: '❌ 无权限:升级检查仅限管理员使用' };
|
|
2226
|
+
if (isLinkedInstall()) {
|
|
2227
|
+
return { kind: 'command.result', text: '⏭ 开发模式,跳过升级检查' };
|
|
2228
|
+
}
|
|
2229
|
+
const localVer = getLocalVersion();
|
|
2230
|
+
const remoteVer = await checkLatestVersion();
|
|
2231
|
+
if (!remoteVer) {
|
|
2232
|
+
return { kind: 'command.result', text: `⚠️ 无法连接 npm registry(当前版本 ${localVer})` };
|
|
2233
|
+
}
|
|
2234
|
+
if (compareVersions(localVer, remoteVer) >= 0) {
|
|
2235
|
+
return { kind: 'command.result', text: `✓ 已是最新版本 (${localVer})` };
|
|
2236
|
+
}
|
|
2237
|
+
return { kind: 'command.result', text: `📦 发现新版本 ${localVer} → ${remoteVer}\n执行 /restart 升级` };
|
|
2238
|
+
}
|
|
2221
2239
|
// /pwd 命令:显示当前项目路径
|
|
2222
2240
|
if (normalizedContent === '/pwd') {
|
|
2223
2241
|
// session 现在总是存在(上面已自动创建)
|
|
@@ -3256,7 +3274,8 @@ export class CommandHandler {
|
|
|
3256
3274
|
'/help', '/status', '/check', '/pwd',
|
|
3257
3275
|
'/model', '/effort', '/perm', '/agent',
|
|
3258
3276
|
'/compact', '/file', '/send', '/restart', '/bind', '/aid', '/rpc', '/storage',
|
|
3259
|
-
'/rename', '/name', '/evolagent',
|
|
3277
|
+
'/rename', '/name', '/evolagent', '/trigger',
|
|
3278
|
+
'/chatmode', '/dispatch', '/activity',
|
|
3260
3279
|
];
|
|
3261
3280
|
/** ctl 中仅允许查询形态的指令;写形态(带参)一律拒绝 */
|
|
3262
3281
|
static CTL_READONLY = new Set(['/agent']);
|
|
@@ -3278,6 +3297,8 @@ export class CommandHandler {
|
|
|
3278
3297
|
const taskId = this.sessionManager.getActiveTaskId(session.id);
|
|
3279
3298
|
const chatmode = session.sessionMode || 'interactive';
|
|
3280
3299
|
const encrypted = this.sessionManager.getSessionEncrypt(session.id);
|
|
3300
|
+
// 诊断日志:记录 task_id 解析结果
|
|
3301
|
+
logger.info(`[CommandHandler] buildCtlReplyContext: sessionId=${session.id} taskId=${taskId ?? 'none'} chatmode=${chatmode} threadId=${ctx.threadId ?? 'none'}`);
|
|
3281
3302
|
if (taskId || chatmode !== 'interactive' || encrypted != null) {
|
|
3282
3303
|
ctx.metadata = {};
|
|
3283
3304
|
if (taskId)
|
|
@@ -3294,6 +3315,7 @@ export class CommandHandler {
|
|
|
3294
3315
|
* 复用现有 slash cmd 逻辑,权限继承 session 用户角色
|
|
3295
3316
|
*/
|
|
3296
3317
|
async handleCtl(cmd, sessionId) {
|
|
3318
|
+
logger.info(`[ctl] cmd="${cmd}" sessionId=${sessionId}`);
|
|
3297
3319
|
// 1. 白名单检查
|
|
3298
3320
|
const inputCmd = cmd.split(' ')[0];
|
|
3299
3321
|
if (!CommandHandler.CTL_COMMANDS.includes(inputCmd)) {
|
|
@@ -3362,7 +3384,9 @@ export class CommandHandler {
|
|
|
3362
3384
|
return { ok: false, error: `adapter 未找到: ${session.channel}` };
|
|
3363
3385
|
try {
|
|
3364
3386
|
const replyContext = this.buildCtlReplyContext(session);
|
|
3365
|
-
|
|
3387
|
+
const taskId = replyContext?.metadata?.taskId;
|
|
3388
|
+
const chatmode = replyContext?.metadata?.chatmode ?? 'interactive';
|
|
3389
|
+
await adapter.send(buildEnvelope({ taskId, channel: adapter.channelName, channelId: session.channelId, chatmode, replyContext }), { kind: 'result.text', text, isFinal: true });
|
|
3366
3390
|
return { ok: true, result: '已发送' };
|
|
3367
3391
|
}
|
|
3368
3392
|
catch (err) {
|
package/dist/core/evolagent.js
CHANGED
|
@@ -150,17 +150,12 @@ export class EvolAgent {
|
|
|
150
150
|
this.persist();
|
|
151
151
|
}
|
|
152
152
|
// ── ShowActivities ────────────────────────────────────────────────────
|
|
153
|
-
getShowActivities(
|
|
154
|
-
|
|
155
|
-
return inst?.showActivities ?? this.merged.show_activities ?? 'all';
|
|
153
|
+
getShowActivities(_channelKey) {
|
|
154
|
+
return this.merged.show_activities ?? 'all';
|
|
156
155
|
}
|
|
157
|
-
setShowActivities(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
logger.warn(`[EvolAgent ${this.aid}] setShowActivities: channel "${channelKey}" not found`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
inst.showActivities = mode;
|
|
156
|
+
setShowActivities(_channelKey, mode) {
|
|
157
|
+
this.rawAgent.show_activities = mode;
|
|
158
|
+
this.merged.show_activities = mode;
|
|
164
159
|
this.persist();
|
|
165
160
|
}
|
|
166
161
|
// ── Baseagent 字段写入 ────────────────────────────────────────────────
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
|
+
import { summarizeToolInput } from '../permission.js';
|
|
2
3
|
import fs from 'fs';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import { resolvePaths } from '../../paths.js';
|
|
@@ -29,8 +30,8 @@ export class IMRenderer {
|
|
|
29
30
|
diagEnabled;
|
|
30
31
|
/** 串行发送队列:保证消息按序到达 */
|
|
31
32
|
sendChain = Promise.resolve();
|
|
32
|
-
/** proactive:是否已发过
|
|
33
|
-
|
|
33
|
+
/** proactive:是否已发过 text 文本(用于去重 complete.result) */
|
|
34
|
+
hasEmittedText = false;
|
|
34
35
|
/** 自增 callId 兜底(runner 没提供时用) */
|
|
35
36
|
syntheticCallSeq = 0;
|
|
36
37
|
constructor(opts) {
|
|
@@ -75,7 +76,34 @@ export class IMRenderer {
|
|
|
75
76
|
}
|
|
76
77
|
/** 是否有 pending 内容 */
|
|
77
78
|
hasContent() {
|
|
78
|
-
return this.textBuffer.length > 0 || this.itemsQueue.some(it => it.kind !== '
|
|
79
|
+
return this.textBuffer.length > 0 || this.itemsQueue.some(it => it.kind !== 'text');
|
|
80
|
+
}
|
|
81
|
+
/** 是否有待发送的文本 */
|
|
82
|
+
hasTextPending() {
|
|
83
|
+
return this.textBuffer.length > 0;
|
|
84
|
+
}
|
|
85
|
+
/** flush 当前 textBuffer 作为独立的 result.text(非 final),然后清空 buffer */
|
|
86
|
+
async flushText() {
|
|
87
|
+
if (this.opts.envelope.chatmode === 'proactive')
|
|
88
|
+
return;
|
|
89
|
+
if (this.textBuffer.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
if (this.timer) {
|
|
92
|
+
clearTimeout(this.timer);
|
|
93
|
+
this.timer = undefined;
|
|
94
|
+
}
|
|
95
|
+
const text = this.textBuffer;
|
|
96
|
+
this.textBuffer = '';
|
|
97
|
+
// 清掉 itemsQueue 中的 text items(已发出)
|
|
98
|
+
this.itemsQueue = this.itemsQueue.filter(it => it.kind !== 'text');
|
|
99
|
+
const payload = { kind: 'result.text', text, isFinal: false };
|
|
100
|
+
this.sentContent = true;
|
|
101
|
+
this.sendChain = this.sendChain
|
|
102
|
+
.then(() => this.opts.send(payload))
|
|
103
|
+
.catch(e => logger.warn('[IMRenderer] flushText send failed:', e));
|
|
104
|
+
await this.sendChain;
|
|
105
|
+
this.lastFlush = Date.now();
|
|
106
|
+
this.flushCount++;
|
|
79
107
|
}
|
|
80
108
|
/** 是否已发送过内容(用于决定最终 flush 是否带 isFinal 标题) */
|
|
81
109
|
hasSentContent() {
|
|
@@ -92,9 +120,9 @@ export class IMRenderer {
|
|
|
92
120
|
/** 从 buffer 中移除指定 pattern(用于文件标记预处理) */
|
|
93
121
|
stripFromBuffer(pattern) {
|
|
94
122
|
this.textBuffer = this.textBuffer.replace(pattern, '').trim();
|
|
95
|
-
// itemsQueue 中的
|
|
123
|
+
// itemsQueue 中的 text items 也同步过滤
|
|
96
124
|
for (const item of this.itemsQueue) {
|
|
97
|
-
if (item.kind === '
|
|
125
|
+
if (item.kind === 'text') {
|
|
98
126
|
item.text = item.text.replace(pattern, '');
|
|
99
127
|
}
|
|
100
128
|
}
|
|
@@ -106,13 +134,13 @@ export class IMRenderer {
|
|
|
106
134
|
return;
|
|
107
135
|
if (!text)
|
|
108
136
|
return;
|
|
109
|
-
// 同一窗口内连续 text delta 合并到最后一个
|
|
137
|
+
// 同一窗口内连续 text delta 合并到最后一个 text item
|
|
110
138
|
const last = this.itemsQueue[this.itemsQueue.length - 1];
|
|
111
|
-
if (last && last.kind === '
|
|
139
|
+
if (last && last.kind === 'text') {
|
|
112
140
|
last.text += text;
|
|
113
141
|
}
|
|
114
142
|
else {
|
|
115
|
-
this.itemsQueue.push({ kind: '
|
|
143
|
+
this.itemsQueue.push({ kind: 'text', text });
|
|
116
144
|
}
|
|
117
145
|
this.textBuffer += text;
|
|
118
146
|
this.allText += text;
|
|
@@ -241,17 +269,17 @@ export class IMRenderer {
|
|
|
241
269
|
const maxDelay = interval * 2.5;
|
|
242
270
|
return Math.max(minDelay, Math.min(maxDelay, dynamicDelay));
|
|
243
271
|
}
|
|
244
|
-
/** 仅 flush 非
|
|
272
|
+
/** 仅 flush 非 text items(text items 和 textBuffer 保留,等待下次完整 flush) */
|
|
245
273
|
async flushActivitiesInternal() {
|
|
246
|
-
const nonThinking = this.itemsQueue.filter(it => it.kind !== '
|
|
274
|
+
const nonThinking = this.itemsQueue.filter(it => it.kind !== 'text');
|
|
247
275
|
if (nonThinking.length === 0)
|
|
248
276
|
return;
|
|
249
277
|
if (this.timer) {
|
|
250
278
|
clearTimeout(this.timer);
|
|
251
279
|
this.timer = undefined;
|
|
252
280
|
}
|
|
253
|
-
// 移除已 flush 的 non-
|
|
254
|
-
this.itemsQueue = this.itemsQueue.filter(it => it.kind === '
|
|
281
|
+
// 移除已 flush 的 non-text items,保留 text items
|
|
282
|
+
this.itemsQueue = this.itemsQueue.filter(it => it.kind === 'text');
|
|
255
283
|
const payload = { kind: 'activity.batch', items: nonThinking };
|
|
256
284
|
if (this.diagEnabled)
|
|
257
285
|
diag(this.instanceId, 'flushActivitiesOnly', { itemCount: nonThinking.length });
|
|
@@ -276,13 +304,13 @@ export class IMRenderer {
|
|
|
276
304
|
if (this.opts.fileMarkerPattern) {
|
|
277
305
|
this.textBuffer = this.textBuffer.replace(this.opts.fileMarkerPattern, '').trim();
|
|
278
306
|
for (const item of this.itemsQueue) {
|
|
279
|
-
if (item.kind === '
|
|
307
|
+
if (item.kind === 'text')
|
|
280
308
|
item.text = item.text.replace(this.opts.fileMarkerPattern, '');
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
|
-
// 清掉空
|
|
311
|
+
// 清掉空 text items
|
|
284
312
|
const items = this.itemsQueue.filter(it => {
|
|
285
|
-
if (it.kind === '
|
|
313
|
+
if (it.kind === 'text')
|
|
286
314
|
return it.text.length > 0;
|
|
287
315
|
return true;
|
|
288
316
|
});
|
|
@@ -290,7 +318,6 @@ export class IMRenderer {
|
|
|
290
318
|
const finalText = isFinal ? this.textBuffer : '';
|
|
291
319
|
if (isFinal)
|
|
292
320
|
this.textBuffer = '';
|
|
293
|
-
// 非 final flush 保留 textBuffer,供最终 result.text 使用
|
|
294
321
|
if (this.diagEnabled) {
|
|
295
322
|
diag(this.instanceId, 'flush', {
|
|
296
323
|
isFinal,
|
|
@@ -300,12 +327,8 @@ export class IMRenderer {
|
|
|
300
327
|
sinceLastFlush: Date.now() - this.lastFlush,
|
|
301
328
|
});
|
|
302
329
|
}
|
|
303
|
-
// 1. interactive
|
|
304
|
-
|
|
305
|
-
let itemsForBatch = items;
|
|
306
|
-
if (isFinal) {
|
|
307
|
-
itemsForBatch = items.filter(it => it.kind !== 'thinking');
|
|
308
|
-
}
|
|
330
|
+
// 1. interactive 模式下:不发 text items(由 result.text 统一发送最终文本)
|
|
331
|
+
let itemsForBatch = items.filter(it => it.kind !== 'text');
|
|
309
332
|
if (itemsForBatch.length > 0) {
|
|
310
333
|
const payload = { kind: 'activity.batch', items: itemsForBatch };
|
|
311
334
|
this.sentContent = true;
|
|
@@ -334,14 +357,14 @@ export class IMRenderer {
|
|
|
334
357
|
if (event.type === 'complete' &&
|
|
335
358
|
!event.isError &&
|
|
336
359
|
event.result &&
|
|
337
|
-
this.
|
|
360
|
+
this.hasEmittedText) {
|
|
338
361
|
return;
|
|
339
362
|
}
|
|
340
363
|
const item = this.mapEventToItem(event);
|
|
341
364
|
if (!item)
|
|
342
365
|
return;
|
|
343
|
-
if (item.kind === '
|
|
344
|
-
this.
|
|
366
|
+
if (item.kind === 'text') {
|
|
367
|
+
this.hasEmittedText = true;
|
|
345
368
|
this.allText += item.text;
|
|
346
369
|
}
|
|
347
370
|
const payload = { kind: 'activity.batch', items: [item] };
|
|
@@ -355,9 +378,9 @@ export class IMRenderer {
|
|
|
355
378
|
case 'text':
|
|
356
379
|
if (!event.text)
|
|
357
380
|
return null;
|
|
358
|
-
return { kind: '
|
|
381
|
+
return { kind: 'text', text: event.text };
|
|
359
382
|
case 'tool_use': {
|
|
360
|
-
const desc =
|
|
383
|
+
const desc = summarizeToolInput(event.name, event.input || {});
|
|
361
384
|
return {
|
|
362
385
|
kind: 'tool_call',
|
|
363
386
|
call_id: event.callId || this.synthCallId(),
|
|
@@ -437,23 +460,6 @@ export class IMRenderer {
|
|
|
437
460
|
return null;
|
|
438
461
|
}
|
|
439
462
|
}
|
|
440
|
-
summarizeInput(input, toolName) {
|
|
441
|
-
if (!input || typeof input !== 'object')
|
|
442
|
-
return '';
|
|
443
|
-
if (toolName === 'Bash' && typeof input.command === 'string') {
|
|
444
|
-
const cmd = input.command;
|
|
445
|
-
if (cmd.includes('evolclaw ctl send') || cmd.includes('evolclaw ctl file')) {
|
|
446
|
-
return cmd;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return (input.description ||
|
|
450
|
-
input.file_path ||
|
|
451
|
-
input.pattern ||
|
|
452
|
-
(typeof input.command === 'string' ? input.command.substring(0, 80) : '') ||
|
|
453
|
-
(typeof input.prompt === 'string' ? input.prompt.substring(0, 80) : '') ||
|
|
454
|
-
(typeof input.query === 'string' ? input.query.substring(0, 80) : '') ||
|
|
455
|
-
'');
|
|
456
|
-
}
|
|
457
463
|
stringifyResult(result) {
|
|
458
464
|
if (result === null || result === undefined)
|
|
459
465
|
return '';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export function formatItemsAsText(items) {
|
|
7
7
|
if (!items || items.length === 0)
|
|
8
|
-
return '';
|
|
8
|
+
return ''; // early exit
|
|
9
9
|
const lines = [];
|
|
10
10
|
for (const item of items) {
|
|
11
11
|
const line = formatItem(item);
|
|
@@ -16,20 +16,27 @@ export function formatItemsAsText(items) {
|
|
|
16
16
|
}
|
|
17
17
|
function formatItem(item) {
|
|
18
18
|
switch (item.kind) {
|
|
19
|
-
case '
|
|
19
|
+
case 'text':
|
|
20
20
|
return item.text;
|
|
21
21
|
case 'reasoning':
|
|
22
22
|
return `💭 ${item.text}`;
|
|
23
23
|
case 'tool_call': {
|
|
24
24
|
const desc = item.text || summarizeArgs(item.arguments);
|
|
25
|
-
|
|
25
|
+
if (!desc)
|
|
26
|
+
return `🔧 ${item.name}`;
|
|
27
|
+
// 多行 desc(如 Edit diff):第一行跟工具名同行,代码块从新行开始
|
|
28
|
+
if (desc.includes('\n')) {
|
|
29
|
+
const nlIdx = desc.indexOf('\n');
|
|
30
|
+
return `🔧 ${item.name} ${desc.slice(0, nlIdx)}\n${desc.slice(nlIdx + 1)}`;
|
|
31
|
+
}
|
|
32
|
+
return `🔧 ${item.name}: ${desc}`;
|
|
26
33
|
}
|
|
27
34
|
case 'tool_result': {
|
|
28
35
|
if (!item.ok) {
|
|
29
36
|
const errMsg = item.error || (typeof item.result === 'string' ? item.result : '执行失败');
|
|
30
37
|
return `⚠️ ${item.name}: ${errMsg}`;
|
|
31
38
|
}
|
|
32
|
-
return item.text ?
|
|
39
|
+
return item.text ? `✓ ${item.name}: ${item.text}` : `✓ ${item.name}`;
|
|
33
40
|
}
|
|
34
41
|
case 'progress':
|
|
35
42
|
return `⏳ ${item.text}`;
|
|
@@ -63,6 +63,8 @@ export class MessageBridge {
|
|
|
63
63
|
onMessage(async (msg) => {
|
|
64
64
|
try {
|
|
65
65
|
let content = msg.content.trim();
|
|
66
|
+
// 渠道入站日志
|
|
67
|
+
logger.channelIn({ channel: channelName, channelId: msg.channelId, peerId: msg.peerId, peerName: msg.peerName, chatType: msg.chatType, msgId: msg.messageId, threadId: msg.threadId, content, images: msg.images?.length ?? 0, mentions: msg.mentions, replyContext: msg.replyContext });
|
|
66
68
|
// 0. 自定义消息快速路径(menu.query 等)
|
|
67
69
|
if (await this.handleCustomPayload(content, channelName, msg, sendReply, adapter))
|
|
68
70
|
return;
|
|
@@ -75,7 +77,10 @@ export class MessageBridge {
|
|
|
75
77
|
if (this.cmdHandler.isCommand(cmdContent)) {
|
|
76
78
|
logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
|
|
77
79
|
}
|
|
78
|
-
if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) =>
|
|
80
|
+
if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
|
|
81
|
+
logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId: `cmd-${msg.messageId || Date.now()}`, payload: { kind: 'command.result', text } });
|
|
82
|
+
return sendReply(msg.channelId, text, msg.replyContext);
|
|
83
|
+
}, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext))
|
|
79
84
|
return;
|
|
80
85
|
// 3. session 解析(使用 Channel 层填充的 chatType)
|
|
81
86
|
const chatType = msg.chatType || 'private';
|
|
@@ -104,7 +109,7 @@ export class MessageBridge {
|
|
|
104
109
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
|
|
105
110
|
const effectiveProjectPath = owningAgent?.projectPath
|
|
106
111
|
?? this.defaultProjectPath;
|
|
107
|
-
const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType);
|
|
112
|
+
const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType, msg.peerType);
|
|
108
113
|
// 4. 消息前缀(由 policy 决定)
|
|
109
114
|
const channelInfo = this.processor.getChannelInfo?.(channelName);
|
|
110
115
|
if (channelInfo?.policy) {
|