evolclaw 2.8.0 → 2.8.2
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/dist/agents/templates.js +122 -0
- package/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +206 -103
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +676 -20
- package/dist/config.js +94 -22
- package/dist/core/agent-registry.js +450 -0
- package/dist/core/command-handler.js +422 -255
- package/dist/core/evolagent-registry.js +503 -0
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +315 -0
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +56 -11
- package/dist/core/message/message-queue.js +59 -4
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +119 -20
- package/dist/ipc.js +47 -0
- package/dist/paths.js +2 -0
- package/dist/types.js +2 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- package/dist/utils/reload-hooks.js +87 -0
- package/dist/utils/rich-content-renderer.js +33 -0
- package/dist/utils/stats-collector.js +15 -10
- package/evolclaw-install-aun.md +48 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
4
5
|
import { spawn, execFile } from 'child_process';
|
|
5
6
|
import { promisify } from 'util';
|
|
6
7
|
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from './paths.js';
|
|
@@ -67,13 +68,11 @@ function rotateLogs(logDir) {
|
|
|
67
68
|
function countLines(pkgRoot, logDir) {
|
|
68
69
|
const srcDir = path.join(pkgRoot, 'src');
|
|
69
70
|
const statsFile = path.join(logDir, 'line-stats.log');
|
|
70
|
-
const countDir = (dir
|
|
71
|
+
const countDir = (dir) => {
|
|
71
72
|
if (!fs.existsSync(dir))
|
|
72
73
|
return 0;
|
|
73
74
|
let total = 0;
|
|
74
75
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
75
|
-
if (exclude && entry.name === exclude)
|
|
76
|
-
continue;
|
|
77
76
|
const full = path.join(dir, entry.name);
|
|
78
77
|
if (entry.isDirectory()) {
|
|
79
78
|
total += countDir(full);
|
|
@@ -92,12 +91,14 @@ function countLines(pkgRoot, logDir) {
|
|
|
92
91
|
console.log('\n[launcher] 正在统计代码行数...\n');
|
|
93
92
|
const core = countDir(path.join(srcDir, 'core'));
|
|
94
93
|
const agents = countDir(path.join(srcDir, 'agents'));
|
|
95
|
-
const channels = countDir(path.join(srcDir, 'channels')
|
|
94
|
+
const channels = countDir(path.join(srcDir, 'channels'));
|
|
96
95
|
const utils = countDir(path.join(srcDir, 'utils'));
|
|
97
96
|
const entry = countFile(path.join(srcDir, 'index.ts'))
|
|
98
97
|
+ countFile(path.join(srcDir, 'config.ts'))
|
|
99
98
|
+ countFile(path.join(srcDir, 'types.ts'))
|
|
100
|
-
+ countFile(path.join(srcDir, 'cli.ts'))
|
|
99
|
+
+ countFile(path.join(srcDir, 'cli.ts'))
|
|
100
|
+
+ countFile(path.join(srcDir, 'ipc.ts'))
|
|
101
|
+
+ countFile(path.join(srcDir, 'paths.ts'));
|
|
101
102
|
const total = core + agents + channels + utils + entry;
|
|
102
103
|
console.log('==================================================');
|
|
103
104
|
console.log('EvolClaw 代码统计');
|
|
@@ -606,20 +607,26 @@ async function cmdStatus() {
|
|
|
606
607
|
showConfigChannels(config);
|
|
607
608
|
}
|
|
608
609
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
610
|
+
// EvolAgent summary (via IPC, only when running)
|
|
611
|
+
if (pid) {
|
|
612
|
+
try {
|
|
613
|
+
const agentResult = await ipcQuery(p.socket, { type: 'evolagent.list' });
|
|
614
|
+
if (agentResult?.ok && agentResult.agents?.length > 0) {
|
|
615
|
+
const agents = agentResult.agents.filter((a) => !a.isDefault);
|
|
616
|
+
if (agents.length > 0) {
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log('🤖 EvolAgents:');
|
|
619
|
+
for (const a of agents) {
|
|
620
|
+
const statusIcon = a.status === 'running' ? '●' : a.status === 'error' ? '✗' : a.status === 'disabled' ? '○' : '◌';
|
|
621
|
+
const channels = a.channels?.join(', ') || '—';
|
|
622
|
+
console.log(` ${statusIcon} ${a.name.padEnd(14)} ${a.status.padEnd(10)} ${channels}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
// IPC query for agents failed — skip section
|
|
629
|
+
}
|
|
623
630
|
}
|
|
624
631
|
}
|
|
625
632
|
// Log line pattern: [timestamp] [LEVEL] [Module?] message
|
|
@@ -1378,6 +1385,621 @@ async function cmdCtl(args) {
|
|
|
1378
1385
|
process.exit(1);
|
|
1379
1386
|
}
|
|
1380
1387
|
}
|
|
1388
|
+
// ==================== Agent ====================
|
|
1389
|
+
async function cmdAgent(args) {
|
|
1390
|
+
const sub = args[0];
|
|
1391
|
+
if (!sub) {
|
|
1392
|
+
await cmdAgentList();
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (sub === 'new') {
|
|
1396
|
+
const name = args[1];
|
|
1397
|
+
const nonInteractive = args.includes('--non-interactive');
|
|
1398
|
+
if (nonInteractive) {
|
|
1399
|
+
if (!name) {
|
|
1400
|
+
console.error('Usage: evolclaw agent new <name> --non-interactive ...');
|
|
1401
|
+
process.exit(1);
|
|
1402
|
+
}
|
|
1403
|
+
await cmdAgentNewNonInteractive(name, args.slice(2));
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
// Interactive mode: name from CLI is suggested default; user can override at prompt
|
|
1407
|
+
await cmdAgentNew(name);
|
|
1408
|
+
}
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if (sub === 'reload') {
|
|
1412
|
+
const name = args[1];
|
|
1413
|
+
if (!name) {
|
|
1414
|
+
console.error('Usage: evolclaw agent reload <name>');
|
|
1415
|
+
process.exit(1);
|
|
1416
|
+
}
|
|
1417
|
+
const p = resolvePaths();
|
|
1418
|
+
try {
|
|
1419
|
+
const result = await ipcQuery(p.socket, { type: 'evolagent.reload', name });
|
|
1420
|
+
if (result && result.ok) {
|
|
1421
|
+
console.log(`✓ Agent "${name}" reloaded`);
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
console.error(`✗ Reload failed: ${result?.error || 'unknown error'}`);
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
catch {
|
|
1429
|
+
console.error('⚠ evolclaw 未运行,请先 evolclaw start 后再 reload');
|
|
1430
|
+
console.log(' 或直接 evolclaw restart 重新加载所有 agent');
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
// `evolclaw agent <name>` — show detail
|
|
1436
|
+
await cmdAgentShow(sub);
|
|
1437
|
+
}
|
|
1438
|
+
async function cmdAgentList() {
|
|
1439
|
+
const p = resolvePaths();
|
|
1440
|
+
// Try IPC first (running process has real status)
|
|
1441
|
+
try {
|
|
1442
|
+
const result = await ipcQuery(p.socket, { type: 'evolagent.list' });
|
|
1443
|
+
if (result && result.ok && result.agents) {
|
|
1444
|
+
printAgentTable(result.agents);
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
catch {
|
|
1449
|
+
// IPC unavailable — fall through to cold mode
|
|
1450
|
+
}
|
|
1451
|
+
// Cold mode: read from disk
|
|
1452
|
+
const { EvolAgentRegistry } = await import('./core/evolagent-registry.js');
|
|
1453
|
+
const { loadConfig } = await import('./config.js');
|
|
1454
|
+
let config;
|
|
1455
|
+
try {
|
|
1456
|
+
config = loadConfig(p.config);
|
|
1457
|
+
}
|
|
1458
|
+
catch {
|
|
1459
|
+
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1460
|
+
}
|
|
1461
|
+
const registry = new EvolAgentRegistry(p.agentsDir);
|
|
1462
|
+
registry.loadAll(config);
|
|
1463
|
+
printAgentTable(registry.list());
|
|
1464
|
+
}
|
|
1465
|
+
function printAgentTable(list) {
|
|
1466
|
+
if (list.length === 0) {
|
|
1467
|
+
console.log('No agents configured.');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
console.log('NAME'.padEnd(14) + 'STATUS'.padEnd(10) + 'CHANNELS'.padEnd(24) +
|
|
1471
|
+
'PROJECT'.padEnd(22) + 'BASEAGENT'.padEnd(11) + 'LAST ACTIVE');
|
|
1472
|
+
for (const info of list) {
|
|
1473
|
+
const name = info.isDefault ? '[default]' : info.name;
|
|
1474
|
+
const status = info.status || 'stopped';
|
|
1475
|
+
const channels = info.channels?.length > 0 ? info.channels.join(', ').slice(0, 22) : '—';
|
|
1476
|
+
const project = info.projectPath ? path.basename(info.projectPath) : '—';
|
|
1477
|
+
const baseagent = info.baseagent || '—';
|
|
1478
|
+
const lastActive = info.lastActivity
|
|
1479
|
+
? formatTimeAgo(Date.now() - info.lastActivity)
|
|
1480
|
+
: '—';
|
|
1481
|
+
console.log(name.padEnd(14) +
|
|
1482
|
+
status.padEnd(10) +
|
|
1483
|
+
channels.padEnd(24) +
|
|
1484
|
+
project.padEnd(22) +
|
|
1485
|
+
baseagent.padEnd(11) +
|
|
1486
|
+
lastActive);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async function cmdAgentShow(name) {
|
|
1490
|
+
const p = resolvePaths();
|
|
1491
|
+
// Try IPC first
|
|
1492
|
+
try {
|
|
1493
|
+
const result = await ipcQuery(p.socket, { type: 'evolagent.show', name });
|
|
1494
|
+
if (result && result.ok && result.agent) {
|
|
1495
|
+
const info = result.agent;
|
|
1496
|
+
console.log(`${info.name} (${info.status})\n`);
|
|
1497
|
+
console.log(` Baseagent: ${info.baseagent}`);
|
|
1498
|
+
if (info.model)
|
|
1499
|
+
console.log(` Model: ${info.model}`);
|
|
1500
|
+
if (info.effort)
|
|
1501
|
+
console.log(` Effort: ${info.effort}`);
|
|
1502
|
+
console.log(` Project: ${info.projectPath}`);
|
|
1503
|
+
console.log(` Channels: ${info.channels?.join(', ') || '—'}`);
|
|
1504
|
+
if (info.activeSessions)
|
|
1505
|
+
console.log(` Sessions: ${info.activeSessions} active`);
|
|
1506
|
+
if (info.lastActivity)
|
|
1507
|
+
console.log(` Last active: ${formatTimeAgo(Date.now() - info.lastActivity)}`);
|
|
1508
|
+
if (info.error)
|
|
1509
|
+
console.log(` Error: ${info.error}`);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
// IPC unavailable — fall through to cold mode
|
|
1515
|
+
}
|
|
1516
|
+
// Cold mode
|
|
1517
|
+
const { EvolAgentRegistry } = await import('./core/evolagent-registry.js');
|
|
1518
|
+
const { loadConfig } = await import('./config.js');
|
|
1519
|
+
let config;
|
|
1520
|
+
try {
|
|
1521
|
+
config = loadConfig(p.config);
|
|
1522
|
+
}
|
|
1523
|
+
catch {
|
|
1524
|
+
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1525
|
+
}
|
|
1526
|
+
const registry = new EvolAgentRegistry(p.agentsDir);
|
|
1527
|
+
registry.loadAll(config);
|
|
1528
|
+
const agent = registry.get(name);
|
|
1529
|
+
if (!agent) {
|
|
1530
|
+
console.error(`Agent "${name}" not found.`);
|
|
1531
|
+
const allList = registry.list().filter(i => !i.isDefault);
|
|
1532
|
+
if (allList.length > 0) {
|
|
1533
|
+
console.log(`Available: ${allList.map(i => i.name).join(', ')}`);
|
|
1534
|
+
}
|
|
1535
|
+
process.exit(1);
|
|
1536
|
+
}
|
|
1537
|
+
console.log(`${agent.name} (${agent.status})\n`);
|
|
1538
|
+
console.log(` Baseagent: ${agent.baseagent}`);
|
|
1539
|
+
if (agent.model)
|
|
1540
|
+
console.log(` Model: ${agent.model}`);
|
|
1541
|
+
if (agent.effort)
|
|
1542
|
+
console.log(` Effort: ${agent.effort}`);
|
|
1543
|
+
console.log(` Project: ${agent.projectPath}`);
|
|
1544
|
+
console.log(` Channels: ${agent.channelInstanceNames().join(', ') || '—'}`);
|
|
1545
|
+
if (agent.error)
|
|
1546
|
+
console.log(` Error: ${agent.error}`);
|
|
1547
|
+
if (agent.configPath)
|
|
1548
|
+
console.log(` Config: ${agent.configPath}`);
|
|
1549
|
+
}
|
|
1550
|
+
async function cmdAgentNew(suggestedName) {
|
|
1551
|
+
const p = resolvePaths();
|
|
1552
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1553
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
1554
|
+
let name;
|
|
1555
|
+
try {
|
|
1556
|
+
const prompt = suggestedName
|
|
1557
|
+
? `Agent name [${suggestedName}]: `
|
|
1558
|
+
: 'Agent name: ';
|
|
1559
|
+
const input = (await ask(prompt)).trim();
|
|
1560
|
+
name = input || suggestedName;
|
|
1561
|
+
if (!name) {
|
|
1562
|
+
console.error('Agent name is required.');
|
|
1563
|
+
process.exit(1);
|
|
1564
|
+
}
|
|
1565
|
+
// Disallow dots/slashes/spaces in agent name (used as filename + channel-name prefix)
|
|
1566
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
1567
|
+
console.error(`Invalid agent name "${name}": only letters/digits/_/- allowed`);
|
|
1568
|
+
process.exit(1);
|
|
1569
|
+
}
|
|
1570
|
+
const agentPath = path.join(p.agentsDir, `${name}.json`);
|
|
1571
|
+
if (fs.existsSync(agentPath)) {
|
|
1572
|
+
console.error(`Agent "${name}" already exists: ${agentPath}`);
|
|
1573
|
+
process.exit(1);
|
|
1574
|
+
}
|
|
1575
|
+
console.log(`\nCreating agent: ${name}\n`);
|
|
1576
|
+
// Suggest project path: sibling of evolclaw.json's defaultPath, named after agent
|
|
1577
|
+
let suggestedProjectPath = '';
|
|
1578
|
+
try {
|
|
1579
|
+
const cfg = loadConfig(p.config);
|
|
1580
|
+
const defaultProjectsRoot = cfg.projects?.defaultPath
|
|
1581
|
+
? path.dirname(cfg.projects.defaultPath)
|
|
1582
|
+
: path.join(os.homedir(), 'evolclaw-projects');
|
|
1583
|
+
suggestedProjectPath = path.join(defaultProjectsRoot, name);
|
|
1584
|
+
}
|
|
1585
|
+
catch {
|
|
1586
|
+
suggestedProjectPath = path.join(os.homedir(), 'evolclaw-projects', name);
|
|
1587
|
+
}
|
|
1588
|
+
const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
|
|
1589
|
+
const projectPath = projectInput || suggestedProjectPath;
|
|
1590
|
+
if (!path.isAbsolute(projectPath)) {
|
|
1591
|
+
console.error('Project path must be an absolute path.');
|
|
1592
|
+
process.exit(1);
|
|
1593
|
+
}
|
|
1594
|
+
if (!fs.existsSync(projectPath)) {
|
|
1595
|
+
const create = (await ask(`Project path does not exist. Create ${projectPath}? [Y/n]: `)).trim().toLowerCase();
|
|
1596
|
+
if (create === '' || create === 'y' || create === 'yes') {
|
|
1597
|
+
try {
|
|
1598
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
1599
|
+
console.log(` ✓ Created ${projectPath}`);
|
|
1600
|
+
}
|
|
1601
|
+
catch (e) {
|
|
1602
|
+
console.error(`Failed to create ${projectPath}: ${e?.message || e}`);
|
|
1603
|
+
process.exit(1);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
else {
|
|
1607
|
+
console.error('Aborted: project path does not exist.');
|
|
1608
|
+
process.exit(1);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
|
|
1612
|
+
const baseagent = (await ask(`Baseagent (${baseagentChoices.join('/')}) [claude]: `)).trim() || 'claude';
|
|
1613
|
+
if (!baseagentChoices.includes(baseagent)) {
|
|
1614
|
+
console.error(`Invalid baseagent: ${baseagent}`);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
const model = (await ask('Model (leave empty for default): ')).trim() || undefined;
|
|
1618
|
+
const effort = (await ask('Effort (low/medium/high/max) [high]: ')).trim() || 'high';
|
|
1619
|
+
const chatmodeChoices = ['interactive', 'proactive'];
|
|
1620
|
+
console.log('\nChat mode determines how the agent responds:');
|
|
1621
|
+
console.log(' interactive — replies to every message; agent output sent automatically');
|
|
1622
|
+
console.log(' proactive — silent by default; agent decides when/whether to send via ctl send');
|
|
1623
|
+
const chatmodePrivate = (await ask(`Private (1-on-1) chat mode (${chatmodeChoices.join('/')}) [interactive]: `)).trim() || 'interactive';
|
|
1624
|
+
if (!chatmodeChoices.includes(chatmodePrivate)) {
|
|
1625
|
+
console.error(`Invalid chat mode: ${chatmodePrivate}`);
|
|
1626
|
+
process.exit(1);
|
|
1627
|
+
}
|
|
1628
|
+
const chatmodeGroup = (await ask(`Group chat mode (${chatmodeChoices.join('/')}) [proactive]: `)).trim() || 'proactive';
|
|
1629
|
+
if (!chatmodeChoices.includes(chatmodeGroup)) {
|
|
1630
|
+
console.error(`Invalid chat mode: ${chatmodeGroup}`);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
// Channels (loop to allow multiple)
|
|
1634
|
+
const channelsConfig = {};
|
|
1635
|
+
const { getChannelCredentialCollector } = await import('./utils/init-channel.js');
|
|
1636
|
+
// Close outer rl before channel loop (collectors create their own readline)
|
|
1637
|
+
rl.close();
|
|
1638
|
+
while (true) {
|
|
1639
|
+
const loopRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1640
|
+
const loopAsk = (q) => new Promise(r => loopRl.question(q, r));
|
|
1641
|
+
const addChannel = (await loopAsk('\nAdd channel? (y/n) [n]: ')).trim().toLowerCase();
|
|
1642
|
+
if (addChannel !== 'y') {
|
|
1643
|
+
loopRl.close();
|
|
1644
|
+
break;
|
|
1645
|
+
}
|
|
1646
|
+
const channelType = (await loopAsk('Channel type (feishu/aun/wechat/wecom/dingtalk/qqbot): ')).trim();
|
|
1647
|
+
const collector = getChannelCredentialCollector(channelType);
|
|
1648
|
+
if (!collector) {
|
|
1649
|
+
console.error(`Unknown channel type: ${channelType}`);
|
|
1650
|
+
loopRl.close();
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
// Close loop rl before collector opens its own
|
|
1654
|
+
loopRl.close();
|
|
1655
|
+
let creds = null;
|
|
1656
|
+
try {
|
|
1657
|
+
creds = await collector();
|
|
1658
|
+
}
|
|
1659
|
+
catch (e) {
|
|
1660
|
+
console.error(` Channel setup failed: ${e?.message || e}`);
|
|
1661
|
+
}
|
|
1662
|
+
if (!creds) {
|
|
1663
|
+
console.log(' Channel setup cancelled.');
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
const nameRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1667
|
+
const defaultEffName = `${name}-${channelType}`;
|
|
1668
|
+
const instName = await new Promise(r => nameRl.question(` Channel instance name (leave empty for ${defaultEffName}): `, r));
|
|
1669
|
+
nameRl.close();
|
|
1670
|
+
const trimmedName = instName.trim();
|
|
1671
|
+
if (trimmedName)
|
|
1672
|
+
creds.name = trimmedName;
|
|
1673
|
+
// else: omit name; effective channel name will be ${agent.name}-${type} via EvolAgent.effectiveChannelName
|
|
1674
|
+
if (!channelsConfig[channelType])
|
|
1675
|
+
channelsConfig[channelType] = [];
|
|
1676
|
+
channelsConfig[channelType].push(creds);
|
|
1677
|
+
}
|
|
1678
|
+
// Simplify channels: if only one instance per type, unwrap from array
|
|
1679
|
+
const finalChannels = {};
|
|
1680
|
+
for (const [type, instances] of Object.entries(channelsConfig)) {
|
|
1681
|
+
finalChannels[type] = instances.length === 1 ? instances[0] : instances;
|
|
1682
|
+
}
|
|
1683
|
+
const agentConfig = {
|
|
1684
|
+
name,
|
|
1685
|
+
enabled: true,
|
|
1686
|
+
agents: { [baseagent]: { ...(model && { model }), effort } },
|
|
1687
|
+
channels: finalChannels,
|
|
1688
|
+
projects: { defaultPath: projectPath.trim() },
|
|
1689
|
+
chatmode: { private: chatmodePrivate, group: chatmodeGroup },
|
|
1690
|
+
};
|
|
1691
|
+
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
1692
|
+
fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
|
|
1693
|
+
console.log(`\n✓ Created: ${agentPath}`);
|
|
1694
|
+
console.log(' Run `evolclaw restart` to activate.');
|
|
1695
|
+
}
|
|
1696
|
+
finally {
|
|
1697
|
+
// rl may already be closed if channel collector was invoked
|
|
1698
|
+
try {
|
|
1699
|
+
rl.close();
|
|
1700
|
+
}
|
|
1701
|
+
catch { }
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
async function cmdAgentNewNonInteractive(name, args) {
|
|
1705
|
+
const p = resolvePaths();
|
|
1706
|
+
const agentPath = path.join(p.agentsDir, `${name}.json`);
|
|
1707
|
+
if (fs.existsSync(agentPath)) {
|
|
1708
|
+
console.error(`Agent "${name}" already exists: ${agentPath}`);
|
|
1709
|
+
process.exit(1);
|
|
1710
|
+
}
|
|
1711
|
+
// Helper: extract --flag value from args
|
|
1712
|
+
const getArg = (flag) => {
|
|
1713
|
+
const idx = args.indexOf(flag);
|
|
1714
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
1715
|
+
};
|
|
1716
|
+
// Required: baseagent + project
|
|
1717
|
+
const baseagent = getArg('--baseagent');
|
|
1718
|
+
if (!baseagent) {
|
|
1719
|
+
console.error('--baseagent is required (claude|codex|gemini|hermes)');
|
|
1720
|
+
process.exit(1);
|
|
1721
|
+
}
|
|
1722
|
+
const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
|
|
1723
|
+
if (!baseagentChoices.includes(baseagent)) {
|
|
1724
|
+
console.error(`Invalid --baseagent: ${baseagent}`);
|
|
1725
|
+
process.exit(1);
|
|
1726
|
+
}
|
|
1727
|
+
const project = getArg('--project');
|
|
1728
|
+
if (!project) {
|
|
1729
|
+
console.error('--project is required (absolute path)');
|
|
1730
|
+
process.exit(1);
|
|
1731
|
+
}
|
|
1732
|
+
if (!path.isAbsolute(project)) {
|
|
1733
|
+
console.error(`--project must be absolute: ${project}`);
|
|
1734
|
+
process.exit(1);
|
|
1735
|
+
}
|
|
1736
|
+
if (!fs.existsSync(project)) {
|
|
1737
|
+
try {
|
|
1738
|
+
fs.mkdirSync(project, { recursive: true });
|
|
1739
|
+
console.log(` ✓ Created ${project}`);
|
|
1740
|
+
}
|
|
1741
|
+
catch (e) {
|
|
1742
|
+
console.error(`Failed to create ${project}: ${e?.message || e}`);
|
|
1743
|
+
process.exit(1);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
// Optional: chatmode
|
|
1747
|
+
const chatmodePrivate = getArg('--chatmode-private') || 'interactive';
|
|
1748
|
+
const chatmodeGroup = getArg('--chatmode-group') || 'proactive';
|
|
1749
|
+
const chatmodeValid = new Set(['interactive', 'proactive']);
|
|
1750
|
+
if (!chatmodeValid.has(chatmodePrivate)) {
|
|
1751
|
+
console.error(`Invalid --chatmode-private: ${chatmodePrivate}`);
|
|
1752
|
+
process.exit(1);
|
|
1753
|
+
}
|
|
1754
|
+
if (!chatmodeValid.has(chatmodeGroup)) {
|
|
1755
|
+
console.error(`Invalid --chatmode-group: ${chatmodeGroup}`);
|
|
1756
|
+
process.exit(1);
|
|
1757
|
+
}
|
|
1758
|
+
// Channels
|
|
1759
|
+
const channelsConfig = {};
|
|
1760
|
+
// AUN
|
|
1761
|
+
const aunAid = getArg('--aun-aid');
|
|
1762
|
+
const aunOwner = getArg('--aun-owner');
|
|
1763
|
+
if (aunAid || aunOwner) {
|
|
1764
|
+
if (!aunAid || !aunOwner) {
|
|
1765
|
+
console.error('--aun-aid and --aun-owner must both be provided');
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
const { isValidAid, aidCreate } = await import('./channels/aun-ops.js');
|
|
1769
|
+
if (!isValidAid(aunAid)) {
|
|
1770
|
+
console.error(`Invalid --aun-aid: ${aunAid}`);
|
|
1771
|
+
process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
if (!isValidAid(aunOwner)) {
|
|
1774
|
+
console.error(`Invalid --aun-owner: ${aunOwner}`);
|
|
1775
|
+
process.exit(1);
|
|
1776
|
+
}
|
|
1777
|
+
try {
|
|
1778
|
+
const result = await aidCreate(aunAid);
|
|
1779
|
+
try {
|
|
1780
|
+
await result.client.close();
|
|
1781
|
+
}
|
|
1782
|
+
catch { /* ignore */ }
|
|
1783
|
+
console.log(`✓ AID ${result.alreadyExisted ? 'reused' : 'created'}: ${aunAid}`);
|
|
1784
|
+
}
|
|
1785
|
+
catch (e) {
|
|
1786
|
+
console.error(`AID creation failed: ${e?.message || e}`);
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
channelsConfig.aun = { enabled: true, aid: aunAid, owner: aunOwner };
|
|
1790
|
+
}
|
|
1791
|
+
// Feishu
|
|
1792
|
+
const feishuAppId = getArg('--feishu-app-id');
|
|
1793
|
+
const feishuAppSecret = getArg('--feishu-app-secret');
|
|
1794
|
+
if (feishuAppId || feishuAppSecret) {
|
|
1795
|
+
if (!feishuAppId || !feishuAppSecret) {
|
|
1796
|
+
console.error('--feishu-app-id and --feishu-app-secret must both be provided');
|
|
1797
|
+
process.exit(1);
|
|
1798
|
+
}
|
|
1799
|
+
channelsConfig.feishu = [{
|
|
1800
|
+
name: `feishu-${name}`,
|
|
1801
|
+
enabled: true,
|
|
1802
|
+
appId: feishuAppId,
|
|
1803
|
+
appSecret: feishuAppSecret,
|
|
1804
|
+
}];
|
|
1805
|
+
}
|
|
1806
|
+
// WeChat
|
|
1807
|
+
const wechatToken = getArg('--wechat-token');
|
|
1808
|
+
if (wechatToken) {
|
|
1809
|
+
channelsConfig.wechat = { enabled: true, token: wechatToken };
|
|
1810
|
+
}
|
|
1811
|
+
// WeCom
|
|
1812
|
+
const wecomBotId = getArg('--wecom-bot-id');
|
|
1813
|
+
const wecomSecret = getArg('--wecom-secret');
|
|
1814
|
+
if (wecomBotId || wecomSecret) {
|
|
1815
|
+
if (!wecomBotId || !wecomSecret) {
|
|
1816
|
+
console.error('--wecom-bot-id and --wecom-secret must both be provided');
|
|
1817
|
+
process.exit(1);
|
|
1818
|
+
}
|
|
1819
|
+
channelsConfig.wecom = { enabled: true, botId: wecomBotId, secret: wecomSecret };
|
|
1820
|
+
}
|
|
1821
|
+
// DingTalk
|
|
1822
|
+
const dingtalkClientId = getArg('--dingtalk-client-id');
|
|
1823
|
+
const dingtalkClientSecret = getArg('--dingtalk-client-secret');
|
|
1824
|
+
if (dingtalkClientId || dingtalkClientSecret) {
|
|
1825
|
+
if (!dingtalkClientId || !dingtalkClientSecret) {
|
|
1826
|
+
console.error('--dingtalk-client-id and --dingtalk-client-secret must both be provided');
|
|
1827
|
+
process.exit(1);
|
|
1828
|
+
}
|
|
1829
|
+
channelsConfig.dingtalk = { enabled: true, clientId: dingtalkClientId, clientSecret: dingtalkClientSecret };
|
|
1830
|
+
}
|
|
1831
|
+
// QQBot
|
|
1832
|
+
const qqbotAppId = getArg('--qqbot-app-id');
|
|
1833
|
+
const qqbotClientSecret = getArg('--qqbot-client-secret');
|
|
1834
|
+
if (qqbotAppId || qqbotClientSecret) {
|
|
1835
|
+
if (!qqbotAppId || !qqbotClientSecret) {
|
|
1836
|
+
console.error('--qqbot-app-id and --qqbot-client-secret must both be provided');
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1839
|
+
channelsConfig.qqbot = { enabled: true, appId: qqbotAppId, clientSecret: qqbotClientSecret };
|
|
1840
|
+
}
|
|
1841
|
+
if (Object.keys(channelsConfig).length === 0) {
|
|
1842
|
+
console.error('At least one channel must be configured (aun / feishu / wechat / wecom / dingtalk / qqbot)');
|
|
1843
|
+
process.exit(1);
|
|
1844
|
+
}
|
|
1845
|
+
const agentConfig = {
|
|
1846
|
+
name,
|
|
1847
|
+
enabled: true,
|
|
1848
|
+
agents: { [baseagent]: {} },
|
|
1849
|
+
channels: channelsConfig,
|
|
1850
|
+
projects: { defaultPath: project },
|
|
1851
|
+
chatmode: { private: chatmodePrivate, group: chatmodeGroup },
|
|
1852
|
+
};
|
|
1853
|
+
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
1854
|
+
fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
|
|
1855
|
+
console.log(`✓ Created: ${agentPath}`);
|
|
1856
|
+
console.log(' Run `evolclaw restart` (or `evolclaw agent reload <name>`) to activate.');
|
|
1857
|
+
}
|
|
1858
|
+
// ==================== AID ====================
|
|
1859
|
+
async function cmdAid(args) {
|
|
1860
|
+
const sub = args[0] || 'list';
|
|
1861
|
+
if (sub === 'help') {
|
|
1862
|
+
console.log(`用法: evolclaw aid <command>
|
|
1863
|
+
|
|
1864
|
+
Commands:
|
|
1865
|
+
list 列出本地所有 AID
|
|
1866
|
+
new <aid> 创建新 AID 身份
|
|
1867
|
+
|
|
1868
|
+
示例:
|
|
1869
|
+
evolclaw aid list
|
|
1870
|
+
evolclaw aid new reviewer.agentid.pub`);
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('./channels/aun-ops.js');
|
|
1874
|
+
if (sub === 'list') {
|
|
1875
|
+
const aids = aidList();
|
|
1876
|
+
if (aids.length === 0) {
|
|
1877
|
+
console.log('本地无 AID');
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
console.log('本地 AID:');
|
|
1881
|
+
for (const a of aids) {
|
|
1882
|
+
const icons = [
|
|
1883
|
+
a.hasPrivateKey ? '🔑' : ' ',
|
|
1884
|
+
a.hasAgentMd ? '📄' : ' ',
|
|
1885
|
+
].join('');
|
|
1886
|
+
console.log(` ${icons} ${a.aid}`);
|
|
1887
|
+
}
|
|
1888
|
+
console.log('\n🔑=私钥 📄=agent.md');
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (sub === 'new') {
|
|
1892
|
+
const aid = args[1];
|
|
1893
|
+
if (!aid) {
|
|
1894
|
+
console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
|
|
1895
|
+
process.exit(1);
|
|
1896
|
+
}
|
|
1897
|
+
if (!isValidAid(aid)) {
|
|
1898
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1899
|
+
process.exit(1);
|
|
1900
|
+
}
|
|
1901
|
+
const result = await aidCreate(aid);
|
|
1902
|
+
if (!result.alreadyExisted) {
|
|
1903
|
+
const content = buildInitialAgentMd({ aid });
|
|
1904
|
+
try {
|
|
1905
|
+
await agentmdPut(content, { aid, client: result.client });
|
|
1906
|
+
console.log('✓ agent.md 已发布');
|
|
1907
|
+
}
|
|
1908
|
+
catch (e) {
|
|
1909
|
+
console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
try {
|
|
1913
|
+
await result.client.close();
|
|
1914
|
+
}
|
|
1915
|
+
catch { }
|
|
1916
|
+
const verb = result.alreadyExisted ? '已存在' : '已创建';
|
|
1917
|
+
console.log(`✓ ${aid} ${verb}`);
|
|
1918
|
+
console.log(' 如需上线 AUN 通道,运行 evolclaw init aun');
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
console.error(`未知子命令: ${sub}\n用法: evolclaw aid [list|new <aid>]`);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
// ==================== AgentMd ====================
|
|
1925
|
+
async function cmdAgentmd(args) {
|
|
1926
|
+
if (args.length === 0 || args[0] === 'help') {
|
|
1927
|
+
console.log(`用法: evolclaw agentmd <command> <aid>
|
|
1928
|
+
|
|
1929
|
+
Commands:
|
|
1930
|
+
<aid> 查看指定 AID 的 agent.md
|
|
1931
|
+
put <aid> 上传本地 agent.md 到 AUN 网络
|
|
1932
|
+
set <aid> <内容> 设置并上传 agent.md
|
|
1933
|
+
|
|
1934
|
+
示例:
|
|
1935
|
+
evolclaw agentmd mybot.agentid.pub
|
|
1936
|
+
evolclaw agentmd put mybot.agentid.pub
|
|
1937
|
+
evolclaw agentmd set mybot.agentid.pub "---\\naid: mybot.agentid.pub\\n---"`);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const { agentmdGet, agentmdPut, isValidAid } = await import('./channels/aun-ops.js');
|
|
1941
|
+
if (args[0] === 'put') {
|
|
1942
|
+
const aid = args[1];
|
|
1943
|
+
if (!aid) {
|
|
1944
|
+
console.error('用法: evolclaw agentmd put <aid>');
|
|
1945
|
+
process.exit(1);
|
|
1946
|
+
}
|
|
1947
|
+
if (!isValidAid(aid)) {
|
|
1948
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1949
|
+
process.exit(1);
|
|
1950
|
+
}
|
|
1951
|
+
// Read local file directly (put = push local → network)
|
|
1952
|
+
const localPath = path.join(os.homedir(), '.aun', 'AIDs', aid, 'agent.md');
|
|
1953
|
+
if (!fs.existsSync(localPath)) {
|
|
1954
|
+
console.error(`❌ 本地无 agent.md: ${aid}`);
|
|
1955
|
+
process.exit(1);
|
|
1956
|
+
}
|
|
1957
|
+
const content = fs.readFileSync(localPath, 'utf-8');
|
|
1958
|
+
await agentmdPut(content, { aid });
|
|
1959
|
+
console.log('✓ agent.md 已发布');
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (args[0] === 'set') {
|
|
1963
|
+
const aid = args[1];
|
|
1964
|
+
const content = args.slice(2).join(' ');
|
|
1965
|
+
if (!aid || !content) {
|
|
1966
|
+
console.error('用法: evolclaw agentmd set <aid> <内容>');
|
|
1967
|
+
process.exit(1);
|
|
1968
|
+
}
|
|
1969
|
+
if (!isValidAid(aid)) {
|
|
1970
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1971
|
+
process.exit(1);
|
|
1972
|
+
}
|
|
1973
|
+
await agentmdPut(content, { aid });
|
|
1974
|
+
console.log('✓ agent.md 已更新并发布');
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
// Default: view
|
|
1978
|
+
const aid = args[0];
|
|
1979
|
+
if (!isValidAid(aid)) {
|
|
1980
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1981
|
+
process.exit(1);
|
|
1982
|
+
}
|
|
1983
|
+
try {
|
|
1984
|
+
const md = await agentmdGet(aid);
|
|
1985
|
+
if (!md || !md.trim()) {
|
|
1986
|
+
console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
|
|
1987
|
+
}
|
|
1988
|
+
else {
|
|
1989
|
+
console.log(md);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
catch (e) {
|
|
1993
|
+
const msg = String(e.message || e);
|
|
1994
|
+
if (msg.includes('not found') || msg.includes('404')) {
|
|
1995
|
+
console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
|
|
1996
|
+
}
|
|
1997
|
+
else {
|
|
1998
|
+
console.error(`❌ 获取失败: ${msg.slice(0, 100)}`);
|
|
1999
|
+
process.exit(1);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1381
2003
|
// ==================== Main ====================
|
|
1382
2004
|
function getArgValue(args, flag) {
|
|
1383
2005
|
const idx = args.indexOf(flag);
|
|
@@ -1446,7 +2068,7 @@ export async function main(args) {
|
|
|
1446
2068
|
if (nonInteractive) {
|
|
1447
2069
|
await cmdInit({
|
|
1448
2070
|
nonInteractive: true,
|
|
1449
|
-
defaultPath: getArgValue(args, '--default-path') ||
|
|
2071
|
+
defaultPath: getArgValue(args, '--default-path') || path.join(os.homedir(), 'projects', 'default'),
|
|
1450
2072
|
channel: getArgValue(args, '--channel') || 'aun',
|
|
1451
2073
|
aunAid: getArgValue(args, '--aun-aid'),
|
|
1452
2074
|
aunOwner: getArgValue(args, '--aun-owner'),
|
|
@@ -1484,6 +2106,15 @@ export async function main(args) {
|
|
|
1484
2106
|
case 'ctl':
|
|
1485
2107
|
await cmdCtl(args.slice(1));
|
|
1486
2108
|
break;
|
|
2109
|
+
case 'agent':
|
|
2110
|
+
await cmdAgent(args.slice(1));
|
|
2111
|
+
break;
|
|
2112
|
+
case 'aid':
|
|
2113
|
+
await cmdAid(args.slice(1));
|
|
2114
|
+
break;
|
|
2115
|
+
case 'agentmd':
|
|
2116
|
+
await cmdAgentmd(args.slice(1));
|
|
2117
|
+
break;
|
|
1487
2118
|
default:
|
|
1488
2119
|
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|ctl|diagnose|mv}
|
|
1489
2120
|
|
|
@@ -1505,6 +2136,31 @@ Commands:
|
|
|
1505
2136
|
--raw 原始输出,不着色
|
|
1506
2137
|
ctl 运行时自管理(模型切换、推理强度、压缩上下文等)
|
|
1507
2138
|
evolclaw ctl help 查看完整命令列表
|
|
2139
|
+
agent 管理 EvolAgent
|
|
2140
|
+
agent 列出所有 agent
|
|
2141
|
+
agent <name> 查看指定 agent 详情
|
|
2142
|
+
agent new <name> 创建新 agent(交互式)
|
|
2143
|
+
agent new <name> --non-interactive ... 非交互创建(自动化)
|
|
2144
|
+
必填: --baseagent <claude|codex|gemini|hermes>
|
|
2145
|
+
--project <absolute path>
|
|
2146
|
+
可选 channel:
|
|
2147
|
+
--aun-aid <aid> --aun-owner <aid>
|
|
2148
|
+
--feishu-app-id xxx --feishu-app-secret yyy
|
|
2149
|
+
--wechat-token xxx
|
|
2150
|
+
--wecom-bot-id xxx --wecom-secret yyy
|
|
2151
|
+
--dingtalk-client-id xxx --dingtalk-client-secret yyy
|
|
2152
|
+
--qqbot-app-id xxx --qqbot-client-secret yyy
|
|
2153
|
+
可选行为:
|
|
2154
|
+
--chatmode-private <interactive|proactive> (默认 interactive)
|
|
2155
|
+
--chatmode-group <interactive|proactive> (默认 proactive)
|
|
2156
|
+
agent reload <n> 热重载 agent 配置
|
|
2157
|
+
aid AID 身份管理
|
|
2158
|
+
aid list 列出本地所有 AID
|
|
2159
|
+
aid new <aid> 创建新 AID 身份
|
|
2160
|
+
agentmd agent.md 管理
|
|
2161
|
+
agentmd <aid> 查看 agent.md
|
|
2162
|
+
agentmd put <aid> 上传本地 agent.md
|
|
2163
|
+
agentmd set <aid> <内容> 设置并上传
|
|
1508
2164
|
diagnose 诊断启动环境(配置、数据库、进程)
|
|
1509
2165
|
mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
|
|
1510
2166
|
|