evolclaw 2.7.3 → 2.8.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/dist/agents/claude-runner.js +91 -48
- package/dist/channels/aun-ops.js +275 -0
- package/dist/channels/aun.js +285 -125
- package/dist/cli.js +360 -1
- package/dist/config.js +1 -1
- package/dist/core/agent-registry.js +164 -0
- package/dist/core/command-handler.js +147 -97
- package/dist/core/evolagent-schema.js +72 -0
- package/dist/core/evolagent.js +66 -0
- package/dist/core/message/message-bridge.js +14 -2
- package/dist/core/message/message-processor.js +24 -10
- package/dist/core/message/thought-emitter.js +4 -2
- package/dist/core/session/session-manager.js +22 -3
- package/dist/index.js +8 -12
- package/dist/paths.js +2 -0
- package/dist/templates/prompts.md +1 -0
- package/dist/utils/init-channel.js +91 -221
- package/dist/utils/init.js +18 -42
- package/dist/utils/logger.js +58 -2
- package/evolclaw-install-aun.md +48 -7
- package/package.json +2 -2
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';
|
|
@@ -1378,6 +1379,343 @@ async function cmdCtl(args) {
|
|
|
1378
1379
|
process.exit(1);
|
|
1379
1380
|
}
|
|
1380
1381
|
}
|
|
1382
|
+
// ==================== Agent ====================
|
|
1383
|
+
async function cmdAgent(args) {
|
|
1384
|
+
const sub = args[0];
|
|
1385
|
+
if (!sub) {
|
|
1386
|
+
await cmdAgentList();
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
if (sub === 'new') {
|
|
1390
|
+
const name = args[1];
|
|
1391
|
+
if (!name) {
|
|
1392
|
+
console.error('Usage: evolclaw agent new <name>');
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
}
|
|
1395
|
+
await cmdAgentNew(name);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (sub === 'reload') {
|
|
1399
|
+
const name = args[1];
|
|
1400
|
+
if (!name) {
|
|
1401
|
+
console.error('Usage: evolclaw agent reload <name>');
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
}
|
|
1404
|
+
// Phase 1 stub: no IPC yet
|
|
1405
|
+
console.error('⚠ evolclaw 未运行或 IPC 未就绪,请使用 evolclaw restart 重新加载所有 agent');
|
|
1406
|
+
process.exit(1);
|
|
1407
|
+
}
|
|
1408
|
+
// `evolclaw agent <name>` — show detail
|
|
1409
|
+
await cmdAgentShow(sub);
|
|
1410
|
+
}
|
|
1411
|
+
async function cmdAgentList() {
|
|
1412
|
+
const p = resolvePaths();
|
|
1413
|
+
const { AgentRegistry } = await import('./core/agent-registry.js');
|
|
1414
|
+
const { loadConfig } = await import('./config.js');
|
|
1415
|
+
let config;
|
|
1416
|
+
try {
|
|
1417
|
+
config = loadConfig(p.config);
|
|
1418
|
+
}
|
|
1419
|
+
catch {
|
|
1420
|
+
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1421
|
+
}
|
|
1422
|
+
const registry = new AgentRegistry(p.agentsDir);
|
|
1423
|
+
registry.loadAll(config);
|
|
1424
|
+
const list = registry.list();
|
|
1425
|
+
if (list.length === 0) {
|
|
1426
|
+
console.log('No agents configured.');
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
// Table output
|
|
1430
|
+
console.log('NAME'.padEnd(14) + 'STATUS'.padEnd(10) + 'CHANNELS'.padEnd(24) + 'PROJECT'.padEnd(22) + 'BASEAGENT'.padEnd(11) + 'LAST ACTIVE');
|
|
1431
|
+
for (const info of list) {
|
|
1432
|
+
const name = info.isDefault ? '[default]' : info.name;
|
|
1433
|
+
const status = info.status;
|
|
1434
|
+
const channels = info.channels.length > 0 ? info.channels.join(', ').slice(0, 22) : '—';
|
|
1435
|
+
const project = info.projectPath ? path.basename(info.projectPath) : '—';
|
|
1436
|
+
const baseagent = info.baseagent || '—';
|
|
1437
|
+
const lastActive = info.lastActivity ? formatTimeAgo(Date.now() - info.lastActivity) : '—';
|
|
1438
|
+
console.log(name.padEnd(14) +
|
|
1439
|
+
status.padEnd(10) +
|
|
1440
|
+
channels.padEnd(24) +
|
|
1441
|
+
project.padEnd(22) +
|
|
1442
|
+
baseagent.padEnd(11) +
|
|
1443
|
+
lastActive);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
async function cmdAgentShow(name) {
|
|
1447
|
+
const p = resolvePaths();
|
|
1448
|
+
const { AgentRegistry } = await import('./core/agent-registry.js');
|
|
1449
|
+
const { loadConfig } = await import('./config.js');
|
|
1450
|
+
let config;
|
|
1451
|
+
try {
|
|
1452
|
+
config = loadConfig(p.config);
|
|
1453
|
+
}
|
|
1454
|
+
catch {
|
|
1455
|
+
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1456
|
+
}
|
|
1457
|
+
const registry = new AgentRegistry(p.agentsDir);
|
|
1458
|
+
registry.loadAll(config);
|
|
1459
|
+
const agent = registry.get(name);
|
|
1460
|
+
if (!agent) {
|
|
1461
|
+
console.error(`Agent "${name}" not found.`);
|
|
1462
|
+
const allList = registry.list().filter(i => !i.isDefault);
|
|
1463
|
+
if (allList.length > 0) {
|
|
1464
|
+
console.log(`Available: ${allList.map(i => i.name).join(', ')}`);
|
|
1465
|
+
}
|
|
1466
|
+
process.exit(1);
|
|
1467
|
+
}
|
|
1468
|
+
console.log(`${agent.name} (${agent.status})\n`);
|
|
1469
|
+
console.log(` Baseagent: ${agent.baseagent}`);
|
|
1470
|
+
if (agent.model)
|
|
1471
|
+
console.log(` Model: ${agent.model}`);
|
|
1472
|
+
if (agent.effort)
|
|
1473
|
+
console.log(` Effort: ${agent.effort}`);
|
|
1474
|
+
console.log(` Project: ${agent.projectPath}`);
|
|
1475
|
+
console.log(` Channels: ${agent.channelInstanceNames().join(', ') || '—'}`);
|
|
1476
|
+
if (agent.error)
|
|
1477
|
+
console.log(` Error: ${agent.error}`);
|
|
1478
|
+
if (agent.configPath)
|
|
1479
|
+
console.log(` Config: ${agent.configPath}`);
|
|
1480
|
+
}
|
|
1481
|
+
async function cmdAgentNew(name) {
|
|
1482
|
+
const p = resolvePaths();
|
|
1483
|
+
const agentPath = path.join(p.agentsDir, `${name}.json`);
|
|
1484
|
+
if (fs.existsSync(agentPath)) {
|
|
1485
|
+
console.error(`Agent "${name}" already exists: ${agentPath}`);
|
|
1486
|
+
process.exit(1);
|
|
1487
|
+
}
|
|
1488
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1489
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
1490
|
+
try {
|
|
1491
|
+
console.log(`\nCreating agent: ${name}\n`);
|
|
1492
|
+
const projectPath = await ask('Project path: ');
|
|
1493
|
+
if (!projectPath.trim() || !path.isAbsolute(projectPath.trim())) {
|
|
1494
|
+
console.error('Project path must be an absolute path.');
|
|
1495
|
+
process.exit(1);
|
|
1496
|
+
}
|
|
1497
|
+
const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
|
|
1498
|
+
const baseagent = (await ask(`Baseagent (${baseagentChoices.join('/')}) [claude]: `)).trim() || 'claude';
|
|
1499
|
+
if (!baseagentChoices.includes(baseagent)) {
|
|
1500
|
+
console.error(`Invalid baseagent: ${baseagent}`);
|
|
1501
|
+
process.exit(1);
|
|
1502
|
+
}
|
|
1503
|
+
const model = (await ask('Model (leave empty for default): ')).trim() || undefined;
|
|
1504
|
+
const effort = (await ask('Effort (low/medium/high/max) [high]: ')).trim() || 'high';
|
|
1505
|
+
const chatmodeChoices = ['interactive', 'proactive'];
|
|
1506
|
+
const chatmodePrivate = (await ask(`ChatMode private (${chatmodeChoices.join('/')}) [interactive]: `)).trim() || 'interactive';
|
|
1507
|
+
// Channels (loop to allow multiple)
|
|
1508
|
+
const channelsConfig = {};
|
|
1509
|
+
const { getChannelCredentialCollector } = await import('./utils/init-channel.js');
|
|
1510
|
+
// Close outer rl before channel loop (collectors create their own readline)
|
|
1511
|
+
rl.close();
|
|
1512
|
+
while (true) {
|
|
1513
|
+
const loopRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1514
|
+
const loopAsk = (q) => new Promise(r => loopRl.question(q, r));
|
|
1515
|
+
const addChannel = (await loopAsk('\nAdd channel? (y/n) [n]: ')).trim().toLowerCase();
|
|
1516
|
+
if (addChannel !== 'y') {
|
|
1517
|
+
loopRl.close();
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
const channelType = (await loopAsk('Channel type (feishu/aun/wechat/wecom/dingtalk/qqbot): ')).trim();
|
|
1521
|
+
const collector = getChannelCredentialCollector(channelType);
|
|
1522
|
+
if (!collector) {
|
|
1523
|
+
console.error(`Unknown channel type: ${channelType}`);
|
|
1524
|
+
loopRl.close();
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
// Close loop rl before collector opens its own
|
|
1528
|
+
loopRl.close();
|
|
1529
|
+
let creds = null;
|
|
1530
|
+
try {
|
|
1531
|
+
creds = await collector();
|
|
1532
|
+
}
|
|
1533
|
+
catch (e) {
|
|
1534
|
+
console.error(` Channel setup failed: ${e?.message || e}`);
|
|
1535
|
+
}
|
|
1536
|
+
if (!creds) {
|
|
1537
|
+
console.log(' Channel setup cancelled.');
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const nameRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1541
|
+
const instName = await new Promise(r => nameRl.question(` Channel instance name [${name}]: `, r));
|
|
1542
|
+
nameRl.close();
|
|
1543
|
+
creds.name = instName.trim() || name;
|
|
1544
|
+
if (!channelsConfig[channelType])
|
|
1545
|
+
channelsConfig[channelType] = [];
|
|
1546
|
+
channelsConfig[channelType].push(creds);
|
|
1547
|
+
}
|
|
1548
|
+
// Simplify channels: if only one instance per type, unwrap from array
|
|
1549
|
+
const finalChannels = {};
|
|
1550
|
+
for (const [type, instances] of Object.entries(channelsConfig)) {
|
|
1551
|
+
finalChannels[type] = instances.length === 1 ? instances[0] : instances;
|
|
1552
|
+
}
|
|
1553
|
+
const agentConfig = {
|
|
1554
|
+
name,
|
|
1555
|
+
enabled: true,
|
|
1556
|
+
agents: { [baseagent]: { ...(model && { model }), effort } },
|
|
1557
|
+
channels: finalChannels,
|
|
1558
|
+
projects: { defaultPath: projectPath.trim() },
|
|
1559
|
+
chatmode: { private: chatmodePrivate, group: 'proactive' },
|
|
1560
|
+
};
|
|
1561
|
+
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
1562
|
+
fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
|
|
1563
|
+
console.log(`\n✓ Created: ${agentPath}`);
|
|
1564
|
+
console.log(' Run `evolclaw restart` to activate.');
|
|
1565
|
+
}
|
|
1566
|
+
finally {
|
|
1567
|
+
// rl may already be closed if channel collector was invoked
|
|
1568
|
+
try {
|
|
1569
|
+
rl.close();
|
|
1570
|
+
}
|
|
1571
|
+
catch { }
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
// ==================== AID ====================
|
|
1575
|
+
async function cmdAid(args) {
|
|
1576
|
+
const sub = args[0] || 'list';
|
|
1577
|
+
if (sub === 'help') {
|
|
1578
|
+
console.log(`用法: evolclaw aid <command>
|
|
1579
|
+
|
|
1580
|
+
Commands:
|
|
1581
|
+
list 列出本地所有 AID
|
|
1582
|
+
new <aid> 创建新 AID 身份
|
|
1583
|
+
|
|
1584
|
+
示例:
|
|
1585
|
+
evolclaw aid list
|
|
1586
|
+
evolclaw aid new reviewer.agentid.pub`);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
const { aidList, aidCreate, agentmdPut, buildInitialAgentMd, isValidAid } = await import('./channels/aun-ops.js');
|
|
1590
|
+
if (sub === 'list') {
|
|
1591
|
+
const aids = aidList();
|
|
1592
|
+
if (aids.length === 0) {
|
|
1593
|
+
console.log('本地无 AID');
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
console.log('本地 AID:');
|
|
1597
|
+
for (const a of aids) {
|
|
1598
|
+
const icons = [
|
|
1599
|
+
a.hasPrivateKey ? '🔑' : ' ',
|
|
1600
|
+
a.hasAgentMd ? '📄' : ' ',
|
|
1601
|
+
].join('');
|
|
1602
|
+
console.log(` ${icons} ${a.aid}`);
|
|
1603
|
+
}
|
|
1604
|
+
console.log('\n🔑=私钥 📄=agent.md');
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (sub === 'new') {
|
|
1608
|
+
const aid = args[1];
|
|
1609
|
+
if (!aid) {
|
|
1610
|
+
console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
|
|
1611
|
+
process.exit(1);
|
|
1612
|
+
}
|
|
1613
|
+
if (!isValidAid(aid)) {
|
|
1614
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
const result = await aidCreate(aid);
|
|
1618
|
+
if (!result.alreadyExisted) {
|
|
1619
|
+
const content = buildInitialAgentMd({ aid });
|
|
1620
|
+
try {
|
|
1621
|
+
await agentmdPut(content, { aid, client: result.client });
|
|
1622
|
+
console.log('✓ agent.md 已发布');
|
|
1623
|
+
}
|
|
1624
|
+
catch (e) {
|
|
1625
|
+
console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
await result.client.close();
|
|
1630
|
+
}
|
|
1631
|
+
catch { }
|
|
1632
|
+
const verb = result.alreadyExisted ? '已存在' : '已创建';
|
|
1633
|
+
console.log(`✓ ${aid} ${verb}`);
|
|
1634
|
+
console.log(' 如需上线 AUN 通道,运行 evolclaw init aun');
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
console.error(`未知子命令: ${sub}\n用法: evolclaw aid [list|new <aid>]`);
|
|
1638
|
+
process.exit(1);
|
|
1639
|
+
}
|
|
1640
|
+
// ==================== AgentMd ====================
|
|
1641
|
+
async function cmdAgentmd(args) {
|
|
1642
|
+
if (args.length === 0 || args[0] === 'help') {
|
|
1643
|
+
console.log(`用法: evolclaw agentmd <command> <aid>
|
|
1644
|
+
|
|
1645
|
+
Commands:
|
|
1646
|
+
<aid> 查看指定 AID 的 agent.md
|
|
1647
|
+
put <aid> 上传本地 agent.md 到 AUN 网络
|
|
1648
|
+
set <aid> <内容> 设置并上传 agent.md
|
|
1649
|
+
|
|
1650
|
+
示例:
|
|
1651
|
+
evolclaw agentmd mybot.agentid.pub
|
|
1652
|
+
evolclaw agentmd put mybot.agentid.pub
|
|
1653
|
+
evolclaw agentmd set mybot.agentid.pub "---\\naid: mybot.agentid.pub\\n---"`);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const { agentmdGet, agentmdPut, isValidAid } = await import('./channels/aun-ops.js');
|
|
1657
|
+
if (args[0] === 'put') {
|
|
1658
|
+
const aid = args[1];
|
|
1659
|
+
if (!aid) {
|
|
1660
|
+
console.error('用法: evolclaw agentmd put <aid>');
|
|
1661
|
+
process.exit(1);
|
|
1662
|
+
}
|
|
1663
|
+
if (!isValidAid(aid)) {
|
|
1664
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1665
|
+
process.exit(1);
|
|
1666
|
+
}
|
|
1667
|
+
// Read local file directly (put = push local → network)
|
|
1668
|
+
const localPath = path.join(os.homedir(), '.aun', 'AIDs', aid, 'agent.md');
|
|
1669
|
+
if (!fs.existsSync(localPath)) {
|
|
1670
|
+
console.error(`❌ 本地无 agent.md: ${aid}`);
|
|
1671
|
+
process.exit(1);
|
|
1672
|
+
}
|
|
1673
|
+
const content = fs.readFileSync(localPath, 'utf-8');
|
|
1674
|
+
await agentmdPut(content, { aid });
|
|
1675
|
+
console.log('✓ agent.md 已发布');
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (args[0] === 'set') {
|
|
1679
|
+
const aid = args[1];
|
|
1680
|
+
const content = args.slice(2).join(' ');
|
|
1681
|
+
if (!aid || !content) {
|
|
1682
|
+
console.error('用法: evolclaw agentmd set <aid> <内容>');
|
|
1683
|
+
process.exit(1);
|
|
1684
|
+
}
|
|
1685
|
+
if (!isValidAid(aid)) {
|
|
1686
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1687
|
+
process.exit(1);
|
|
1688
|
+
}
|
|
1689
|
+
await agentmdPut(content, { aid });
|
|
1690
|
+
console.log('✓ agent.md 已更新并发布');
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
// Default: view
|
|
1694
|
+
const aid = args[0];
|
|
1695
|
+
if (!isValidAid(aid)) {
|
|
1696
|
+
console.error(`❌ 无效 AID 格式: ${aid}`);
|
|
1697
|
+
process.exit(1);
|
|
1698
|
+
}
|
|
1699
|
+
try {
|
|
1700
|
+
const md = await agentmdGet(aid);
|
|
1701
|
+
if (!md || !md.trim()) {
|
|
1702
|
+
console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
console.log(md);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
catch (e) {
|
|
1709
|
+
const msg = String(e.message || e);
|
|
1710
|
+
if (msg.includes('not found') || msg.includes('404')) {
|
|
1711
|
+
console.log(`ℹ️ ${aid} 尚未设置 agent.md`);
|
|
1712
|
+
}
|
|
1713
|
+
else {
|
|
1714
|
+
console.error(`❌ 获取失败: ${msg.slice(0, 100)}`);
|
|
1715
|
+
process.exit(1);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1381
1719
|
// ==================== Main ====================
|
|
1382
1720
|
function getArgValue(args, flag) {
|
|
1383
1721
|
const idx = args.indexOf(flag);
|
|
@@ -1446,7 +1784,7 @@ export async function main(args) {
|
|
|
1446
1784
|
if (nonInteractive) {
|
|
1447
1785
|
await cmdInit({
|
|
1448
1786
|
nonInteractive: true,
|
|
1449
|
-
defaultPath: getArgValue(args, '--default-path') ||
|
|
1787
|
+
defaultPath: getArgValue(args, '--default-path') || path.join(os.homedir(), 'projects', 'default'),
|
|
1450
1788
|
channel: getArgValue(args, '--channel') || 'aun',
|
|
1451
1789
|
aunAid: getArgValue(args, '--aun-aid'),
|
|
1452
1790
|
aunOwner: getArgValue(args, '--aun-owner'),
|
|
@@ -1484,6 +1822,15 @@ export async function main(args) {
|
|
|
1484
1822
|
case 'ctl':
|
|
1485
1823
|
await cmdCtl(args.slice(1));
|
|
1486
1824
|
break;
|
|
1825
|
+
case 'agent':
|
|
1826
|
+
await cmdAgent(args.slice(1));
|
|
1827
|
+
break;
|
|
1828
|
+
case 'aid':
|
|
1829
|
+
await cmdAid(args.slice(1));
|
|
1830
|
+
break;
|
|
1831
|
+
case 'agentmd':
|
|
1832
|
+
await cmdAgentmd(args.slice(1));
|
|
1833
|
+
break;
|
|
1487
1834
|
default:
|
|
1488
1835
|
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|ctl|diagnose|mv}
|
|
1489
1836
|
|
|
@@ -1505,6 +1852,18 @@ Commands:
|
|
|
1505
1852
|
--raw 原始输出,不着色
|
|
1506
1853
|
ctl 运行时自管理(模型切换、推理强度、压缩上下文等)
|
|
1507
1854
|
evolclaw ctl help 查看完整命令列表
|
|
1855
|
+
agent 管理 EvolAgent
|
|
1856
|
+
agent 列出所有 agent
|
|
1857
|
+
agent <name> 查看指定 agent 详情
|
|
1858
|
+
agent new <name> 创建新 agent(交互式)
|
|
1859
|
+
agent reload <n> 热重载 agent 配置
|
|
1860
|
+
aid AID 身份管理
|
|
1861
|
+
aid list 列出本地所有 AID
|
|
1862
|
+
aid new <aid> 创建新 AID 身份
|
|
1863
|
+
agentmd agent.md 管理
|
|
1864
|
+
agentmd <aid> 查看 agent.md
|
|
1865
|
+
agentmd put <aid> 上传本地 agent.md
|
|
1866
|
+
agentmd set <aid> <内容> 设置并上传
|
|
1508
1867
|
diagnose 诊断启动环境(配置、数据库、进程)
|
|
1509
1868
|
mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
|
|
1510
1869
|
|
package/dist/config.js
CHANGED
|
@@ -155,7 +155,7 @@ export function loadConfig(configPath = resolvePaths().config) {
|
|
|
155
155
|
logger.warn(`Config file missing, creating from sample: ${samplePath}`);
|
|
156
156
|
const sample = JSON.parse(fs.readFileSync(samplePath, 'utf-8'));
|
|
157
157
|
// Set a usable defaultPath
|
|
158
|
-
const defaultProjectDir = path.join(os.homedir(), '
|
|
158
|
+
const defaultProjectDir = path.join(os.homedir(), 'projects', 'default');
|
|
159
159
|
sample.projects.defaultPath = defaultProjectDir;
|
|
160
160
|
if (!fs.existsSync(defaultProjectDir)) {
|
|
161
161
|
fs.mkdirSync(defaultProjectDir, { recursive: true });
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { EvolAgent } from './evolagent.js';
|
|
4
|
+
import { validateEvolAgentConfig } from './evolagent-schema.js';
|
|
5
|
+
import { extractFingerprint } from '../utils/channel-fingerprint.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
export class AgentRegistry {
|
|
8
|
+
agentsDir;
|
|
9
|
+
agents = new Map();
|
|
10
|
+
defaultAgent = null;
|
|
11
|
+
channelIndex = new Map();
|
|
12
|
+
constructor(agentsDir) {
|
|
13
|
+
this.agentsDir = agentsDir;
|
|
14
|
+
}
|
|
15
|
+
loadAll(globalConfig) {
|
|
16
|
+
this.agents.clear();
|
|
17
|
+
this.channelIndex.clear();
|
|
18
|
+
const files = fs.existsSync(this.agentsDir)
|
|
19
|
+
? fs.readdirSync(this.agentsDir).filter(f => f.endsWith('.json'))
|
|
20
|
+
: [];
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
const fullPath = path.join(this.agentsDir, file);
|
|
23
|
+
try {
|
|
24
|
+
const raw = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
25
|
+
const validation = validateEvolAgentConfig(raw);
|
|
26
|
+
if (!validation.valid) {
|
|
27
|
+
const name = raw?.name || path.basename(file, '.json');
|
|
28
|
+
const errorAgent = new EvolAgent(fullPath, { ...raw, name });
|
|
29
|
+
errorAgent.status = 'error';
|
|
30
|
+
errorAgent.error = validation.errors.join('; ');
|
|
31
|
+
this.agents.set(name, errorAgent);
|
|
32
|
+
logger.warn(`[AgentRegistry] ${file}: ${validation.errors.join('; ')}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const agent = new EvolAgent(fullPath, raw);
|
|
36
|
+
this.agents.set(agent.name, agent);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
logger.warn(`[AgentRegistry] Failed to load ${file}: ${e}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.defaultAgent = this.buildDefaultAgent(globalConfig);
|
|
43
|
+
this.detectAndFlagConflicts();
|
|
44
|
+
this.buildChannelIndex();
|
|
45
|
+
}
|
|
46
|
+
buildDefaultAgent(globalConfig) {
|
|
47
|
+
const agents = globalConfig.agents || {};
|
|
48
|
+
const defaultName = agents.defaultAgent || 'claude';
|
|
49
|
+
const cfg = {
|
|
50
|
+
name: '[default]',
|
|
51
|
+
enabled: true,
|
|
52
|
+
agents: { [defaultName]: agents[defaultName] || {} },
|
|
53
|
+
channels: globalConfig.channels || {},
|
|
54
|
+
projects: { defaultPath: globalConfig.projects?.defaultPath || process.cwd() },
|
|
55
|
+
chatmode: globalConfig.chatmode,
|
|
56
|
+
};
|
|
57
|
+
return new EvolAgent(null, cfg, { isDefault: true });
|
|
58
|
+
}
|
|
59
|
+
detectAndFlagConflicts() {
|
|
60
|
+
const seen = new Map();
|
|
61
|
+
const record = (agentName, channelsBlock) => {
|
|
62
|
+
for (const [type, raw] of Object.entries(channelsBlock || {})) {
|
|
63
|
+
if (type === 'defaultChannel')
|
|
64
|
+
continue;
|
|
65
|
+
const instances = Array.isArray(raw) ? raw : [raw];
|
|
66
|
+
for (const inst of instances) {
|
|
67
|
+
if (!inst || typeof inst !== 'object')
|
|
68
|
+
continue;
|
|
69
|
+
const fp = extractFingerprint(type, inst);
|
|
70
|
+
if (!fp)
|
|
71
|
+
continue;
|
|
72
|
+
const instName = inst.name ?? type;
|
|
73
|
+
const entry = seen.get(fp) || [];
|
|
74
|
+
entry.push({ agent: agentName, instance: instName });
|
|
75
|
+
seen.set(fp, entry);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
for (const agent of this.agents.values()) {
|
|
80
|
+
if (agent.status === 'error')
|
|
81
|
+
continue;
|
|
82
|
+
record(agent.name, agent.config.channels);
|
|
83
|
+
}
|
|
84
|
+
if (this.defaultAgent) {
|
|
85
|
+
record(this.defaultAgent.name, this.defaultAgent.config.channels);
|
|
86
|
+
}
|
|
87
|
+
for (const [_fp, occurrences] of seen) {
|
|
88
|
+
if (occurrences.length <= 1)
|
|
89
|
+
continue;
|
|
90
|
+
const msg = `Channel conflict: fingerprint claimed by ${occurrences.map(o => `${o.agent}(${o.instance})`).join(', ')}`;
|
|
91
|
+
const involvedNames = [...new Set(occurrences.map(o => o.agent))];
|
|
92
|
+
for (const name of involvedNames) {
|
|
93
|
+
if (name === '[default]')
|
|
94
|
+
continue;
|
|
95
|
+
const a = this.agents.get(name);
|
|
96
|
+
if (a && a.status !== 'error') {
|
|
97
|
+
a.status = 'error';
|
|
98
|
+
a.error = msg;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
logger.error(`[AgentRegistry] ${msg}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
buildChannelIndex() {
|
|
105
|
+
for (const agent of this.agents.values()) {
|
|
106
|
+
if (agent.status === 'error' || agent.status === 'disabled')
|
|
107
|
+
continue;
|
|
108
|
+
for (const name of agent.channelInstanceNames()) {
|
|
109
|
+
this.channelIndex.set(name, agent.name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (this.defaultAgent) {
|
|
113
|
+
for (const name of this.defaultAgent.channelInstanceNames()) {
|
|
114
|
+
if (this.channelIndex.has(name))
|
|
115
|
+
continue;
|
|
116
|
+
this.channelIndex.set(name, '[default]');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
resolveByChannel(channelName) {
|
|
121
|
+
const agentName = this.channelIndex.get(channelName);
|
|
122
|
+
if (!agentName)
|
|
123
|
+
return null;
|
|
124
|
+
if (agentName === '[default]')
|
|
125
|
+
return this.defaultAgent;
|
|
126
|
+
return this.agents.get(agentName) || null;
|
|
127
|
+
}
|
|
128
|
+
get(name) {
|
|
129
|
+
if (name === '[default]')
|
|
130
|
+
return this.defaultAgent;
|
|
131
|
+
return this.agents.get(name) || null;
|
|
132
|
+
}
|
|
133
|
+
list() {
|
|
134
|
+
const result = [];
|
|
135
|
+
for (const agent of this.agents.values()) {
|
|
136
|
+
result.push(this.toInfo(agent));
|
|
137
|
+
}
|
|
138
|
+
if (this.defaultAgent) {
|
|
139
|
+
result.push(this.toInfo(this.defaultAgent));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
runnableAgents() {
|
|
144
|
+
return [...this.agents.values()].filter(a => a.status === 'stopped');
|
|
145
|
+
}
|
|
146
|
+
toInfo(agent) {
|
|
147
|
+
let baseagent = 'claude';
|
|
148
|
+
try {
|
|
149
|
+
baseagent = agent.baseagent;
|
|
150
|
+
}
|
|
151
|
+
catch { /* invalid config */ }
|
|
152
|
+
return {
|
|
153
|
+
name: agent.name,
|
|
154
|
+
status: agent.status,
|
|
155
|
+
channels: agent.channelInstanceNames(),
|
|
156
|
+
projectPath: agent.config.projects?.defaultPath ?? '',
|
|
157
|
+
baseagent,
|
|
158
|
+
lastActivity: agent.lastActivity,
|
|
159
|
+
activeSessions: agent.activeSessions,
|
|
160
|
+
error: agent.error,
|
|
161
|
+
isDefault: agent.isDefault,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|