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/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') || process.cwd(),
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(), 'evolclaw-project');
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
+ }