evolclaw 2.1.1 → 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 +571 -223
- package/dist/core/stats-collector.js +86 -0
- package/dist/index.js +309 -243
- package/dist/paths.js +1 -0
- package/dist/utils/error-utils.js +4 -2
- 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
|
@@ -10,7 +10,8 @@ export var ErrorType;
|
|
|
10
10
|
export function classifyError(error) {
|
|
11
11
|
const msg = (error?.message || '').toLowerCase();
|
|
12
12
|
if (msg.includes('上下文过长') || msg.includes('context too long')
|
|
13
|
-
|| msg.includes('context_length_exceeded') || msg.includes('context_compact_failed')
|
|
13
|
+
|| msg.includes('context_length_exceeded') || msg.includes('context_compact_failed')
|
|
14
|
+
|| msg.includes('prompt is too long') || msg.includes('context limit')) {
|
|
14
15
|
return ErrorType.CONTEXT_TOO_LONG;
|
|
15
16
|
}
|
|
16
17
|
if (msg.includes('timeout') || msg.includes('etimedout')) {
|
|
@@ -32,7 +33,8 @@ export function getErrorMessage(error) {
|
|
|
32
33
|
if (msg.includes('CONTEXT_COMPACT_FAILED')) {
|
|
33
34
|
return '⚠️ 上下文过长,自动压缩失败,请手动输入 /compact 重试';
|
|
34
35
|
}
|
|
35
|
-
if (msg.includes('上下文过长') || msg.includes('context too long') || msg.includes('context_length_exceeded')
|
|
36
|
+
if (msg.includes('上下文过长') || msg.includes('context too long') || msg.includes('context_length_exceeded')
|
|
37
|
+
|| msg.includes('Prompt is too long') || msg.includes('Context limit')) {
|
|
36
38
|
return '⚠️ 上下文过长,自动压缩重试失败,请手动输入 /compact 重试';
|
|
37
39
|
}
|
|
38
40
|
if (msg.includes('API Error: 400')) {
|
|
@@ -247,6 +247,8 @@ export async function cmdInitFeishu() {
|
|
|
247
247
|
else {
|
|
248
248
|
delete config.channels.feishu.owner;
|
|
249
249
|
}
|
|
250
|
+
if (!config.channels.defaultChannel)
|
|
251
|
+
config.channels.defaultChannel = 'feishu';
|
|
250
252
|
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
251
253
|
console.log(`\n✅ 飞书连接成功!`);
|
|
252
254
|
console.log(` App ID: ${result.appId}`);
|
|
@@ -154,6 +154,8 @@ export async function cmdInitWechat() {
|
|
|
154
154
|
baseUrl: status.baseurl || DEFAULT_BASE_URL,
|
|
155
155
|
token: status.bot_token,
|
|
156
156
|
};
|
|
157
|
+
if (!config.channels.defaultChannel)
|
|
158
|
+
config.channels.defaultChannel = 'wechat';
|
|
157
159
|
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
158
160
|
console.log(`\n✅ 微信连接成功!`);
|
|
159
161
|
console.log(` Bot ID: ${status.ilink_bot_id}`);
|
package/dist/utils/init.js
CHANGED
|
@@ -7,7 +7,7 @@ import { execFileSync } from 'child_process';
|
|
|
7
7
|
import { promisify } from 'util';
|
|
8
8
|
import { execFile } from 'child_process';
|
|
9
9
|
import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from '../paths.js';
|
|
10
|
-
import { isWindows, commandExists } from './platform.js';
|
|
10
|
+
import { isWindows, commandExists } from './cross-platform.js';
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
// ==================== Helpers ====================
|
|
13
13
|
function ask(rl, question) {
|
|
@@ -184,43 +184,73 @@ async function checkEnvironment(rl) {
|
|
|
184
184
|
console.log(' → 请先安装: npm install -g @anthropic-ai/claude-code');
|
|
185
185
|
return false;
|
|
186
186
|
}
|
|
187
|
-
//
|
|
188
|
-
|
|
187
|
+
// Agent SDK 检查:claude-agent-sdk / codex-sdk,至少需要一个
|
|
188
|
+
const MIN_CLAUDE_SDK = [0, 2, 75];
|
|
189
|
+
let hasClaudeSdk = false;
|
|
190
|
+
let hasCodexSdk = false;
|
|
191
|
+
// Check claude-agent-sdk
|
|
189
192
|
try {
|
|
190
|
-
// 用 require.resolve 找到 SDK 入口,推导 package.json 路径
|
|
191
193
|
const esmRequire = createRequire(import.meta.url);
|
|
192
194
|
const sdkEntry = esmRequire.resolve('@anthropic-ai/claude-agent-sdk');
|
|
193
195
|
const sdkPkgPath = path.join(path.dirname(sdkEntry), 'package.json');
|
|
194
196
|
const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, 'utf-8'));
|
|
195
197
|
const sdkVer = sdkPkg.version;
|
|
196
198
|
const parts = sdkVer.split('.').map(Number);
|
|
197
|
-
const sdkOk = parts[0] > 0
|
|
199
|
+
const sdkOk = parts[0] > MIN_CLAUDE_SDK[0]
|
|
200
|
+
|| (parts[0] === MIN_CLAUDE_SDK[0] && parts[1] > MIN_CLAUDE_SDK[1])
|
|
201
|
+
|| (parts[0] === MIN_CLAUDE_SDK[0] && parts[1] === MIN_CLAUDE_SDK[1] && parts[2] >= MIN_CLAUDE_SDK[2]);
|
|
198
202
|
if (sdkOk) {
|
|
199
203
|
console.log(` ✓ claude-agent-sdk v${sdkVer}`);
|
|
204
|
+
hasClaudeSdk = true;
|
|
200
205
|
}
|
|
201
206
|
else {
|
|
202
|
-
console.log(` ✗ claude-agent-sdk v${sdkVer} — 需要 >=
|
|
203
|
-
|
|
207
|
+
console.log(` ✗ claude-agent-sdk v${sdkVer} — 需要 >= ${MIN_CLAUDE_SDK.join('.')}`);
|
|
208
|
+
const answer = (await ask(rl, ' → 是否升级 claude-agent-sdk?[Y/n] ')).trim().toLowerCase();
|
|
209
|
+
if (answer !== 'n' && answer !== 'no') {
|
|
210
|
+
console.log(' 正在升级 claude-agent-sdk...');
|
|
211
|
+
try {
|
|
212
|
+
await npmInstallGlobal('@anthropic-ai/claude-agent-sdk@latest');
|
|
213
|
+
console.log(' ✓ claude-agent-sdk 升级完成');
|
|
214
|
+
hasClaudeSdk = true;
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
console.log(` ✗ 升级失败: ${e.message?.slice(0, 200) || e}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
console.log(' - claude-agent-sdk 未安装');
|
|
224
|
+
}
|
|
225
|
+
// Check @openai/codex-sdk (ESM-only, cannot use require.resolve)
|
|
226
|
+
try {
|
|
227
|
+
const codexPkgPath = path.join(getPackageRoot(), 'node_modules', '@openai', 'codex-sdk', 'package.json');
|
|
228
|
+
if (fs.existsSync(codexPkgPath)) {
|
|
229
|
+
const codexPkg = JSON.parse(fs.readFileSync(codexPkgPath, 'utf-8'));
|
|
230
|
+
console.log(` ✓ codex-sdk v${codexPkg.version}`);
|
|
231
|
+
hasCodexSdk = true;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.log(' - codex-sdk 未安装');
|
|
204
235
|
}
|
|
205
236
|
}
|
|
206
237
|
catch {
|
|
207
|
-
console.log('
|
|
208
|
-
sdkAction = 'install';
|
|
238
|
+
console.log(' - codex-sdk 未安装');
|
|
209
239
|
}
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
const answer = (await ask(rl,
|
|
240
|
+
if (!hasClaudeSdk && !hasCodexSdk) {
|
|
241
|
+
console.log('\n ✗ 需要至少安装一个 Agent SDK:claude-agent-sdk 或 codex-sdk');
|
|
242
|
+
const answer = (await ask(rl, ' → 是否安装 claude-agent-sdk?[Y/n] ')).trim().toLowerCase();
|
|
213
243
|
if (answer === 'n' || answer === 'no') {
|
|
214
244
|
console.log(' 已取消');
|
|
215
245
|
return false;
|
|
216
246
|
}
|
|
217
|
-
console.log(
|
|
247
|
+
console.log(' 正在安装 claude-agent-sdk...');
|
|
218
248
|
try {
|
|
219
249
|
await npmInstallGlobal('@anthropic-ai/claude-agent-sdk@latest');
|
|
220
|
-
console.log(
|
|
250
|
+
console.log(' ✓ claude-agent-sdk 安装完成');
|
|
221
251
|
}
|
|
222
252
|
catch (e) {
|
|
223
|
-
console.log(` ✗
|
|
253
|
+
console.log(` ✗ 安装失败: ${e.message?.slice(0, 200) || e}`);
|
|
224
254
|
return false;
|
|
225
255
|
}
|
|
226
256
|
}
|
|
@@ -312,6 +342,182 @@ async function initFeishuManual(rl, config) {
|
|
|
312
342
|
config.channels.feishu.enabled = true;
|
|
313
343
|
return true;
|
|
314
344
|
}
|
|
345
|
+
// ==================== AUN Environment Check ====================
|
|
346
|
+
export async function checkAunEnvironment(rl) {
|
|
347
|
+
console.log('\n🔍 AUN 环境检查...\n');
|
|
348
|
+
// Check python3 (needed for aun_cli.py AID management)
|
|
349
|
+
if (!commandExists('python3')) {
|
|
350
|
+
console.log(' ✗ python3 未找到');
|
|
351
|
+
console.log(' → 请先安装 Python 3: https://www.python.org/downloads/');
|
|
352
|
+
const answer = (await ask(rl, ' → 返回重新选择渠道?[Y/n] ')).trim().toLowerCase();
|
|
353
|
+
if (answer === 'n' || answer === 'no')
|
|
354
|
+
process.exit(0);
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
console.log(' ✓ python3');
|
|
358
|
+
// Check aun_core
|
|
359
|
+
try {
|
|
360
|
+
execFileSync('python3', ['-c', 'import aun_core'], { encoding: 'utf-8', stdio: 'pipe' });
|
|
361
|
+
console.log(' ✓ aun_core');
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
console.log(' ✗ aun_core 未安装');
|
|
365
|
+
console.log(' → 请安装: pip3 install aun-core');
|
|
366
|
+
const answer = (await ask(rl, ' → 返回重新选择渠道?[Y/n] ')).trim().toLowerCase();
|
|
367
|
+
if (answer === 'n' || answer === 'no')
|
|
368
|
+
process.exit(0);
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
console.log('');
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
// ==================== Rich Content Renderer ====================
|
|
375
|
+
async function offerRichContentRenderer(rl, config) {
|
|
376
|
+
const answer = (await ask(rl, '\n是否启用 LaTeX + Mermaid 渲染模块(约 35MB)?[y/N] ')).trim().toLowerCase();
|
|
377
|
+
const enableRich = answer === 'y' || answer === 'yes';
|
|
378
|
+
// 记录用户选择到配置文件(仅 Feishu 通道需要)
|
|
379
|
+
if (config.channels?.feishu) {
|
|
380
|
+
config.channels.feishu.enableRichContent = enableRich;
|
|
381
|
+
}
|
|
382
|
+
if (!enableRich) {
|
|
383
|
+
console.log(' ✓ 已跳过富内容渲染模块安装');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.log(' 正在安装 katex 和 mermaid(可能需要 1-2 分钟)...');
|
|
387
|
+
try {
|
|
388
|
+
await npmInstallGlobal('katex');
|
|
389
|
+
await npmInstallGlobal('mermaid');
|
|
390
|
+
console.log(' ✓ LaTeX + Mermaid 渲染模块安装完成');
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
const msg = e.message || '';
|
|
394
|
+
if (e.killed || msg.includes('ETIMEDOUT') || msg.includes('timed out')) {
|
|
395
|
+
console.log(' ✗ 安装超时,网络可能较慢');
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log(` ✗ 安装失败: ${msg.slice(0, 200)}`);
|
|
399
|
+
}
|
|
400
|
+
console.log(' → 可稍后手动安装: npm install -g katex mermaid');
|
|
401
|
+
// 安装失败时,将配置设为 false
|
|
402
|
+
if (config.channels?.feishu) {
|
|
403
|
+
config.channels.feishu.enableRichContent = false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// ==================== AUN AID Helpers ====================
|
|
408
|
+
function isValidAid(name) {
|
|
409
|
+
const labels = name.split('.');
|
|
410
|
+
return labels.length >= 3 && labels.every(l => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(l));
|
|
411
|
+
}
|
|
412
|
+
async function setupAunAid(rl, config) {
|
|
413
|
+
const pythonBin = config.channels?.aun?.pythonBin || process.env.AUN_PYTHON || 'python3';
|
|
414
|
+
const cliScript = path.join(getPackageRoot(), 'aun', 'aun_cli.py');
|
|
415
|
+
let aid = '';
|
|
416
|
+
let gatewayPort;
|
|
417
|
+
// Outer loop: allows retrying with a different AID
|
|
418
|
+
while (true) {
|
|
419
|
+
// Ask AID with format validation
|
|
420
|
+
aid = '';
|
|
421
|
+
while (!aid) {
|
|
422
|
+
aid = (await ask(rl, ' AUN Agent ID (例: mybot.agentid.pub): ')).trim();
|
|
423
|
+
if (!aid) {
|
|
424
|
+
console.log(' ⚠ 不能为空');
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!isValidAid(aid)) {
|
|
428
|
+
console.log(' ⚠ 无效 AID 格式(需要合法域名,至少三级,如 alice.agentid.pub)');
|
|
429
|
+
aid = '';
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const portStr = (await ask(rl, ' Gateway 端口 [留空使用默认 443]: ')).trim();
|
|
433
|
+
gatewayPort = portStr ? parseInt(portStr, 10) : undefined;
|
|
434
|
+
if (gatewayPort !== undefined && (isNaN(gatewayPort) || gatewayPort < 1 || gatewayPort > 65535)) {
|
|
435
|
+
console.log(' ⚠ 端口号无效,使用默认 443');
|
|
436
|
+
gatewayPort = undefined;
|
|
437
|
+
}
|
|
438
|
+
// Check if AID exists locally
|
|
439
|
+
const aidDir = path.join(os.homedir(), '.aun', 'AIDs', aid);
|
|
440
|
+
if (fs.existsSync(aidDir)) {
|
|
441
|
+
console.log(` ✓ AID ${aid} 已存在`);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
const answer = (await ask(rl, ` ⚠ AID ${aid} 本地不存在,是否创建?[Y/n] `)).trim().toLowerCase();
|
|
445
|
+
if (answer === 'n' || answer === 'no') {
|
|
446
|
+
console.log(' 已跳过 AID 创建(启动时可能连接失败)');
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
// Try creating
|
|
450
|
+
console.log(' 正在创建 AID...');
|
|
451
|
+
const args = [cliScript, 'aid', 'new', aid];
|
|
452
|
+
if (gatewayPort)
|
|
453
|
+
args.push('-p', String(gatewayPort));
|
|
454
|
+
let failed = false;
|
|
455
|
+
try {
|
|
456
|
+
const { stdout, stderr } = await execFileAsync(pythonBin, args, { timeout: 30000, encoding: 'utf-8' });
|
|
457
|
+
const output = (stdout + stderr).trim();
|
|
458
|
+
if (output)
|
|
459
|
+
console.log(` ${output}`);
|
|
460
|
+
// aun_cli.py 可能 exit 0 但输出包含错误
|
|
461
|
+
failed = output.includes('✗') || output.includes('失败');
|
|
462
|
+
}
|
|
463
|
+
catch (e) {
|
|
464
|
+
const msg = e.stderr || e.stdout || e.message || '';
|
|
465
|
+
console.log(` ✗ AID 创建失败: ${msg.trim().slice(0, 200)}`);
|
|
466
|
+
failed = true;
|
|
467
|
+
}
|
|
468
|
+
if (!failed) {
|
|
469
|
+
console.log(` ✓ AID ${aid} 创建成功`);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
// Creation failed — retry or give up
|
|
473
|
+
const retry = (await ask(rl, ' → 重新输入 (r) / 跳过 (s) / 取消 (c)?[r/s/c] ')).trim().toLowerCase();
|
|
474
|
+
if (retry === 'c')
|
|
475
|
+
return null;
|
|
476
|
+
if (retry === 's')
|
|
477
|
+
break;
|
|
478
|
+
// default: retry with new AID
|
|
479
|
+
}
|
|
480
|
+
return { aid, gatewayPort };
|
|
481
|
+
}
|
|
482
|
+
// ==================== Init AUN (standalone) ====================
|
|
483
|
+
export async function cmdInitAun() {
|
|
484
|
+
const p = resolvePaths();
|
|
485
|
+
if (!fs.existsSync(p.config)) {
|
|
486
|
+
console.log('❌ 配置文件不存在,请先运行 evolclaw init');
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const config = JSON.parse(fs.readFileSync(p.config, 'utf-8'));
|
|
490
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
491
|
+
try {
|
|
492
|
+
if (config.channels?.aun?.aid) {
|
|
493
|
+
const answer = (await ask(rl, '已有 AUN 配置,是否重新配置?[y/N] ')).trim().toLowerCase();
|
|
494
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
495
|
+
console.log('已取消');
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (!await checkAunEnvironment(rl)) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const result = await setupAunAid(rl, config);
|
|
503
|
+
if (!result)
|
|
504
|
+
return;
|
|
505
|
+
if (!config.channels)
|
|
506
|
+
config.channels = {};
|
|
507
|
+
config.channels.aun = {
|
|
508
|
+
enabled: true,
|
|
509
|
+
aid: result.aid,
|
|
510
|
+
...(result.gatewayPort && { gatewayPort: result.gatewayPort }),
|
|
511
|
+
};
|
|
512
|
+
if (!config.channels.defaultChannel)
|
|
513
|
+
config.channels.defaultChannel = 'aun';
|
|
514
|
+
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
515
|
+
console.log('\n✓ AUN 配置已写入');
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
rl.close();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
315
521
|
// ==================== Main ====================
|
|
316
522
|
export async function cmdInit() {
|
|
317
523
|
const p = resolvePaths();
|
|
@@ -360,56 +566,82 @@ export async function cmdInit() {
|
|
|
360
566
|
}
|
|
361
567
|
const modelInput = (await ask(rl, ' 模型 [sonnet(默认)/opus/haiku]: ')).trim().toLowerCase();
|
|
362
568
|
const model = ['opus', 'haiku'].includes(modelInput) ? modelInput : 'sonnet';
|
|
363
|
-
//
|
|
364
|
-
console.log('\n选择消息渠道:');
|
|
365
|
-
console.log(' 1. 飞书 (Feishu)');
|
|
366
|
-
console.log(' 2. 微信 (WeChat)');
|
|
367
|
-
const channelChoice = (await ask(rl, '请选择 [1]: ')).trim() || '1';
|
|
569
|
+
// 渠道选择(支持退回重选)
|
|
368
570
|
const config = JSON.parse(fs.readFileSync(sampleSrc, 'utf-8'));
|
|
369
571
|
config.projects.defaultPath = defaultPath;
|
|
370
572
|
config.projects.list = { [path.basename(defaultPath)]: defaultPath };
|
|
371
573
|
config.agents.anthropic.model = model;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
console.log('
|
|
375
|
-
console.log('
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
574
|
+
let channelConfigured = false;
|
|
575
|
+
while (!channelConfigured) {
|
|
576
|
+
console.log('\n选择消息渠道:');
|
|
577
|
+
console.log(' 1. 飞书 (Feishu)');
|
|
578
|
+
console.log(' 2. 微信 (WeChat)');
|
|
579
|
+
console.log(' 3. AUN (AgentUnin.Network)');
|
|
580
|
+
const channelChoice = (await ask(rl, '请选择 [1]: ')).trim() || '1';
|
|
581
|
+
if (channelChoice === '1') {
|
|
582
|
+
console.log('\n飞书配置方式:');
|
|
583
|
+
console.log(' 1. 扫码自动注册(推荐)');
|
|
584
|
+
console.log(' 2. 手动输入 App ID/Secret');
|
|
585
|
+
const feishuMethod = (await ask(rl, '请选择 [1]: ')).trim() || '1';
|
|
586
|
+
if (feishuMethod === '1') {
|
|
587
|
+
const { runFeishuQrFlow } = await import('./init-feishu.js');
|
|
588
|
+
const result = await runFeishuQrFlow();
|
|
589
|
+
if (!result) {
|
|
590
|
+
console.log('已取消');
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
config.channels.feishu.appId = result.appId;
|
|
594
|
+
config.channels.feishu.appSecret = result.appSecret;
|
|
595
|
+
config.channels.feishu.enabled = true;
|
|
596
|
+
if (result.openId)
|
|
597
|
+
config.channels.feishu.owner = result.openId;
|
|
383
598
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
599
|
+
else {
|
|
600
|
+
if (!await initFeishuManual(rl, config)) {
|
|
601
|
+
console.log('已取消');
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
channelConfigured = true;
|
|
606
|
+
config.channels.defaultChannel = 'feishu';
|
|
389
607
|
}
|
|
390
|
-
else {
|
|
391
|
-
|
|
608
|
+
else if (channelChoice === '2') {
|
|
609
|
+
const { runWechatQrFlow } = await import('./init-wechat.js');
|
|
610
|
+
const result = await runWechatQrFlow();
|
|
611
|
+
if (!result) {
|
|
392
612
|
console.log('已取消');
|
|
393
613
|
return;
|
|
394
614
|
}
|
|
615
|
+
config.channels.wechat = {
|
|
616
|
+
enabled: true,
|
|
617
|
+
baseUrl: result.baseUrl,
|
|
618
|
+
token: result.token,
|
|
619
|
+
};
|
|
620
|
+
channelConfigured = true;
|
|
621
|
+
config.channels.defaultChannel = 'wechat';
|
|
395
622
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
623
|
+
else if (channelChoice === '3') {
|
|
624
|
+
const aunReady = await checkAunEnvironment(rl);
|
|
625
|
+
if (!aunReady)
|
|
626
|
+
continue; // 退回重选渠道
|
|
627
|
+
const result = await setupAunAid(rl, config);
|
|
628
|
+
if (!result)
|
|
629
|
+
continue;
|
|
630
|
+
config.channels.aun = {
|
|
631
|
+
enabled: true,
|
|
632
|
+
aid: result.aid,
|
|
633
|
+
...(result.gatewayPort && { gatewayPort: result.gatewayPort }),
|
|
634
|
+
};
|
|
635
|
+
channelConfigured = true;
|
|
636
|
+
config.channels.defaultChannel = 'aun';
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
console.log(' 无效选择,请重新输入');
|
|
403
640
|
}
|
|
404
|
-
config.channels.wechat = {
|
|
405
|
-
enabled: true,
|
|
406
|
-
baseUrl: result.baseUrl,
|
|
407
|
-
token: result.token,
|
|
408
|
-
};
|
|
409
641
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
642
|
+
// 可选:富内容渲染模块(仅 Feishu 通道需要)
|
|
643
|
+
if (config.channels?.feishu?.enabled) {
|
|
644
|
+
await offerRichContentRenderer(rl, config);
|
|
413
645
|
}
|
|
414
646
|
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
415
647
|
console.log(`\n✓ 已创建配置文件: ${p.config}`);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
/**
|
|
3
|
+
* Query the running EvolClaw daemon via Unix socket.
|
|
4
|
+
* Returns null if the service is not running or the socket is unreachable.
|
|
5
|
+
*/
|
|
6
|
+
export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const conn = net.connect(socketPath);
|
|
9
|
+
let buf = '';
|
|
10
|
+
const timer = setTimeout(() => {
|
|
11
|
+
conn.destroy();
|
|
12
|
+
resolve(null);
|
|
13
|
+
}, timeoutMs);
|
|
14
|
+
conn.on('connect', () => {
|
|
15
|
+
conn.write(JSON.stringify(cmd) + '\n');
|
|
16
|
+
});
|
|
17
|
+
conn.on('data', (data) => {
|
|
18
|
+
buf += data.toString();
|
|
19
|
+
const idx = buf.indexOf('\n');
|
|
20
|
+
if (idx !== -1) {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
try {
|
|
23
|
+
resolve(JSON.parse(buf.slice(0, idx)));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
resolve(null);
|
|
27
|
+
}
|
|
28
|
+
conn.destroy();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
conn.on('error', () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
resolve(null);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { resolvePaths } from '../paths.js';
|
|
5
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
6
|
+
/** 将绝对路径编码为 Claude Code 的目录名格式(/ \ . 替换为 -) */
|
|
7
|
+
function encodePath(p) {
|
|
8
|
+
return p.replace(/[/\\\.]/g, '-');
|
|
9
|
+
}
|
|
10
|
+
/** 查找最新的 ~/.codex/state_*.sqlite */
|
|
11
|
+
function findCodexDb() {
|
|
12
|
+
const codexHome = path.join(os.homedir(), '.codex');
|
|
13
|
+
if (!fs.existsSync(codexHome))
|
|
14
|
+
return null;
|
|
15
|
+
const files = fs.readdirSync(codexHome)
|
|
16
|
+
.filter(f => /^state_\d+\.sqlite$/.test(f))
|
|
17
|
+
.sort((a, b) => {
|
|
18
|
+
const va = parseInt(a.match(/state_(\d+)/)?.[1] || '0');
|
|
19
|
+
const vb = parseInt(b.match(/state_(\d+)/)?.[1] || '0');
|
|
20
|
+
return vb - va;
|
|
21
|
+
});
|
|
22
|
+
return files.length > 0 ? path.join(codexHome, files[0]) : null;
|
|
23
|
+
}
|
|
24
|
+
export async function migrateProject(oldPath, newPath) {
|
|
25
|
+
const result = {
|
|
26
|
+
claudeSessionsMoved: false,
|
|
27
|
+
claudeHistoryUpdated: false,
|
|
28
|
+
codexUpdated: 0,
|
|
29
|
+
evolclawDbUpdated: 0,
|
|
30
|
+
evolclawConfigUpdated: false,
|
|
31
|
+
directoryMoved: false,
|
|
32
|
+
};
|
|
33
|
+
const oldAbs = path.resolve(oldPath);
|
|
34
|
+
const newAbs = path.resolve(newPath);
|
|
35
|
+
if (!fs.existsSync(oldAbs))
|
|
36
|
+
throw new Error(`源目录不存在: ${oldAbs}`);
|
|
37
|
+
if (fs.existsSync(newAbs))
|
|
38
|
+
throw new Error(`目标目录已存在: ${newAbs}`);
|
|
39
|
+
const claudeProjects = path.join(os.homedir(), '.claude', 'projects');
|
|
40
|
+
const oldEncoded = encodePath(oldAbs);
|
|
41
|
+
const newEncoded = encodePath(newAbs);
|
|
42
|
+
// 1. 迁移 ~/.claude/projects/{encoded}/
|
|
43
|
+
const oldClaudeDir = path.join(claudeProjects, oldEncoded);
|
|
44
|
+
const newClaudeDir = path.join(claudeProjects, newEncoded);
|
|
45
|
+
if (fs.existsSync(oldClaudeDir)) {
|
|
46
|
+
fs.renameSync(oldClaudeDir, newClaudeDir);
|
|
47
|
+
result.claudeSessionsMoved = true;
|
|
48
|
+
}
|
|
49
|
+
// 2. .jsonl 内部路径不需要替换 — 它们是历史对话记录,
|
|
50
|
+
// resume 时模型会根据当前 cwd 工作,旧路径只是历史上下文
|
|
51
|
+
// 3. 更新 ~/.claude/history.jsonl
|
|
52
|
+
const historyFile = path.join(os.homedir(), '.claude', 'history.jsonl');
|
|
53
|
+
if (fs.existsSync(historyFile)) {
|
|
54
|
+
const lines = fs.readFileSync(historyFile, 'utf-8').split('\n');
|
|
55
|
+
const updated = lines.map(line => {
|
|
56
|
+
if (!line.trim())
|
|
57
|
+
return line;
|
|
58
|
+
try {
|
|
59
|
+
const obj = JSON.parse(line);
|
|
60
|
+
if (obj.project === oldAbs) {
|
|
61
|
+
obj.project = newAbs;
|
|
62
|
+
return JSON.stringify(obj);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch { /* skip */ }
|
|
66
|
+
return line;
|
|
67
|
+
});
|
|
68
|
+
const newContent = updated.join('\n');
|
|
69
|
+
if (newContent !== fs.readFileSync(historyFile, 'utf-8')) {
|
|
70
|
+
fs.writeFileSync(historyFile, newContent, 'utf-8');
|
|
71
|
+
result.claudeHistoryUpdated = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 4. 更新 Codex SQLite threads.cwd
|
|
75
|
+
const codexDbPath = findCodexDb();
|
|
76
|
+
if (codexDbPath) {
|
|
77
|
+
try {
|
|
78
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
79
|
+
const db = new DatabaseSync(codexDbPath);
|
|
80
|
+
const r = db.prepare('UPDATE threads SET cwd = ? WHERE cwd = ?').run(newAbs, oldAbs);
|
|
81
|
+
result.codexUpdated = r.changes ?? 0;
|
|
82
|
+
db.close();
|
|
83
|
+
}
|
|
84
|
+
catch { /* Codex not installed or DB locked */ }
|
|
85
|
+
}
|
|
86
|
+
// 5. 移动项目目录
|
|
87
|
+
fs.renameSync(oldAbs, newAbs);
|
|
88
|
+
result.directoryMoved = true;
|
|
89
|
+
// 6. 更新 EvolClaw sessions.db
|
|
90
|
+
const p = resolvePaths();
|
|
91
|
+
if (fs.existsSync(p.db)) {
|
|
92
|
+
try {
|
|
93
|
+
const { DatabaseSync } = await import('node:sqlite');
|
|
94
|
+
const db = new DatabaseSync(p.db);
|
|
95
|
+
const r = db.prepare('UPDATE sessions SET project_path = ? WHERE project_path = ?').run(newAbs, oldAbs);
|
|
96
|
+
result.evolclawDbUpdated = r.changes ?? 0;
|
|
97
|
+
db.close();
|
|
98
|
+
}
|
|
99
|
+
catch { /* DB not accessible */ }
|
|
100
|
+
}
|
|
101
|
+
// 7. 更新 evolclaw.json projects.list
|
|
102
|
+
if (fs.existsSync(p.config)) {
|
|
103
|
+
try {
|
|
104
|
+
const config = loadConfig(p.config);
|
|
105
|
+
if (config.projects?.list) {
|
|
106
|
+
let changed = false;
|
|
107
|
+
for (const [k, v] of Object.entries(config.projects.list)) {
|
|
108
|
+
if (v === oldAbs) {
|
|
109
|
+
config.projects.list[k] = newAbs;
|
|
110
|
+
changed = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (changed) {
|
|
114
|
+
saveConfig(config, p.config);
|
|
115
|
+
result.evolclawConfigUpdated = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch { /* config not accessible */ }
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
@@ -17,10 +17,10 @@ const DANGEROUS_PATTERNS = [
|
|
|
17
17
|
/\bnet\s+stop/i, // net stop (停止服务)
|
|
18
18
|
];
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* 黑名单检查(用于 PreToolUse hook)
|
|
21
|
+
* 检查危险命令模式,非黑名单一律放行
|
|
22
22
|
*/
|
|
23
|
-
export async function
|
|
23
|
+
export async function checkBlacklist(toolName, input) {
|
|
24
24
|
// 只检查 Bash 工具,其余工具全部放行
|
|
25
25
|
if (toolName === 'Bash') {
|
|
26
26
|
const cmd = input.command || '';
|
|
@@ -41,3 +41,31 @@ export async function canUseTool(toolName, input) {
|
|
|
41
41
|
// 默认允许
|
|
42
42
|
return { behavior: 'allow', updatedInput: input };
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 工具输入摘要(提取工具调用的可读描述,供权限审批和消息展示使用)
|
|
46
|
+
*/
|
|
47
|
+
export function summarizeToolInput(toolName, input) {
|
|
48
|
+
if (!input)
|
|
49
|
+
return '';
|
|
50
|
+
const extractors = {
|
|
51
|
+
'Read': (i) => i.file_path,
|
|
52
|
+
'Edit': (i) => i.file_path,
|
|
53
|
+
'Write': (i) => i.file_path,
|
|
54
|
+
'Bash': (i) => i.command?.substring(0, 80),
|
|
55
|
+
'Grep': (i) => `pattern: ${i.pattern}`,
|
|
56
|
+
'Glob': (i) => `pattern: ${i.pattern}`,
|
|
57
|
+
'Agent': (i) => i.description || i.prompt?.substring(0, 80),
|
|
58
|
+
};
|
|
59
|
+
const extractor = extractors[toolName];
|
|
60
|
+
if (extractor) {
|
|
61
|
+
const result = extractor(input);
|
|
62
|
+
if (result)
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
return input.description
|
|
66
|
+
|| input.file_path
|
|
67
|
+
|| input.pattern
|
|
68
|
+
|| input.command?.substring(0, 80)
|
|
69
|
+
|| input.prompt?.substring(0, 80)
|
|
70
|
+
|| '';
|
|
71
|
+
}
|