evolclaw 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/data/evolclaw.sample.json +9 -1
- package/dist/agents/claude-runner.js +612 -0
- package/dist/agents/codex-runner.js +310 -0
- package/dist/channels/aun.js +416 -9
- package/dist/channels/feishu.js +397 -104
- package/dist/channels/wechat.js +84 -2
- package/dist/cli.js +427 -126
- package/dist/config.js +102 -4
- package/dist/core/adapters/claude-session-file-adapter.js +144 -0
- package/dist/core/adapters/codex-session-file-adapter.js +196 -0
- package/dist/core/agent-loader.js +39 -0
- package/dist/core/channel-loader.js +60 -0
- package/dist/core/command-handler.js +908 -304
- package/dist/core/event-bus.js +32 -0
- package/dist/core/ipc-server.js +71 -0
- package/dist/core/message-bridge.js +187 -0
- package/dist/core/message-processor.js +370 -227
- package/dist/core/message-queue.js +153 -29
- package/dist/core/permission.js +58 -0
- package/dist/core/session-file-adapter.js +7 -0
- package/dist/core/session-manager.js +567 -205
- package/dist/core/stats-collector.js +86 -0
- package/dist/index.js +309 -243
- package/dist/paths.js +1 -0
- package/dist/utils/init-feishu.js +2 -0
- package/dist/utils/init-wechat.js +2 -0
- package/dist/utils/init.js +285 -53
- package/dist/utils/ipc-client.js +36 -0
- package/dist/utils/migrate-project.js +122 -0
- package/dist/utils/{permission.js → permission-utils.js} +31 -3
- package/dist/utils/rich-content-renderer.js +228 -0
- package/dist/utils/session-file-health.js +11 -34
- package/dist/utils/stream-debouncer.js +122 -0
- package/dist/utils/stream-idle-monitor.js +1 -1
- package/package.json +3 -1
- package/dist/core/agent-runner.js +0 -348
- package/dist/core/message-stream.js +0 -59
- package/dist/index.js.bak +0 -340
- package/dist/utils/markdown-to-feishu.js +0 -94
- /package/dist/utils/{platform.js → cross-platform.js} +0 -0
- /package/dist/{core → utils}/message-cache.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -4,10 +4,15 @@ import path from 'path';
|
|
|
4
4
|
import { spawn, execFile } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from './paths.js';
|
|
7
|
-
import {
|
|
7
|
+
import { loadConfig, validateConfigIntegrity, resolveAnthropicConfig } from './config.js';
|
|
8
|
+
import { migrateProject } from './utils/migrate-project.js';
|
|
9
|
+
import readline from 'readline';
|
|
10
|
+
import { cmdInit, cmdInitAun, checkAunEnvironment } from './utils/init.js';
|
|
11
|
+
import { ipcQuery } from './utils/ipc-client.js';
|
|
8
12
|
import { cmdInitWechat } from './utils/init-wechat.js';
|
|
9
13
|
import { cmdInitFeishu } from './utils/init-feishu.js';
|
|
10
|
-
import * as platform from './utils/platform.js';
|
|
14
|
+
import * as platform from './utils/cross-platform.js';
|
|
15
|
+
import { EventBus } from './core/event-bus.js';
|
|
11
16
|
// Suppress Node.js ExperimentalWarning (e.g. SQLite) from cluttering CLI output
|
|
12
17
|
process.removeAllListeners('warning');
|
|
13
18
|
process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
|
|
@@ -86,17 +91,19 @@ function countLines(pkgRoot, logDir) {
|
|
|
86
91
|
};
|
|
87
92
|
console.log('\n[launcher] 正在统计代码行数...\n');
|
|
88
93
|
const core = countDir(path.join(srcDir, 'core'));
|
|
94
|
+
const agents = countDir(path.join(srcDir, 'agents'));
|
|
89
95
|
const channels = countDir(path.join(srcDir, 'channels'), 'experimental');
|
|
90
96
|
const utils = countDir(path.join(srcDir, 'utils'));
|
|
91
97
|
const entry = countFile(path.join(srcDir, 'index.ts'))
|
|
92
98
|
+ countFile(path.join(srcDir, 'config.ts'))
|
|
93
99
|
+ countFile(path.join(srcDir, 'types.ts'))
|
|
94
100
|
+ countFile(path.join(srcDir, 'cli.ts'));
|
|
95
|
-
const total = core + channels + utils + entry;
|
|
101
|
+
const total = core + agents + channels + utils + entry;
|
|
96
102
|
console.log('==================================================');
|
|
97
103
|
console.log('EvolClaw 代码统计');
|
|
98
104
|
console.log('==================================================');
|
|
99
105
|
console.log(`核心模块: ${String(core).padStart(8)} 行`);
|
|
106
|
+
console.log(`Agent 模块: ${String(agents).padStart(8)} 行`);
|
|
100
107
|
console.log(`渠道适配: ${String(channels).padStart(8)} 行`);
|
|
101
108
|
console.log(`工具库: ${String(utils).padStart(8)} 行`);
|
|
102
109
|
console.log(`入口与配置: ${String(entry).padStart(8)} 行`);
|
|
@@ -117,7 +124,7 @@ function countLines(pkgRoot, logDir) {
|
|
|
117
124
|
}
|
|
118
125
|
if (shouldAppend) {
|
|
119
126
|
const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
120
|
-
fs.appendFileSync(statsFile, `${now}\t${core}\t${channels}\t${utils}\t${entry}\t${total}\n`);
|
|
127
|
+
fs.appendFileSync(statsFile, `${now}\t${core}\t${agents}\t${channels}\t${utils}\t${entry}\t${total}\n`);
|
|
121
128
|
}
|
|
122
129
|
showHistory(statsFile);
|
|
123
130
|
}
|
|
@@ -131,21 +138,30 @@ function showHistory(statsFile) {
|
|
|
131
138
|
console.log('\n==================================================');
|
|
132
139
|
console.log('历史记录(最近 8 次)');
|
|
133
140
|
console.log('==================================================');
|
|
134
|
-
console.log(`${'时间'.padEnd(20)} ${'核心'.padStart(6)} ${'渠道'.padStart(6)} ${'工具'.padStart(6)} ${'入口'.padStart(6)} ${'总计'.padStart(6)} ${'变化'.padStart(8)}`);
|
|
141
|
+
console.log(`${'时间'.padEnd(20)} ${'核心'.padStart(6)} ${'Agent'.padStart(6)} ${'渠道'.padStart(6)} ${'工具'.padStart(6)} ${'入口'.padStart(6)} ${'总计'.padStart(6)} ${'变化'.padStart(8)}`);
|
|
135
142
|
console.log('--------------------------------------------------');
|
|
136
143
|
let prevTotal = null;
|
|
137
144
|
for (const line of recent) {
|
|
138
145
|
const parts = line.split('\t');
|
|
139
|
-
|
|
146
|
+
// 兼容旧格式(6列: time,core,ch,utils,entry,total)和新格式(7列: +agents)
|
|
147
|
+
let time, c, a, ch, u, e, t;
|
|
148
|
+
if (parts.length >= 7) {
|
|
149
|
+
[time, c, a, ch, u, e, t] = parts;
|
|
150
|
+
}
|
|
151
|
+
else if (parts.length >= 6) {
|
|
152
|
+
[time, c, ch, u, e, t] = parts;
|
|
153
|
+
a = '-';
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
140
156
|
continue;
|
|
141
|
-
|
|
157
|
+
}
|
|
142
158
|
const total = parseInt(t, 10);
|
|
143
159
|
let diff = '-';
|
|
144
160
|
if (prevTotal !== null) {
|
|
145
161
|
const change = total - prevTotal;
|
|
146
162
|
diff = change >= 0 ? `+${change}` : `${change}`;
|
|
147
163
|
}
|
|
148
|
-
console.log(`${time.padEnd(20)} ${c.padStart(6)} ${ch.padStart(6)} ${u.padStart(6)} ${e.padStart(6)} ${t.padStart(6)} ${diff.padStart(8)}`);
|
|
164
|
+
console.log(`${time.padEnd(20)} ${c.padStart(6)} ${a.padStart(6)} ${ch.padStart(6)} ${u.padStart(6)} ${e.padStart(6)} ${t.padStart(6)} ${diff.padStart(8)}`);
|
|
149
165
|
prevTotal = total;
|
|
150
166
|
}
|
|
151
167
|
console.log('==================================================');
|
|
@@ -159,6 +175,23 @@ async function cmdStart() {
|
|
|
159
175
|
console.log('❌ 配置文件不存在,请先运行 evolclaw init');
|
|
160
176
|
process.exit(1);
|
|
161
177
|
}
|
|
178
|
+
// 配置完整性校验
|
|
179
|
+
try {
|
|
180
|
+
const config = loadConfig(p.config);
|
|
181
|
+
const integrity = validateConfigIntegrity(config);
|
|
182
|
+
if (!integrity.valid) {
|
|
183
|
+
console.log(`❌ 配置文件完整性校验失败:`);
|
|
184
|
+
for (const reason of integrity.reasons) {
|
|
185
|
+
console.log(` - ${reason}`);
|
|
186
|
+
}
|
|
187
|
+
console.log(`\n配置文件: ${p.config}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
console.log(`❌ 配置文件加载失败: ${e.message}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
162
195
|
// 检查 PID 文件
|
|
163
196
|
const pid = isRunning(p.pid);
|
|
164
197
|
if (pid) {
|
|
@@ -168,7 +201,8 @@ async function cmdStart() {
|
|
|
168
201
|
}
|
|
169
202
|
// 检查是否有残留进程(PID 文件已丢失但进程还在)
|
|
170
203
|
let hasOrphan = false;
|
|
171
|
-
const
|
|
204
|
+
const evolclawMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
205
|
+
const orphanPids = platform.findProcesses(evolclawMain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
172
206
|
if (orphanPids.length > 0) {
|
|
173
207
|
console.log(`⚠ 发现 ${orphanPids.length} 个残留进程,正在清理...`);
|
|
174
208
|
for (const p of orphanPids) {
|
|
@@ -204,7 +238,7 @@ async function cmdStart() {
|
|
|
204
238
|
});
|
|
205
239
|
fs.writeFileSync(p.pid, String(child.pid));
|
|
206
240
|
child.unref();
|
|
207
|
-
// 等待 ready signal(最多
|
|
241
|
+
// 等待 ready signal(最多 30 秒,AUN sidecar 超时 15s + 其他通道连接)
|
|
208
242
|
const startTime = Date.now();
|
|
209
243
|
const checkReady = () => {
|
|
210
244
|
// ready signal 出现(优先检查,避免 Windows 上 isRunning 误判)
|
|
@@ -213,6 +247,41 @@ async function cmdStart() {
|
|
|
213
247
|
console.log(`✓ EvolClaw started successfully (PID: ${pid})`);
|
|
214
248
|
console.log(` EVOLCLAW_HOME: ${resolveRoot()}`);
|
|
215
249
|
console.log(` Logs: ${p.logs}/`);
|
|
250
|
+
// 从主日志提取渠道连接摘要
|
|
251
|
+
const mainLog = path.join(p.logs, 'evolclaw.log');
|
|
252
|
+
if (fs.existsSync(mainLog)) {
|
|
253
|
+
const logLines = fs.readFileSync(mainLog, 'utf-8').split('\n');
|
|
254
|
+
// 从末尾往前找最近一次启动的摘要
|
|
255
|
+
let channelSummary = '';
|
|
256
|
+
for (let i = logLines.length - 1; i >= 0; i--) {
|
|
257
|
+
if (logLines[i].includes('EvolClaw is running with')) {
|
|
258
|
+
channelSummary = logLines[i];
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (channelSummary) {
|
|
263
|
+
const match = channelSummary.match(/running with .+/);
|
|
264
|
+
if (match)
|
|
265
|
+
console.log(` ${match[0]}`);
|
|
266
|
+
}
|
|
267
|
+
// 最近一次启动的失败信息
|
|
268
|
+
let lastReadyIdx = -1;
|
|
269
|
+
for (let i = logLines.length - 1; i >= 0; i--) {
|
|
270
|
+
if (logLines[i].includes('Ready signal written')) {
|
|
271
|
+
lastReadyIdx = i;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (lastReadyIdx > 0) {
|
|
276
|
+
for (let i = Math.max(0, lastReadyIdx - 20); i < lastReadyIdx; i++) {
|
|
277
|
+
const line = logLines[i];
|
|
278
|
+
if (line.includes('failed to connect') || line.includes('Failed to create channel')) {
|
|
279
|
+
const match = line.match(/\[WARN\]\s*(.+)/);
|
|
280
|
+
console.log(` ⚠ ${match ? match[1] : line.trim()}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
216
285
|
console.log('');
|
|
217
286
|
// 代码统计仅在开发环境显示(EVOLCLAW_HOME 指向包目录)
|
|
218
287
|
if (resolveRoot() === getPackageRoot()) {
|
|
@@ -221,7 +290,7 @@ async function cmdStart() {
|
|
|
221
290
|
return;
|
|
222
291
|
}
|
|
223
292
|
// 超时
|
|
224
|
-
if (Date.now() - startTime >
|
|
293
|
+
if (Date.now() - startTime > 30000) {
|
|
225
294
|
console.log('❌ Failed to start EvolClaw (ready signal timeout)');
|
|
226
295
|
console.log('');
|
|
227
296
|
console.log('📝 Error details (last 10 lines of stdout):');
|
|
@@ -302,6 +371,43 @@ async function cmdRestart() {
|
|
|
302
371
|
await stopAndWait(p.pid);
|
|
303
372
|
setTimeout(() => cmdStart(), 1000);
|
|
304
373
|
}
|
|
374
|
+
function formatTimeAgo(ms) {
|
|
375
|
+
const sec = Math.floor(ms / 1000);
|
|
376
|
+
if (sec < 60)
|
|
377
|
+
return '刚刚';
|
|
378
|
+
const min = Math.floor(sec / 60);
|
|
379
|
+
if (min < 60)
|
|
380
|
+
return `${min}分钟前`;
|
|
381
|
+
const hour = Math.floor(min / 60);
|
|
382
|
+
if (hour < 24)
|
|
383
|
+
return `${hour}小时前`;
|
|
384
|
+
const day = Math.floor(hour / 24);
|
|
385
|
+
return `${day}天前`;
|
|
386
|
+
}
|
|
387
|
+
function showConfigChannels(config) {
|
|
388
|
+
// Feishu
|
|
389
|
+
if (config.channels?.feishu?.appId) {
|
|
390
|
+
console.log(` feishu: Configured (App ID: ${config.channels.feishu.appId.slice(0, 8)}...)`);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
console.log(' feishu: - Not configured');
|
|
394
|
+
}
|
|
395
|
+
// WeChat
|
|
396
|
+
if (config.channels?.wechat?.token) {
|
|
397
|
+
console.log(` wechat: Configured (Token: ${config.channels.wechat.token.slice(0, 20)}...)`);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
console.log(' wechat: - Not configured');
|
|
401
|
+
}
|
|
402
|
+
// AUN
|
|
403
|
+
const aunAid = config.channels?.aun?.aid;
|
|
404
|
+
if (aunAid && !aunAid.includes('your-') && !aunAid.includes('placeholder')) {
|
|
405
|
+
console.log(` aun: Configured (${aunAid})`);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
console.log(' aun: - Not configured');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
305
411
|
async function cmdStatus() {
|
|
306
412
|
const p = resolvePaths();
|
|
307
413
|
const pid = isRunning(p.pid);
|
|
@@ -320,6 +426,39 @@ async function cmdStatus() {
|
|
|
320
426
|
}
|
|
321
427
|
catch { }
|
|
322
428
|
console.log(` EVOLCLAW_HOME: ${resolveRoot()}`);
|
|
429
|
+
// Runtime statistics (only when running)
|
|
430
|
+
if (fs.existsSync(p.db)) {
|
|
431
|
+
try {
|
|
432
|
+
const Database = await import('node:sqlite');
|
|
433
|
+
const db = new Database.DatabaseSync(p.db);
|
|
434
|
+
// Get recent active sessions (last 5)
|
|
435
|
+
const recentSessions = db.prepare(`
|
|
436
|
+
SELECT id, project_path, name, channel, chat_type, thread_id, agent_session_id, agent_id, metadata, updated_at
|
|
437
|
+
FROM sessions
|
|
438
|
+
WHERE deleted_at IS NULL
|
|
439
|
+
ORDER BY updated_at DESC
|
|
440
|
+
LIMIT 5
|
|
441
|
+
`).all();
|
|
442
|
+
db.close();
|
|
443
|
+
if (recentSessions.length > 0) {
|
|
444
|
+
console.log('');
|
|
445
|
+
console.log('📋 Recent Active Sessions:');
|
|
446
|
+
for (const s of recentSessions) {
|
|
447
|
+
const projectName = path.basename(s.project_path);
|
|
448
|
+
const sessionType = s.thread_id ? '话题会话' : '主会话';
|
|
449
|
+
const chatType = s.chat_type === 'group' ? '群聊' : '单聊';
|
|
450
|
+
const sessionName = s.name || '默认会话';
|
|
451
|
+
const timeAgo = formatTimeAgo(Date.now() - s.updated_at);
|
|
452
|
+
const meta = s.metadata ? JSON.parse(s.metadata) : {};
|
|
453
|
+
const dot = meta.isActive ? '•' : '○';
|
|
454
|
+
const agentId = s.agent_session_id ? ` [${s.agent_session_id}]` : '';
|
|
455
|
+
const agentType = s.agent_id || 'claude';
|
|
456
|
+
console.log(` ${dot} [${agentType}] ${projectName} / ${sessionName} (${sessionType}, ${chatType})${agentId} - ${timeAgo}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch { }
|
|
461
|
+
}
|
|
323
462
|
}
|
|
324
463
|
else {
|
|
325
464
|
console.log('⚠ EvolClaw is not running');
|
|
@@ -327,16 +466,17 @@ async function cmdStatus() {
|
|
|
327
466
|
console.log(` Stale PID file found: ${p.pid}`);
|
|
328
467
|
}
|
|
329
468
|
}
|
|
469
|
+
// Session & Project statistics (always show if DB exists)
|
|
330
470
|
if (fs.existsSync(p.db)) {
|
|
331
471
|
console.log('');
|
|
332
472
|
console.log('📦 Sessions & Projects:');
|
|
333
473
|
try {
|
|
334
474
|
const Database = await import('node:sqlite');
|
|
335
475
|
const db = new Database.DatabaseSync(p.db);
|
|
336
|
-
const totalSessions = db.prepare('SELECT count(*) as cnt FROM sessions').get();
|
|
337
|
-
const activeSessions = db.prepare(
|
|
338
|
-
const uniqueChats = db.prepare('SELECT count(DISTINCT channel_id) as cnt FROM sessions').get();
|
|
339
|
-
const projects = db.prepare('SELECT count(DISTINCT project_path) as cnt FROM sessions').get();
|
|
476
|
+
const totalSessions = db.prepare('SELECT count(*) as cnt FROM sessions WHERE deleted_at IS NULL').get();
|
|
477
|
+
const activeSessions = db.prepare("SELECT count(*) as cnt FROM sessions WHERE json_extract(metadata, '$.isActive') = true AND deleted_at IS NULL").get();
|
|
478
|
+
const uniqueChats = db.prepare('SELECT count(DISTINCT channel_id) as cnt FROM sessions WHERE deleted_at IS NULL').get();
|
|
479
|
+
const projects = db.prepare('SELECT count(DISTINCT project_path) as cnt FROM sessions WHERE deleted_at IS NULL').get();
|
|
340
480
|
db.close();
|
|
341
481
|
console.log(` Total sessions: ${totalSessions.cnt} (active: ${activeSessions.cnt})`);
|
|
342
482
|
console.log(` Unique chats: ${uniqueChats.cnt}`);
|
|
@@ -344,102 +484,43 @@ async function cmdStatus() {
|
|
|
344
484
|
}
|
|
345
485
|
catch { }
|
|
346
486
|
}
|
|
347
|
-
// Channel
|
|
487
|
+
// Channel status
|
|
348
488
|
if (fs.existsSync(p.config)) {
|
|
349
489
|
console.log('');
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
console.log(` Feishu: ✗ Connection refused (${res.msg})`);
|
|
366
|
-
}
|
|
490
|
+
const config = JSON.parse(fs.readFileSync(p.config, 'utf-8'));
|
|
491
|
+
if (pid) {
|
|
492
|
+
// Running: query IPC for real-time status
|
|
493
|
+
const status = await ipcQuery(p.socket, { type: 'status' });
|
|
494
|
+
if (status) {
|
|
495
|
+
console.log('🔌 Channels (live):');
|
|
496
|
+
for (const [name, ch] of Object.entries(status.channels)) {
|
|
497
|
+
const label = ch.connected
|
|
498
|
+
? '✓ Connected'
|
|
499
|
+
: ch.reconnectAttempt
|
|
500
|
+
? `⏳ Reconnecting (${ch.reconnectAttempt}/${ch.maxAttempts})`
|
|
501
|
+
: '✗ Disconnected';
|
|
502
|
+
console.log(` ${name}: ${label}`);
|
|
367
503
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
504
|
+
if (status.stats) {
|
|
505
|
+
console.log('');
|
|
506
|
+
console.log('📊 Last hour:');
|
|
507
|
+
console.log(` Messages: ${status.stats.received} received, ${status.stats.completed} completed`);
|
|
508
|
+
if (status.stats.errors > 0)
|
|
509
|
+
console.log(` Errors: ${status.stats.errors}`);
|
|
510
|
+
if (status.stats.completed > 0)
|
|
511
|
+
console.log(` Avg response: ${(status.stats.avgResponseMs / 1000).toFixed(1)}s`);
|
|
376
512
|
}
|
|
377
513
|
}
|
|
378
514
|
else {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const tokenPreview = config.channels.wechat.token.slice(0, 20);
|
|
383
|
-
// Validate token by calling getconfig API
|
|
384
|
-
try {
|
|
385
|
-
const baseUrl = (config.channels.wechat.baseUrl || 'https://ilinkai.weixin.qq.com').replace(/\/$/, '');
|
|
386
|
-
const body = JSON.stringify({ base_info: { channel_version: '1.0.0' } });
|
|
387
|
-
const uint32 = (await import('node:crypto')).default.randomBytes(4).readUInt32BE(0);
|
|
388
|
-
const wechatUin = Buffer.from(String(uint32), 'utf-8').toString('base64');
|
|
389
|
-
const res = await fetch(`${baseUrl}/ilink/bot/getconfig`, {
|
|
390
|
-
method: 'POST',
|
|
391
|
-
headers: {
|
|
392
|
-
'Content-Type': 'application/json',
|
|
393
|
-
'AuthorizationType': 'ilink_bot_token',
|
|
394
|
-
'Authorization': `Bearer ${config.channels.wechat.token.trim()}`,
|
|
395
|
-
'X-WECHAT-UIN': wechatUin,
|
|
396
|
-
},
|
|
397
|
-
body,
|
|
398
|
-
signal: AbortSignal.timeout(10_000),
|
|
399
|
-
});
|
|
400
|
-
const resp = JSON.parse(await res.text());
|
|
401
|
-
const isExpired = resp.errcode === -14 || resp.ret === -14;
|
|
402
|
-
if (isExpired) {
|
|
403
|
-
console.log(` WeChat: ✗ Token expired (Token: ${tokenPreview}...)`);
|
|
404
|
-
console.log(' Run: evolclaw init wechat && evolclaw restart');
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
console.log(` WeChat: ✓ Connected (Token: ${tokenPreview}...)`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
catch (e) {
|
|
411
|
-
const msg = e.message || '';
|
|
412
|
-
if (msg.includes('ETIMEDOUT') || msg.includes('ENETUNREACH') || msg.includes('ENOTFOUND')) {
|
|
413
|
-
console.log(` WeChat: ✗ Connection timeout (Token: ${tokenPreview}...)`);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
console.log(` WeChat: ✓ Configured (Token: ${tokenPreview}...)`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
else {
|
|
421
|
-
console.log(' WeChat: - Not configured');
|
|
422
|
-
}
|
|
423
|
-
// Check AUN with placeholder detection
|
|
424
|
-
const aunDomain = config.channels?.aun?.domain;
|
|
425
|
-
const aunAgent = config.channels?.aun?.agentName;
|
|
426
|
-
const isAunPlaceholder = !aunDomain || !aunAgent ||
|
|
427
|
-
aunDomain.includes('your-') || aunDomain.includes('placeholder') ||
|
|
428
|
-
aunAgent.includes('your-') || aunAgent.includes('placeholder');
|
|
429
|
-
if (aunDomain && aunAgent && !isAunPlaceholder) {
|
|
430
|
-
console.log(` AUN: ✓ Configured (${aunAgent}@${aunDomain})`);
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
console.log(' AUN: - Not configured');
|
|
434
|
-
}
|
|
435
|
-
if (config.agents?.anthropic?.model) {
|
|
436
|
-
console.log(` Model: ${config.agents.anthropic.model}`);
|
|
437
|
-
}
|
|
438
|
-
if (config.projects?.defaultPath) {
|
|
439
|
-
console.log(` Default project: ${config.projects.defaultPath}`);
|
|
515
|
+
// IPC unreachable but PID exists — show config only
|
|
516
|
+
console.log('🔌 Channels (IPC unreachable):');
|
|
517
|
+
showConfigChannels(config);
|
|
440
518
|
}
|
|
441
519
|
}
|
|
442
|
-
|
|
520
|
+
else {
|
|
521
|
+
console.log('🔌 Channel Configuration:');
|
|
522
|
+
showConfigChannels(config);
|
|
523
|
+
}
|
|
443
524
|
}
|
|
444
525
|
console.log('');
|
|
445
526
|
console.log('📁 Log Files:');
|
|
@@ -483,11 +564,17 @@ async function cmdRestartMonitor() {
|
|
|
483
564
|
const p = resolvePaths();
|
|
484
565
|
const restartLog = path.join(p.logs, 'restart.log');
|
|
485
566
|
const MAX_HEAL_ATTEMPTS = 3;
|
|
486
|
-
const READY_TIMEOUT =
|
|
567
|
+
const READY_TIMEOUT = 30000; // 30s(AUN sidecar 10s + Feishu 连接 12s)
|
|
568
|
+
const HEAL_TIMEOUT = 30 * 60 * 1000; // 30 分钟,让 claude 自然结束
|
|
569
|
+
const eventBus = new EventBus();
|
|
487
570
|
const log = (msg) => {
|
|
488
571
|
const line = `[${new Date().toISOString().replace('T', ' ').slice(0, 19)}] ${msg}\n`;
|
|
489
572
|
fs.appendFileSync(restartLog, line);
|
|
490
573
|
};
|
|
574
|
+
/** 检查服务是否已经在运行(ready signal 存在 + 进程存活) */
|
|
575
|
+
const isServiceAlive = () => {
|
|
576
|
+
return fs.existsSync(p.readySignal) && isRunning(p.pid) !== null;
|
|
577
|
+
};
|
|
491
578
|
log('Restart monitor started');
|
|
492
579
|
// 读取 restart-pending.json 用于后续通知
|
|
493
580
|
const pendingFile = path.join(p.dataDir, 'restart-pending.json');
|
|
@@ -527,40 +614,90 @@ async function cmdRestartMonitor() {
|
|
|
527
614
|
if (started) {
|
|
528
615
|
log('✓ Service restarted successfully');
|
|
529
616
|
archiveSelfHealLog(p, log);
|
|
530
|
-
|
|
531
|
-
cleanupPendingFile(pendingFile, log);
|
|
617
|
+
// 通知由新进程自行发送(channel-agnostic),此处不再调用 notifyChannel
|
|
532
618
|
process.exit(0);
|
|
533
619
|
}
|
|
534
620
|
// 启动失败,进入 self-heal 循环
|
|
535
621
|
log('❌ Service failed to start, entering self-heal loop');
|
|
622
|
+
eventBus.publish({ type: 'self-heal:started', reason: 'Service failed to start after restart' });
|
|
536
623
|
await notifyChannel(p, pendingInfo, '⚠️ 服务启动失败,正在尝试自动修复...', log);
|
|
537
624
|
for (let attempt = 1; attempt <= MAX_HEAL_ATTEMPTS; attempt++) {
|
|
625
|
+
// 前置检查:服务可能已被上一轮 claude 修复并启动
|
|
626
|
+
if (isServiceAlive()) {
|
|
627
|
+
log(`✓ Service already running before attempt ${attempt}, skipping`);
|
|
628
|
+
await sendHealSummary(p, pendingInfo, attempt - 1, log);
|
|
629
|
+
eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt - 1 });
|
|
630
|
+
archiveSelfHealLog(p, log);
|
|
631
|
+
cleanupPendingFile(pendingFile, log);
|
|
632
|
+
process.exit(0);
|
|
633
|
+
}
|
|
538
634
|
log(`Self-heal attempt ${attempt}/${MAX_HEAL_ATTEMPTS}`);
|
|
635
|
+
eventBus.publish({ type: 'self-heal:attempt', attemptNumber: attempt, maxAttempts: MAX_HEAL_ATTEMPTS });
|
|
539
636
|
await notifyChannel(p, pendingInfo, `🔧 自动修复中(第 ${attempt}/${MAX_HEAL_ATTEMPTS} 次)...`, log);
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
637
|
+
const healed = await invokeClaude(p, attempt, MAX_HEAL_ATTEMPTS, HEAL_TIMEOUT, log);
|
|
638
|
+
// 后置检查:不管 invokeClaude 返回什么,都检查服务实际状态
|
|
639
|
+
if (isServiceAlive()) {
|
|
640
|
+
log(`✓ Service is running after attempt ${attempt}`);
|
|
641
|
+
await sendHealSummary(p, pendingInfo, attempt, log);
|
|
642
|
+
eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt });
|
|
643
|
+
archiveSelfHealLog(p, log);
|
|
644
|
+
cleanupPendingFile(pendingFile, log);
|
|
645
|
+
process.exit(0);
|
|
646
|
+
}
|
|
543
647
|
if (!healed) {
|
|
544
648
|
log(`Self-heal attempt ${attempt} failed (claude invocation error)`);
|
|
545
649
|
continue;
|
|
546
650
|
}
|
|
547
|
-
//
|
|
651
|
+
// claude 正常完成但服务没自动启动,尝试 spawn
|
|
548
652
|
started = await spawnAndWaitReady(p, log, READY_TIMEOUT);
|
|
549
653
|
if (started) {
|
|
550
654
|
log(`✓ Self-heal succeeded on attempt ${attempt}`);
|
|
655
|
+
await sendHealSummary(p, pendingInfo, attempt, log);
|
|
656
|
+
eventBus.publish({ type: 'self-heal:completed', success: true, attempts: attempt });
|
|
551
657
|
archiveSelfHealLog(p, log);
|
|
552
|
-
await notifyChannel(p, pendingInfo, `✅ 自愈成功!(第 ${attempt} 次修复后恢复)`, log);
|
|
553
658
|
cleanupPendingFile(pendingFile, log);
|
|
554
659
|
process.exit(0);
|
|
555
660
|
}
|
|
556
661
|
log(`Attempt ${attempt}: still failing after fix`);
|
|
557
662
|
}
|
|
558
|
-
// 全部失败
|
|
663
|
+
// 全部失败 — 最后再检查一次
|
|
664
|
+
if (isServiceAlive()) {
|
|
665
|
+
log('✓ Service recovered during final check');
|
|
666
|
+
await sendHealSummary(p, pendingInfo, MAX_HEAL_ATTEMPTS, log);
|
|
667
|
+
eventBus.publish({ type: 'self-heal:completed', success: true, attempts: MAX_HEAL_ATTEMPTS });
|
|
668
|
+
archiveSelfHealLog(p, log);
|
|
669
|
+
cleanupPendingFile(pendingFile, log);
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
559
672
|
log(`❌ All ${MAX_HEAL_ATTEMPTS} self-heal attempts failed`);
|
|
673
|
+
eventBus.publish({ type: 'self-heal:completed', success: false, attempts: MAX_HEAL_ATTEMPTS });
|
|
560
674
|
await notifyChannel(p, pendingInfo, `❌ ${MAX_HEAL_ATTEMPTS} 次自动修复均失败,需要人工介入。\n修复记录:${p.selfHealLog}`, log);
|
|
561
675
|
cleanupPendingFile(pendingFile, log);
|
|
562
676
|
process.exit(1);
|
|
563
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* 发送 self-heal 修复成功小结(从 self-heal.md 提取摘要)
|
|
680
|
+
*/
|
|
681
|
+
async function sendHealSummary(p, pendingInfo, attempts, log) {
|
|
682
|
+
let summary = `✅ 自动修复成功(第 ${attempts || 1} 次尝试)`;
|
|
683
|
+
try {
|
|
684
|
+
if (fs.existsSync(p.selfHealLog)) {
|
|
685
|
+
const content = fs.readFileSync(p.selfHealLog, 'utf-8');
|
|
686
|
+
// 提取最后一个 ## 章节的要点
|
|
687
|
+
const sections = content.split(/^## /m).filter(Boolean);
|
|
688
|
+
const last = sections[sections.length - 1];
|
|
689
|
+
if (last) {
|
|
690
|
+
const lines = last.split('\n').filter(l => l.startsWith('- ')).map(l => l.trim());
|
|
691
|
+
if (lines.length > 0) {
|
|
692
|
+
summary += '\n' + lines.join('\n');
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch { }
|
|
698
|
+
summary += '\n\n⚠️ 修复前进行中的任务已中断,如需继续请重新发送。';
|
|
699
|
+
await notifyChannel(p, pendingInfo, summary, log);
|
|
700
|
+
}
|
|
564
701
|
function sleep(ms) {
|
|
565
702
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
566
703
|
}
|
|
@@ -643,16 +780,27 @@ async function invokeClaude(p, attempt, maxAttempts, timeout, log) {
|
|
|
643
780
|
|
|
644
781
|
关键信息:
|
|
645
782
|
- 项目目录:${projectDir}
|
|
646
|
-
-
|
|
647
|
-
-
|
|
783
|
+
- EVOLCLAW_HOME:${p.root}
|
|
784
|
+
- 错误日志:${stdoutLog}
|
|
785
|
+
- 主日志:${path.join(p.logs, 'evolclaw.log')}(logger 输出在这里,包含 config 校验失败等关键错误)
|
|
648
786
|
- 修复记录:${selfHealLog}(${selfHealExists})
|
|
649
787
|
|
|
788
|
+
⚠️ 重要诊断技巧:
|
|
789
|
+
- stdout.log 可能是空的(进程秒退时 logger 输出不会到 stdout),一定要同时读 evolclaw.log
|
|
790
|
+
- 必须实际运行进程来复现错误:\`EVOLCLAW_HOME=${p.root} node dist/index.js 2>&1\`,观察输出和退出码
|
|
791
|
+
- 检查是否有旧进程仍在运行:\`ps aux | grep 'node.*dist/index.js' | grep -v grep\`,旧进程可能占用端口或锁文件
|
|
792
|
+
- 可以运行 \`EVOLCLAW_HOME=${p.root} node dist/cli.js diagnose\` 快速检查配置和数据库
|
|
793
|
+
- 如果进程无任何输出就 exit(1),说明是 process.exit(1) 被显式调用,搜索源码中所有 process.exit(1) 位置
|
|
794
|
+
- evolclaw.json 有自动备份机制:运行时 config watch 检测到文件损坏会自动保存内存快照到 \`data/evolclaw-{timestamp}.json\`,同时 \`data/evolclaw.backup.json\` 是最近一次完整配置的备份。如果 evolclaw.json 损坏或缺失,可以从这些备份恢复
|
|
795
|
+
|
|
650
796
|
请执行以下步骤:
|
|
651
|
-
1.
|
|
652
|
-
2.
|
|
653
|
-
3.
|
|
654
|
-
4.
|
|
655
|
-
5.
|
|
797
|
+
1. 读取 ${stdoutLog} 和 ${path.join(p.logs, 'evolclaw.log')} 的最后 50 行
|
|
798
|
+
2. 运行 \`EVOLCLAW_HOME=${p.root} node dist/index.js 2>&1\` 复现错误(设置 10 秒超时)
|
|
799
|
+
3. 如果 ${selfHealLog} 存在,先阅读之前的修复记录,避免重复尝试已失败的方案
|
|
800
|
+
4. 根据实际复现的错误修复代码
|
|
801
|
+
5. 执行 npm run build 确认编译通过
|
|
802
|
+
6. 验证修复:启动服务确认 ready.signal 已写入,然后执行 \`EVOLCLAW_HOME=${p.root} node dist/cli.js stop\` 优雅停止(restart-monitor 会负责最终启动)
|
|
803
|
+
7. 将本次修复内容追加到 ${selfHealLog},格式:
|
|
656
804
|
## 第 ${attempt} 次修复 - {时间}
|
|
657
805
|
- 错误原因:...
|
|
658
806
|
- 修复方案:...
|
|
@@ -679,12 +827,19 @@ async function invokeClaude(p, attempt, maxAttempts, timeout, log) {
|
|
|
679
827
|
return true;
|
|
680
828
|
}
|
|
681
829
|
catch (error) {
|
|
682
|
-
|
|
683
|
-
|
|
830
|
+
if (error.killed) {
|
|
831
|
+
log(`Claude CLI timeout after ${timeout / 60000}min (attempt ${attempt})`);
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
log(`Claude CLI error: exit code ${error.code ?? 'unknown'} (attempt ${attempt})`);
|
|
835
|
+
}
|
|
684
836
|
if (error.stdout)
|
|
685
|
-
log(`
|
|
686
|
-
if (error.stderr)
|
|
687
|
-
|
|
837
|
+
log(`Claude output: ${String(error.stdout).slice(0, 500)}`);
|
|
838
|
+
if (error.stderr) {
|
|
839
|
+
const stderr = String(error.stderr).replace(/Warning: no stdin.*\n?/g, '').trim();
|
|
840
|
+
if (stderr)
|
|
841
|
+
log(`Claude stderr: ${stderr.slice(0, 300)}`);
|
|
842
|
+
}
|
|
688
843
|
return false;
|
|
689
844
|
}
|
|
690
845
|
}
|
|
@@ -803,6 +958,136 @@ async function notifyChannel(p, pendingInfo, message, log) {
|
|
|
803
958
|
}
|
|
804
959
|
}
|
|
805
960
|
}
|
|
961
|
+
// ==================== Migrate ====================
|
|
962
|
+
async function cmdMv(oldDir, newDir) {
|
|
963
|
+
if (!oldDir || !newDir) {
|
|
964
|
+
console.log('Usage: evolclaw mv <old_directory> <new_directory>');
|
|
965
|
+
console.log('Example: evolclaw mv ~/projects/old-name ~/projects/new-name');
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
968
|
+
const oldAbs = path.resolve(oldDir);
|
|
969
|
+
const newAbs = path.resolve(newDir);
|
|
970
|
+
console.log(`迁移项目: ${oldAbs} → ${newAbs}\n`);
|
|
971
|
+
try {
|
|
972
|
+
const r = await migrateProject(oldAbs, newAbs);
|
|
973
|
+
if (r.claudeSessionsMoved)
|
|
974
|
+
console.log('✓ Claude Code 会话目录已迁移');
|
|
975
|
+
if (r.claudeHistoryUpdated)
|
|
976
|
+
console.log('✓ Claude Code history.jsonl 已更新');
|
|
977
|
+
if (r.codexUpdated > 0)
|
|
978
|
+
console.log(`✓ Codex 数据库已更新 (${r.codexUpdated} 个会话)`);
|
|
979
|
+
if (r.directoryMoved)
|
|
980
|
+
console.log('✓ 项目目录已移动');
|
|
981
|
+
if (r.evolclawDbUpdated > 0)
|
|
982
|
+
console.log(`✓ EvolClaw sessions.db 已更新 (${r.evolclawDbUpdated} 个会话)`);
|
|
983
|
+
if (r.evolclawConfigUpdated)
|
|
984
|
+
console.log('✓ evolclaw.json projects.list 已更新');
|
|
985
|
+
console.log('\n迁移完成!');
|
|
986
|
+
}
|
|
987
|
+
catch (e) {
|
|
988
|
+
console.error(`迁移失败: ${e instanceof Error ? e.message : e}`);
|
|
989
|
+
process.exit(1);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
// ==================== Diagnose ====================
|
|
993
|
+
async function cmdDiagnose() {
|
|
994
|
+
const p = resolvePaths();
|
|
995
|
+
let hasError = false;
|
|
996
|
+
// 1. 检查数据目录
|
|
997
|
+
console.log(`[diagnose] EVOLCLAW_HOME = ${p.root}`);
|
|
998
|
+
if (!fs.existsSync(p.root)) {
|
|
999
|
+
console.error(`[diagnose] ❌ 数据目录不存在: ${p.root}`);
|
|
1000
|
+
hasError = true;
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
console.log(`[diagnose] ✓ 数据目录存在`);
|
|
1004
|
+
}
|
|
1005
|
+
// 2. 加载并校验配置
|
|
1006
|
+
try {
|
|
1007
|
+
const config = loadConfig();
|
|
1008
|
+
console.log(`[diagnose] ✓ 配置文件加载成功: ${p.config}`);
|
|
1009
|
+
const integrity = validateConfigIntegrity(config);
|
|
1010
|
+
if (!integrity.valid) {
|
|
1011
|
+
console.error(`[diagnose] ❌ 配置完整性校验失败:\n ${integrity.reasons.join('\n ')}`);
|
|
1012
|
+
hasError = true;
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
console.log(`[diagnose] ✓ 配置完整性校验通过`);
|
|
1016
|
+
}
|
|
1017
|
+
// 3. 检查 Anthropic 配置
|
|
1018
|
+
try {
|
|
1019
|
+
const anthropic = resolveAnthropicConfig(config);
|
|
1020
|
+
console.log(`[diagnose] ✓ Anthropic 配置解析成功 (apiKey: ${anthropic.apiKey ? '已设置' : '❌ 未设置'}, model: ${anthropic.model || 'default'})`);
|
|
1021
|
+
}
|
|
1022
|
+
catch (e) {
|
|
1023
|
+
console.error(`[diagnose] ❌ Anthropic 配置解析失败: ${e instanceof Error ? e.message : e}`);
|
|
1024
|
+
hasError = true;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
catch (e) {
|
|
1028
|
+
console.error(`[diagnose] ❌ 配置文件加载失败: ${e instanceof Error ? e.message : e}`);
|
|
1029
|
+
hasError = true;
|
|
1030
|
+
}
|
|
1031
|
+
// 4. 检查数据库
|
|
1032
|
+
try {
|
|
1033
|
+
const { SessionManager } = await import('./core/session-manager.js');
|
|
1034
|
+
const eventBus = new EventBus();
|
|
1035
|
+
new SessionManager(p.db, eventBus);
|
|
1036
|
+
console.log(`[diagnose] ✓ 数据库初始化成功: ${p.db}`);
|
|
1037
|
+
}
|
|
1038
|
+
catch (e) {
|
|
1039
|
+
console.error(`[diagnose] ❌ 数据库初始化失败: ${e instanceof Error ? e.message : e}`);
|
|
1040
|
+
hasError = true;
|
|
1041
|
+
}
|
|
1042
|
+
// 5. 检查残留进程
|
|
1043
|
+
try {
|
|
1044
|
+
const pid = isRunning(p.pid);
|
|
1045
|
+
if (pid) {
|
|
1046
|
+
console.log(`[diagnose] ⚠️ 已有进程运行中: PID ${pid}`);
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
console.log(`[diagnose] ✓ 无残留进程`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
catch {
|
|
1053
|
+
console.log(`[diagnose] ✓ 无 PID 文件`);
|
|
1054
|
+
}
|
|
1055
|
+
// 6. 检查关键文件
|
|
1056
|
+
const appMain = path.join(getPackageRoot(), 'dist', 'index.js');
|
|
1057
|
+
if (!fs.existsSync(appMain)) {
|
|
1058
|
+
console.error(`[diagnose] ❌ 编译产物不存在: ${appMain}`);
|
|
1059
|
+
hasError = true;
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
console.log(`[diagnose] ✓ 编译产物存在: ${appMain}`);
|
|
1063
|
+
}
|
|
1064
|
+
if (hasError) {
|
|
1065
|
+
console.error('\n[diagnose] ❌ 诊断发现问题,请修复后重试');
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
console.log('\n[diagnose] ✓ 所有检查通过');
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function cmdTui() {
|
|
1073
|
+
const config = loadConfig();
|
|
1074
|
+
const aun = config.channels?.aun;
|
|
1075
|
+
if (!aun?.owner || !aun?.aid) {
|
|
1076
|
+
console.error('[tui] AUN 未配置,请先运行: evolclaw init aun');
|
|
1077
|
+
process.exit(1);
|
|
1078
|
+
}
|
|
1079
|
+
// Check Python + aun_core, interactive install if missing
|
|
1080
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1081
|
+
const ready = await checkAunEnvironment(rl);
|
|
1082
|
+
rl.close();
|
|
1083
|
+
if (!ready) {
|
|
1084
|
+
process.exit(1);
|
|
1085
|
+
}
|
|
1086
|
+
const pythonBin = aun.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
1087
|
+
const cliScript = path.join(getPackageRoot(), 'aun', 'aun_cli.py');
|
|
1088
|
+
const child = spawn(pythonBin, [cliScript, '-a', aun.owner, '-t', aun.aid], { stdio: 'inherit' });
|
|
1089
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
1090
|
+
}
|
|
806
1091
|
// ==================== Main ====================
|
|
807
1092
|
export async function main(args) {
|
|
808
1093
|
const cmd = args[0] || 'start';
|
|
@@ -814,6 +1099,9 @@ export async function main(args) {
|
|
|
814
1099
|
else if (args[1] === 'feishu') {
|
|
815
1100
|
await cmdInitFeishu();
|
|
816
1101
|
}
|
|
1102
|
+
else if (args[1] === 'aun') {
|
|
1103
|
+
await cmdInitAun();
|
|
1104
|
+
}
|
|
817
1105
|
else {
|
|
818
1106
|
await cmdInit();
|
|
819
1107
|
}
|
|
@@ -836,18 +1124,31 @@ export async function main(args) {
|
|
|
836
1124
|
case 'restart-monitor':
|
|
837
1125
|
await cmdRestartMonitor();
|
|
838
1126
|
break;
|
|
1127
|
+
case 'mv':
|
|
1128
|
+
await cmdMv(args[1], args[2]);
|
|
1129
|
+
break;
|
|
1130
|
+
case 'diagnose':
|
|
1131
|
+
await cmdDiagnose();
|
|
1132
|
+
break;
|
|
1133
|
+
case 'tui':
|
|
1134
|
+
await cmdTui();
|
|
1135
|
+
break;
|
|
839
1136
|
default:
|
|
840
|
-
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs}
|
|
1137
|
+
console.log(`Usage: evolclaw {init|start|stop|restart|status|logs|tui|diagnose|mv}
|
|
841
1138
|
|
|
842
1139
|
Commands:
|
|
843
1140
|
init 创建配置文件 (${resolvePaths().config})
|
|
844
|
-
init wechat 微信扫码登录并写入配置
|
|
845
1141
|
init feishu 飞书扫码登录并写入配置
|
|
1142
|
+
init wechat 微信扫码登录并写入配置
|
|
1143
|
+
init aun AUN (AgentUnin.Network) 配置
|
|
846
1144
|
start 启动服务 (默认)
|
|
847
1145
|
stop 停止服务
|
|
848
1146
|
restart 重启服务
|
|
849
1147
|
status 查看状态
|
|
850
1148
|
logs 查看日志 (tail -f)
|
|
1149
|
+
tui 启动 AUN TUI 客户端
|
|
1150
|
+
diagnose 诊断启动环境(配置、数据库、进程)
|
|
1151
|
+
mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
|
|
851
1152
|
|
|
852
1153
|
Environment:
|
|
853
1154
|
EVOLCLAW_HOME 数据目录 (默认: ~/.evolclaw)
|