evolclaw 2.4.0 → 2.5.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 +33 -14
- package/dist/agents/claude-runner.js +269 -23
- package/dist/agents/codex-runner.js +2 -8
- package/dist/agents/gemini-runner.js +1 -8
- package/dist/channels/aun.js +525 -53
- package/dist/channels/dingtalk.js +506 -0
- package/dist/channels/feishu.js +31 -231
- package/dist/channels/qqbot.js +391 -0
- package/dist/channels/wechat.js +36 -38
- package/dist/channels/wecom.js +549 -0
- package/dist/cli.js +86 -10
- package/dist/config.js +98 -2
- package/dist/core/command-handler.js +554 -130
- package/dist/core/message/message-bridge.js +26 -9
- package/dist/core/message/message-processor.js +152 -57
- package/dist/core/message/message-queue.js +48 -0
- package/dist/core/message/stream-flusher.js +2 -2
- package/dist/core/permission.js +7 -11
- package/dist/core/session/session-manager.js +21 -3
- package/dist/index.js +48 -13
- package/dist/ipc.js +14 -4
- package/dist/templates/skills.md +64 -0
- package/dist/utils/error-dict.js +63 -0
- package/dist/utils/error-utils.js +156 -56
- package/dist/utils/format.js +32 -0
- package/dist/utils/init-channel.js +752 -8
- package/dist/utils/init.js +85 -3
- package/dist/utils/media-cache.js +2 -0
- package/dist/utils/stats-collector.js +0 -8
- package/evolclaw-install.md +54 -0
- package/package.json +11 -4
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { migrateProject } from './utils/migrate-project.js';
|
|
|
9
9
|
import readline from 'readline';
|
|
10
10
|
import { cmdInit } from './utils/init.js';
|
|
11
11
|
import { ipcQuery } from './ipc.js';
|
|
12
|
-
import { cmdInitWechat, cmdInitFeishu, cmdInitAun } from './utils/init-channel.js';
|
|
12
|
+
import { cmdInitWechat, cmdInitFeishu, cmdInitAun, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './utils/init-channel.js';
|
|
13
13
|
import * as platform from './utils/cross-platform.js';
|
|
14
14
|
import { EventBus } from './core/event-bus.js';
|
|
15
15
|
// Suppress Node.js ExperimentalWarning (e.g. SQLite) from cluttering CLI output
|
|
@@ -54,8 +54,8 @@ function rotateLogs(logDir) {
|
|
|
54
54
|
console.log(` Rotated: ${file} -> ${path.basename(newPath)}`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
else if (file.includes('.log.')) {
|
|
58
|
-
// 清理 7
|
|
57
|
+
else if (file.includes('.log.') || /^aun-\d{8}\.log$/.test(file)) {
|
|
58
|
+
// 清理 7 天前的旧日志(含按日轮转的 aun-YYYYMMDD.log)
|
|
59
59
|
const stat = fs.statSync(filePath);
|
|
60
60
|
if (stat.mtimeMs < cutoff) {
|
|
61
61
|
fs.unlinkSync(filePath);
|
|
@@ -122,7 +122,9 @@ function countLines(pkgRoot, logDir) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
if (shouldAppend) {
|
|
125
|
-
const
|
|
125
|
+
const _d = new Date();
|
|
126
|
+
const _p = (n) => String(n).padStart(2, '0');
|
|
127
|
+
const now = `${_d.getFullYear()}-${_p(_d.getMonth() + 1)}-${_p(_d.getDate())} ${_p(_d.getHours())}:${_p(_d.getMinutes())}:${_p(_d.getSeconds())}`;
|
|
126
128
|
fs.appendFileSync(statsFile, `${now}\t${core}\t${agents}\t${channels}\t${utils}\t${entry}\t${total}\n`);
|
|
127
129
|
}
|
|
128
130
|
showHistory(statsFile);
|
|
@@ -392,6 +394,9 @@ function showConfigChannels(config) {
|
|
|
392
394
|
{ type: 'feishu', isValid: (inst) => !!inst.appId && inst.enabled !== false },
|
|
393
395
|
{ type: 'wechat', isValid: (inst) => !!inst.token && inst.enabled !== false },
|
|
394
396
|
{ type: 'aun', isValid: (inst) => !!inst.aid && inst.enabled !== false && !inst.aid.includes('your-') && !inst.aid.includes('placeholder') },
|
|
397
|
+
{ type: 'dingtalk', isValid: (inst) => !!inst.clientId && inst.enabled !== false && !inst.clientId.includes('your-') && !inst.clientId.includes('placeholder') },
|
|
398
|
+
{ type: 'qqbot', isValid: (inst) => !!inst.appId && inst.enabled !== false && !inst.appId.includes('your-') && !inst.appId.includes('placeholder') },
|
|
399
|
+
{ type: 'wecom', isValid: (inst) => !!inst.botId && inst.enabled !== false && !inst.botId.includes('your-') && !inst.botId.includes('placeholder') },
|
|
395
400
|
];
|
|
396
401
|
for (const { type, isValid } of channelChecks) {
|
|
397
402
|
const raw = config.channels?.[type];
|
|
@@ -688,7 +693,10 @@ async function cmdRestartMonitor() {
|
|
|
688
693
|
const HEAL_TIMEOUT = 30 * 60 * 1000; // 30 分钟,让 claude 自然结束
|
|
689
694
|
const eventBus = new EventBus();
|
|
690
695
|
const log = (msg) => {
|
|
691
|
-
const
|
|
696
|
+
const _d = new Date();
|
|
697
|
+
const _p = (n) => String(n).padStart(2, '0');
|
|
698
|
+
const ts = `${_d.getFullYear()}-${_p(_d.getMonth() + 1)}-${_p(_d.getDate())} ${_p(_d.getHours())}:${_p(_d.getMinutes())}:${_p(_d.getSeconds())}`;
|
|
699
|
+
const line = `[${ts}] ${msg}\n`;
|
|
692
700
|
fs.appendFileSync(restartLog, line);
|
|
693
701
|
};
|
|
694
702
|
/** 检查服务是否已经在运行(ready signal 存在 + 进程存活) */
|
|
@@ -994,7 +1002,7 @@ function archiveSelfHealLog(p, log) {
|
|
|
994
1002
|
* Searches across all channel types (feishu, wechat, aun) for a matching instance.
|
|
995
1003
|
*/
|
|
996
1004
|
function resolveInstanceConfig(config, instanceName) {
|
|
997
|
-
for (const type of ['feishu', 'wechat', 'aun']) {
|
|
1005
|
+
for (const type of ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot', 'wecom']) {
|
|
998
1006
|
const raw = config.channels?.[type];
|
|
999
1007
|
if (!raw)
|
|
1000
1008
|
continue;
|
|
@@ -1246,7 +1254,7 @@ async function cmdTui() {
|
|
|
1246
1254
|
const pythonCheck = aun.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
1247
1255
|
if (!platform.commandExists(pythonCheck)) {
|
|
1248
1256
|
console.error(`[tui] Python 未找到 (${pythonCheck})`);
|
|
1249
|
-
console.error(' → TUI 依赖 Python 和 aun-core: pip3 install aun-core');
|
|
1257
|
+
console.error(' → TUI 依赖 Python 和 aun-core (>=0.2.9): pip3 install -U aun-core');
|
|
1250
1258
|
process.exit(1);
|
|
1251
1259
|
}
|
|
1252
1260
|
const pythonBin = aun.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
@@ -1254,13 +1262,54 @@ async function cmdTui() {
|
|
|
1254
1262
|
if (!fs.existsSync(cliScript)) {
|
|
1255
1263
|
console.error(`[tui] aun_cli.py 不存在: ${cliScript}`);
|
|
1256
1264
|
console.error(' → TUI 需要 AUN CLI 工具,请确认源码目录包含 aun/aun_cli.py');
|
|
1257
|
-
console.error(' → 安装: pip3 install aun-core && 从源码仓库获取 aun_cli.py');
|
|
1265
|
+
console.error(' → 安装: pip3 install -U aun-core && 从源码仓库获取 aun_cli.py');
|
|
1258
1266
|
process.exit(1);
|
|
1259
1267
|
}
|
|
1260
1268
|
const child = spawn(pythonBin, [cliScript, '-a', aun.owner, '-t', aun.aid], { stdio: 'inherit' });
|
|
1261
1269
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
1262
1270
|
}
|
|
1271
|
+
// ==================== Ctl ====================
|
|
1272
|
+
async function cmdCtl(args) {
|
|
1273
|
+
if (args.length === 0) {
|
|
1274
|
+
console.error('用法: evolclaw ctl <command> [args...]');
|
|
1275
|
+
console.error('示例: evolclaw ctl model sonnet');
|
|
1276
|
+
console.error(' evolclaw ctl status');
|
|
1277
|
+
console.error(' evolclaw ctl effort high');
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
const sessionId = process.env.EVOLCLAW_SESSION_ID;
|
|
1281
|
+
if (!sessionId) {
|
|
1282
|
+
console.error('错误: EVOLCLAW_SESSION_ID 未设置(仅在 evolclaw 托管环境中可用)');
|
|
1283
|
+
process.exit(1);
|
|
1284
|
+
}
|
|
1285
|
+
const cmd = '/' + args.join(' ');
|
|
1286
|
+
const socketPath = resolvePaths().socket;
|
|
1287
|
+
// compact/restart 等长时操作使用更长超时
|
|
1288
|
+
const longRunning = ['/compact', '/restart'];
|
|
1289
|
+
const timeout = longRunning.some(c => cmd.startsWith(c)) ? 60_000 : 10_000;
|
|
1290
|
+
const result = await ipcQuery(socketPath, {
|
|
1291
|
+
type: 'ctl',
|
|
1292
|
+
cmd,
|
|
1293
|
+
sessionId,
|
|
1294
|
+
}, timeout);
|
|
1295
|
+
if (!result) {
|
|
1296
|
+
console.error('错误: 无法连接 evolclaw 服务');
|
|
1297
|
+
process.exit(1);
|
|
1298
|
+
}
|
|
1299
|
+
const ctlResult = result;
|
|
1300
|
+
if (ctlResult.ok) {
|
|
1301
|
+
console.log(ctlResult.result);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
console.error(ctlResult.error || '执行失败');
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1263
1308
|
// ==================== Main ====================
|
|
1309
|
+
function getArgValue(args, flag) {
|
|
1310
|
+
const idx = args.indexOf(flag);
|
|
1311
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
1312
|
+
}
|
|
1264
1313
|
export async function main(args) {
|
|
1265
1314
|
const cmd = args[0] || 'start';
|
|
1266
1315
|
switch (cmd) {
|
|
@@ -1274,8 +1323,29 @@ export async function main(args) {
|
|
|
1274
1323
|
else if (args[1] === 'aun') {
|
|
1275
1324
|
await cmdInitAun();
|
|
1276
1325
|
}
|
|
1326
|
+
else if (args[1] === 'dingtalk') {
|
|
1327
|
+
await cmdInitDingtalk();
|
|
1328
|
+
}
|
|
1329
|
+
else if (args[1] === 'qqbot') {
|
|
1330
|
+
await cmdInitQQBot();
|
|
1331
|
+
}
|
|
1332
|
+
else if (args[1] === 'wecom') {
|
|
1333
|
+
await cmdInitWecom();
|
|
1334
|
+
}
|
|
1277
1335
|
else {
|
|
1278
|
-
|
|
1336
|
+
const nonInteractive = args.includes('--non-interactive');
|
|
1337
|
+
if (nonInteractive) {
|
|
1338
|
+
await cmdInit({
|
|
1339
|
+
nonInteractive: true,
|
|
1340
|
+
defaultPath: getArgValue(args, '--default-path') || process.cwd(),
|
|
1341
|
+
channel: getArgValue(args, '--channel') || 'aun',
|
|
1342
|
+
aunAid: getArgValue(args, '--aun-aid'),
|
|
1343
|
+
aunOwner: getArgValue(args, '--aun-owner'),
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
await cmdInit();
|
|
1348
|
+
}
|
|
1279
1349
|
}
|
|
1280
1350
|
break;
|
|
1281
1351
|
case 'start':
|
|
@@ -1305,13 +1375,19 @@ export async function main(args) {
|
|
|
1305
1375
|
case 'tui':
|
|
1306
1376
|
await cmdTui();
|
|
1307
1377
|
break;
|
|
1378
|
+
case 'ctl':
|
|
1379
|
+
await cmdCtl(args.slice(1));
|
|
1380
|
+
break;
|
|
1308
1381
|
default:
|
|
1309
|
-
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|tui|diagnose|mv}
|
|
1382
|
+
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|tui|ctl|diagnose|mv}
|
|
1310
1383
|
|
|
1311
1384
|
Commands:
|
|
1312
1385
|
init 创建配置文件 (${resolvePaths().config})
|
|
1313
1386
|
init feishu 飞书扫码登录并写入配置
|
|
1314
1387
|
init wechat 微信扫码登录并写入配置
|
|
1388
|
+
init dingtalk 钉钉扫码登录并写入配置
|
|
1389
|
+
init qqbot QQ 机器人扫码绑定并写入配置
|
|
1390
|
+
init wecom 企业微信 AI Bot 配置(手动输入 Bot ID + Secret)
|
|
1315
1391
|
init aun AUN (AgentUnin.Network) 配置
|
|
1316
1392
|
start 启动服务 (默认)
|
|
1317
1393
|
stop 停止服务
|
package/dist/config.js
CHANGED
|
@@ -174,7 +174,7 @@ export function saveConfig(config, configPath = resolvePaths().config) {
|
|
|
174
174
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
175
175
|
}
|
|
176
176
|
// ── Channel instance normalization ──
|
|
177
|
-
export const channelTypes = ['feishu', 'wechat', 'aun'];
|
|
177
|
+
export const channelTypes = ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot'];
|
|
178
178
|
/**
|
|
179
179
|
* Normalize a channel config value (single object, array, or undefined) into an array
|
|
180
180
|
* where every element has a `name` field.
|
|
@@ -186,7 +186,10 @@ export function normalizeChannelInstances(cfg, defaultName) {
|
|
|
186
186
|
if (cfg === undefined || cfg === null)
|
|
187
187
|
return [];
|
|
188
188
|
if (Array.isArray(cfg)) {
|
|
189
|
-
return cfg
|
|
189
|
+
return cfg.map((item, i) => ({
|
|
190
|
+
...item,
|
|
191
|
+
name: item.name ?? (cfg.length === 1 ? defaultName : `${defaultName}-${i + 1}`),
|
|
192
|
+
}));
|
|
190
193
|
}
|
|
191
194
|
return [{ ...cfg, name: cfg.name ?? defaultName }];
|
|
192
195
|
}
|
|
@@ -258,6 +261,50 @@ export function setOwner(config, instanceName, userId, configPath = resolvePaths
|
|
|
258
261
|
return;
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
export function getChannelShowActivities(config, instanceName) {
|
|
265
|
+
for (const type of channelTypes) {
|
|
266
|
+
const raw = config.channels?.[type];
|
|
267
|
+
if (raw === undefined)
|
|
268
|
+
continue;
|
|
269
|
+
if (Array.isArray(raw)) {
|
|
270
|
+
const inst = raw.find((item) => item.name === instanceName);
|
|
271
|
+
if (inst)
|
|
272
|
+
return inst.showActivities ?? config.showActivities ?? 'all';
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
const effectiveName = raw.name ?? type;
|
|
276
|
+
if (effectiveName === instanceName)
|
|
277
|
+
return raw.showActivities ?? config.showActivities ?? 'all';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return config.showActivities ?? 'all';
|
|
281
|
+
}
|
|
282
|
+
export function setChannelShowActivities(config, instanceName, mode) {
|
|
283
|
+
if (!config.channels)
|
|
284
|
+
config.channels = {};
|
|
285
|
+
const channels = config.channels;
|
|
286
|
+
for (const type of channelTypes) {
|
|
287
|
+
const raw = channels[type];
|
|
288
|
+
if (raw === undefined)
|
|
289
|
+
continue;
|
|
290
|
+
if (Array.isArray(raw)) {
|
|
291
|
+
const inst = raw.find((item) => item.name === instanceName);
|
|
292
|
+
if (inst) {
|
|
293
|
+
inst.showActivities = mode;
|
|
294
|
+
saveConfig(config);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const effectiveName = raw.name ?? type;
|
|
300
|
+
if (effectiveName === instanceName) {
|
|
301
|
+
raw.showActivities = mode;
|
|
302
|
+
saveConfig(config);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
261
308
|
export function isOwner(config, channelOrType, userId) {
|
|
262
309
|
// 按实例名精确匹配
|
|
263
310
|
if (getOwner(config, channelOrType) === userId)
|
|
@@ -275,6 +322,31 @@ export function isOwner(config, channelOrType, userId) {
|
|
|
275
322
|
}
|
|
276
323
|
return false;
|
|
277
324
|
}
|
|
325
|
+
export function isAdmin(config, channelOrType, userId) {
|
|
326
|
+
// 按实例名精确匹配
|
|
327
|
+
for (const type of channelTypes) {
|
|
328
|
+
const raw = config.channels?.[type];
|
|
329
|
+
const instances = normalizeChannelInstances(raw, type);
|
|
330
|
+
const found = instances.find((inst) => inst.name === channelOrType);
|
|
331
|
+
if (found) {
|
|
332
|
+
const admins = found.admins || [];
|
|
333
|
+
return admins.includes(userId);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// 按 channelType 匹配:检查该类型下所有实例
|
|
337
|
+
for (const type of channelTypes) {
|
|
338
|
+
if (type !== channelOrType)
|
|
339
|
+
continue;
|
|
340
|
+
const raw = config.channels?.[type];
|
|
341
|
+
const instances = normalizeChannelInstances(raw, type);
|
|
342
|
+
for (const inst of instances) {
|
|
343
|
+
const admins = inst.admins || [];
|
|
344
|
+
if (admins.includes(userId))
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
278
350
|
function validateConfig(config) {
|
|
279
351
|
// anthropic 部分不再强制校验,由 resolveAnthropicConfig() 处理
|
|
280
352
|
// Feishu 配置可选,但如果配置了就要完整(支持 array / object 两种格式)
|
|
@@ -308,6 +380,30 @@ function validateConfig(config) {
|
|
|
308
380
|
logger.warn(`⚠ WeChat${label} enabled but token not configured (WeChat channel will be disabled)`);
|
|
309
381
|
}
|
|
310
382
|
}
|
|
383
|
+
// DingTalk 配置可选,但如果配置了就需要 clientId + clientSecret
|
|
384
|
+
const dingtalkInstances = normalizeChannelInstances(config.channels?.dingtalk, 'dingtalk');
|
|
385
|
+
for (const inst of dingtalkInstances) {
|
|
386
|
+
if (inst.enabled === false)
|
|
387
|
+
continue;
|
|
388
|
+
const label = dingtalkInstances.length > 1 ? ` [${inst.name}]` : '';
|
|
389
|
+
const hasClientId = !!inst.clientId && !inst.clientId.includes('your-');
|
|
390
|
+
const hasClientSecret = !!inst.clientSecret && !inst.clientSecret.includes('your-');
|
|
391
|
+
if (hasClientId !== hasClientSecret) {
|
|
392
|
+
logger.warn(`⚠ DingTalk${label} clientId/clientSecret incomplete (DingTalk channel will be disabled)`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// QQBot 配置可选,但如果配置了就需要 appId + clientSecret
|
|
396
|
+
const qqbotInstances = normalizeChannelInstances(config.channels?.qqbot, 'qqbot');
|
|
397
|
+
for (const inst of qqbotInstances) {
|
|
398
|
+
if (inst.enabled === false)
|
|
399
|
+
continue;
|
|
400
|
+
const label = qqbotInstances.length > 1 ? ` [${inst.name}]` : '';
|
|
401
|
+
const hasAppId = !!inst.appId && !inst.appId.includes('your-');
|
|
402
|
+
const hasSecret = !!inst.clientSecret && !inst.clientSecret.includes('your-');
|
|
403
|
+
if (hasAppId !== hasSecret) {
|
|
404
|
+
logger.warn(`⚠ QQBot${label} appId/clientSecret incomplete (QQBot channel will be disabled)`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
311
407
|
}
|
|
312
408
|
export function ensureDir(dirPath) {
|
|
313
409
|
if (!fs.existsSync(dirPath)) {
|