evolclaw 2.8.1 → 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 +1 -1
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/cli.js +345 -48
- package/dist/config.js +93 -21
- package/dist/core/agent-registry.js +287 -1
- package/dist/core/command-handler.js +370 -160
- package/dist/core/evolagent-registry.js +503 -0
- package/dist/core/evolagent.js +250 -1
- package/dist/core/message/message-bridge.js +23 -3
- package/dist/core/message/message-processor.js +39 -4
- package/dist/core/message/message-queue.js +59 -4
- package/dist/core/reload-hooks.js +87 -0
- package/dist/index.js +119 -8
- package/dist/ipc.js +47 -0
- package/dist/types.js +2 -0
- 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/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getPackageRoot, resolveRoot } from '../paths.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
const KNOWN_SECTIONS = new Set(['runtime', 'group', 'proactive']);
|
|
6
|
+
const SECTION_RE = /^##\s+(\w+)\s*$/;
|
|
7
|
+
let sections = null;
|
|
8
|
+
let builtinSections = null;
|
|
9
|
+
function parseTemplate(content) {
|
|
10
|
+
const result = new Map();
|
|
11
|
+
let currentSection = null;
|
|
12
|
+
let currentLines = [];
|
|
13
|
+
for (const line of content.split('\n')) {
|
|
14
|
+
// Stop parsing at horizontal rule separator (documentation follows)
|
|
15
|
+
if (/^---\s*$/.test(line)) {
|
|
16
|
+
if (currentSection) {
|
|
17
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
18
|
+
}
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const m = line.match(SECTION_RE);
|
|
22
|
+
if (m) {
|
|
23
|
+
if (currentSection) {
|
|
24
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
25
|
+
}
|
|
26
|
+
const name = m[1];
|
|
27
|
+
if (KNOWN_SECTIONS.has(name)) {
|
|
28
|
+
currentSection = name;
|
|
29
|
+
currentLines = [];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
currentSection = null;
|
|
33
|
+
currentLines = [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (currentSection) {
|
|
37
|
+
currentLines.push(line);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (currentSection) {
|
|
41
|
+
result.set(currentSection, currentLines.join('\n').trim());
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
function loadBuiltinTemplate() {
|
|
46
|
+
const builtinPath = path.join(getPackageRoot(), 'dist', 'templates', 'prompts.md');
|
|
47
|
+
const srcPath = path.join(getPackageRoot(), 'src', 'templates', 'prompts.md');
|
|
48
|
+
const filePath = fs.existsSync(builtinPath) ? builtinPath : srcPath;
|
|
49
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
50
|
+
return parseTemplate(content);
|
|
51
|
+
}
|
|
52
|
+
export function loadPromptTemplates() {
|
|
53
|
+
builtinSections = loadBuiltinTemplate();
|
|
54
|
+
const userPath = path.join(resolveRoot(), 'data', 'prompts.md');
|
|
55
|
+
if (fs.existsSync(userPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const content = fs.readFileSync(userPath, 'utf-8');
|
|
58
|
+
const parsed = parseTemplate(content);
|
|
59
|
+
sections = new Map(builtinSections);
|
|
60
|
+
for (const [key, value] of parsed) {
|
|
61
|
+
sections.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
logger.info(`[PromptTemplates] Loaded user override: ${userPath}`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
logger.warn(`[PromptTemplates] Failed to load user override (${userPath}), using builtin:`, err);
|
|
67
|
+
sections = builtinSections;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
sections = builtinSections;
|
|
72
|
+
logger.info(`[PromptTemplates] Using builtin templates`);
|
|
73
|
+
}
|
|
74
|
+
for (const name of KNOWN_SECTIONS) {
|
|
75
|
+
if (!sections.has(name)) {
|
|
76
|
+
logger.warn(`[PromptTemplates] Section "${name}" missing, using builtin fallback`);
|
|
77
|
+
const fallback = builtinSections.get(name);
|
|
78
|
+
if (fallback)
|
|
79
|
+
sections.set(name, fallback);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isTruthy(val) {
|
|
84
|
+
if (val === undefined || val === null || val === false || val === '' || val === 0)
|
|
85
|
+
return false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
function renderTemplate(template, vars) {
|
|
89
|
+
// Pass 1: conditional sections {{?key}}...{{/}}
|
|
90
|
+
let result = template.replace(/\{\{\?(\w+)\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, body) => {
|
|
91
|
+
return isTruthy(vars[key]) ? body : '';
|
|
92
|
+
});
|
|
93
|
+
// Pass 2: variable substitution {{key}}
|
|
94
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
95
|
+
const val = vars[key];
|
|
96
|
+
if (!isTruthy(val))
|
|
97
|
+
return '';
|
|
98
|
+
return String(val);
|
|
99
|
+
});
|
|
100
|
+
// Pass 3: remove blank lines
|
|
101
|
+
return result.split('\n').filter(line => line.trim() !== '').join('\n');
|
|
102
|
+
}
|
|
103
|
+
export function renderPromptSection(section, vars) {
|
|
104
|
+
if (!sections)
|
|
105
|
+
loadPromptTemplates();
|
|
106
|
+
const template = sections.get(section);
|
|
107
|
+
if (!template) {
|
|
108
|
+
logger.warn(`[PromptTemplates] Section "${section}" not found`);
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
return renderTemplate(template, vars);
|
|
112
|
+
}
|
|
113
|
+
/** Reset loaded templates (for testing) */
|
|
114
|
+
export function _resetTemplates() {
|
|
115
|
+
sections = null;
|
|
116
|
+
builtinSections = null;
|
|
117
|
+
}
|
|
118
|
+
/** Load templates from a raw string (for testing) */
|
|
119
|
+
export function _loadFromString(content) {
|
|
120
|
+
builtinSections = parseTemplate(content);
|
|
121
|
+
sections = builtinSections;
|
|
122
|
+
}
|
package/dist/channels/aun-ops.js
CHANGED
|
@@ -12,7 +12,7 @@ import { execFileSync } from 'child_process';
|
|
|
12
12
|
import { isWindows } from '../utils/cross-platform.js';
|
|
13
13
|
import { resolvePaths } from '../paths.js';
|
|
14
14
|
// ==================== Constants ====================
|
|
15
|
-
export const MIN_AUN_CORE_SDK = [0, 2,
|
|
15
|
+
export const MIN_AUN_CORE_SDK = [0, 2, 17];
|
|
16
16
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
17
17
|
// ==================== SDK & Environment ====================
|
|
18
18
|
function compareVersion(a, min) {
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from '../utils/logger.js';
|
|
2
|
-
import { markdownToPlainText } from '../utils/
|
|
2
|
+
import { markdownToPlainText } from '../utils/rich-content-renderer.js';
|
|
3
3
|
import { requireOptional } from '../utils/init-channel.js';
|
|
4
4
|
import { normalizeChannelInstances, getChannelShowActivities } from '../config.js';
|
|
5
5
|
// ── QQBotChannel ────────────────────────────────────────────────────────────
|
package/dist/channels/wechat.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { resolvePaths } from '../paths.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
6
|
import { sanitizeFileName, saveToUploads, safeFetch } from '../utils/media-cache.js';
|
|
7
|
-
import { markdownToPlainText } from '../utils/
|
|
7
|
+
import { markdownToPlainText } from '../utils/rich-content-renderer.js';
|
|
8
8
|
const CHANNEL_VERSION = '1.0.0';
|
|
9
9
|
const ILINK_APP_ID = 'bot';
|
|
10
10
|
// iLink-App-ClientVersion: major<<16 | minor<<8 | patch (uint32)
|
package/dist/cli.js
CHANGED
|
@@ -68,13 +68,11 @@ function rotateLogs(logDir) {
|
|
|
68
68
|
function countLines(pkgRoot, logDir) {
|
|
69
69
|
const srcDir = path.join(pkgRoot, 'src');
|
|
70
70
|
const statsFile = path.join(logDir, 'line-stats.log');
|
|
71
|
-
const countDir = (dir
|
|
71
|
+
const countDir = (dir) => {
|
|
72
72
|
if (!fs.existsSync(dir))
|
|
73
73
|
return 0;
|
|
74
74
|
let total = 0;
|
|
75
75
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
76
|
-
if (exclude && entry.name === exclude)
|
|
77
|
-
continue;
|
|
78
76
|
const full = path.join(dir, entry.name);
|
|
79
77
|
if (entry.isDirectory()) {
|
|
80
78
|
total += countDir(full);
|
|
@@ -93,12 +91,14 @@ function countLines(pkgRoot, logDir) {
|
|
|
93
91
|
console.log('\n[launcher] 正在统计代码行数...\n');
|
|
94
92
|
const core = countDir(path.join(srcDir, 'core'));
|
|
95
93
|
const agents = countDir(path.join(srcDir, 'agents'));
|
|
96
|
-
const channels = countDir(path.join(srcDir, 'channels')
|
|
94
|
+
const channels = countDir(path.join(srcDir, 'channels'));
|
|
97
95
|
const utils = countDir(path.join(srcDir, 'utils'));
|
|
98
96
|
const entry = countFile(path.join(srcDir, 'index.ts'))
|
|
99
97
|
+ countFile(path.join(srcDir, 'config.ts'))
|
|
100
98
|
+ countFile(path.join(srcDir, 'types.ts'))
|
|
101
|
-
+ 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'));
|
|
102
102
|
const total = core + agents + channels + utils + entry;
|
|
103
103
|
console.log('==================================================');
|
|
104
104
|
console.log('EvolClaw 代码统计');
|
|
@@ -607,20 +607,26 @@ async function cmdStatus() {
|
|
|
607
607
|
showConfigChannels(config);
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
+
}
|
|
624
630
|
}
|
|
625
631
|
}
|
|
626
632
|
// Log line pattern: [timestamp] [LEVEL] [Module?] message
|
|
@@ -1388,11 +1394,18 @@ async function cmdAgent(args) {
|
|
|
1388
1394
|
}
|
|
1389
1395
|
if (sub === 'new') {
|
|
1390
1396
|
const name = args[1];
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
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);
|
|
1394
1408
|
}
|
|
1395
|
-
await cmdAgentNew(name);
|
|
1396
1409
|
return;
|
|
1397
1410
|
}
|
|
1398
1411
|
if (sub === 'reload') {
|
|
@@ -1401,16 +1414,42 @@ async function cmdAgent(args) {
|
|
|
1401
1414
|
console.error('Usage: evolclaw agent reload <name>');
|
|
1402
1415
|
process.exit(1);
|
|
1403
1416
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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;
|
|
1407
1434
|
}
|
|
1408
1435
|
// `evolclaw agent <name>` — show detail
|
|
1409
1436
|
await cmdAgentShow(sub);
|
|
1410
1437
|
}
|
|
1411
1438
|
async function cmdAgentList() {
|
|
1412
1439
|
const p = resolvePaths();
|
|
1413
|
-
|
|
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');
|
|
1414
1453
|
const { loadConfig } = await import('./config.js');
|
|
1415
1454
|
let config;
|
|
1416
1455
|
try {
|
|
@@ -1419,22 +1458,26 @@ async function cmdAgentList() {
|
|
|
1419
1458
|
catch {
|
|
1420
1459
|
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1421
1460
|
}
|
|
1422
|
-
const registry = new
|
|
1461
|
+
const registry = new EvolAgentRegistry(p.agentsDir);
|
|
1423
1462
|
registry.loadAll(config);
|
|
1424
|
-
|
|
1463
|
+
printAgentTable(registry.list());
|
|
1464
|
+
}
|
|
1465
|
+
function printAgentTable(list) {
|
|
1425
1466
|
if (list.length === 0) {
|
|
1426
1467
|
console.log('No agents configured.');
|
|
1427
1468
|
return;
|
|
1428
1469
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1470
|
+
console.log('NAME'.padEnd(14) + 'STATUS'.padEnd(10) + 'CHANNELS'.padEnd(24) +
|
|
1471
|
+
'PROJECT'.padEnd(22) + 'BASEAGENT'.padEnd(11) + 'LAST ACTIVE');
|
|
1431
1472
|
for (const info of list) {
|
|
1432
1473
|
const name = info.isDefault ? '[default]' : info.name;
|
|
1433
|
-
const status = info.status;
|
|
1434
|
-
const channels = info.channels
|
|
1474
|
+
const status = info.status || 'stopped';
|
|
1475
|
+
const channels = info.channels?.length > 0 ? info.channels.join(', ').slice(0, 22) : '—';
|
|
1435
1476
|
const project = info.projectPath ? path.basename(info.projectPath) : '—';
|
|
1436
1477
|
const baseagent = info.baseagent || '—';
|
|
1437
|
-
const lastActive = info.lastActivity
|
|
1478
|
+
const lastActive = info.lastActivity
|
|
1479
|
+
? formatTimeAgo(Date.now() - info.lastActivity)
|
|
1480
|
+
: '—';
|
|
1438
1481
|
console.log(name.padEnd(14) +
|
|
1439
1482
|
status.padEnd(10) +
|
|
1440
1483
|
channels.padEnd(24) +
|
|
@@ -1445,7 +1488,33 @@ async function cmdAgentList() {
|
|
|
1445
1488
|
}
|
|
1446
1489
|
async function cmdAgentShow(name) {
|
|
1447
1490
|
const p = resolvePaths();
|
|
1448
|
-
|
|
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');
|
|
1449
1518
|
const { loadConfig } = await import('./config.js');
|
|
1450
1519
|
let config;
|
|
1451
1520
|
try {
|
|
@@ -1454,7 +1523,7 @@ async function cmdAgentShow(name) {
|
|
|
1454
1523
|
catch {
|
|
1455
1524
|
config = { agents: {}, channels: {}, projects: { defaultPath: process.cwd() } };
|
|
1456
1525
|
}
|
|
1457
|
-
const registry = new
|
|
1526
|
+
const registry = new EvolAgentRegistry(p.agentsDir);
|
|
1458
1527
|
registry.loadAll(config);
|
|
1459
1528
|
const agent = registry.get(name);
|
|
1460
1529
|
if (!agent) {
|
|
@@ -1478,22 +1547,67 @@ async function cmdAgentShow(name) {
|
|
|
1478
1547
|
if (agent.configPath)
|
|
1479
1548
|
console.log(` Config: ${agent.configPath}`);
|
|
1480
1549
|
}
|
|
1481
|
-
async function cmdAgentNew(
|
|
1550
|
+
async function cmdAgentNew(suggestedName) {
|
|
1482
1551
|
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
1552
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1489
1553
|
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
1554
|
+
let name;
|
|
1490
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
|
+
}
|
|
1491
1575
|
console.log(`\nCreating agent: ${name}\n`);
|
|
1492
|
-
|
|
1493
|
-
|
|
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)) {
|
|
1494
1591
|
console.error('Project path must be an absolute path.');
|
|
1495
1592
|
process.exit(1);
|
|
1496
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
|
+
}
|
|
1497
1611
|
const baseagentChoices = ['claude', 'codex', 'gemini', 'hermes'];
|
|
1498
1612
|
const baseagent = (await ask(`Baseagent (${baseagentChoices.join('/')}) [claude]: `)).trim() || 'claude';
|
|
1499
1613
|
if (!baseagentChoices.includes(baseagent)) {
|
|
@@ -1503,7 +1617,19 @@ async function cmdAgentNew(name) {
|
|
|
1503
1617
|
const model = (await ask('Model (leave empty for default): ')).trim() || undefined;
|
|
1504
1618
|
const effort = (await ask('Effort (low/medium/high/max) [high]: ')).trim() || 'high';
|
|
1505
1619
|
const chatmodeChoices = ['interactive', 'proactive'];
|
|
1506
|
-
|
|
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
|
+
}
|
|
1507
1633
|
// Channels (loop to allow multiple)
|
|
1508
1634
|
const channelsConfig = {};
|
|
1509
1635
|
const { getChannelCredentialCollector } = await import('./utils/init-channel.js');
|
|
@@ -1538,9 +1664,13 @@ async function cmdAgentNew(name) {
|
|
|
1538
1664
|
continue;
|
|
1539
1665
|
}
|
|
1540
1666
|
const nameRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1541
|
-
const
|
|
1667
|
+
const defaultEffName = `${name}-${channelType}`;
|
|
1668
|
+
const instName = await new Promise(r => nameRl.question(` Channel instance name (leave empty for ${defaultEffName}): `, r));
|
|
1542
1669
|
nameRl.close();
|
|
1543
|
-
|
|
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
|
|
1544
1674
|
if (!channelsConfig[channelType])
|
|
1545
1675
|
channelsConfig[channelType] = [];
|
|
1546
1676
|
channelsConfig[channelType].push(creds);
|
|
@@ -1556,7 +1686,7 @@ async function cmdAgentNew(name) {
|
|
|
1556
1686
|
agents: { [baseagent]: { ...(model && { model }), effort } },
|
|
1557
1687
|
channels: finalChannels,
|
|
1558
1688
|
projects: { defaultPath: projectPath.trim() },
|
|
1559
|
-
chatmode: { private: chatmodePrivate, group:
|
|
1689
|
+
chatmode: { private: chatmodePrivate, group: chatmodeGroup },
|
|
1560
1690
|
};
|
|
1561
1691
|
fs.mkdirSync(p.agentsDir, { recursive: true });
|
|
1562
1692
|
fs.writeFileSync(agentPath, JSON.stringify(agentConfig, null, 2));
|
|
@@ -1571,6 +1701,160 @@ async function cmdAgentNew(name) {
|
|
|
1571
1701
|
catch { }
|
|
1572
1702
|
}
|
|
1573
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
|
+
}
|
|
1574
1858
|
// ==================== AID ====================
|
|
1575
1859
|
async function cmdAid(args) {
|
|
1576
1860
|
const sub = args[0] || 'list';
|
|
@@ -1856,6 +2140,19 @@ Commands:
|
|
|
1856
2140
|
agent 列出所有 agent
|
|
1857
2141
|
agent <name> 查看指定 agent 详情
|
|
1858
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)
|
|
1859
2156
|
agent reload <n> 热重载 agent 配置
|
|
1860
2157
|
aid AID 身份管理
|
|
1861
2158
|
aid list 列出本地所有 AID
|