evolclaw 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,13 +10,13 @@ import readline from 'readline';
10
10
  import path from 'path';
11
11
  import os from 'os';
12
12
  import crypto from 'crypto';
13
- import { fileURLToPath } from 'url';
14
- import { execFile, execFileSync } from 'child_process';
13
+ import { execFile } from 'child_process';
15
14
  import { promisify } from 'util';
16
15
  import { resolvePaths } from '../paths.js';
17
16
  import { normalizeChannelInstances } from '../config.js';
18
17
  import { selectInstance } from './init.js';
19
18
  import { isWindows } from './cross-platform.js';
19
+ import { AUN_CORE_SDK_PKG, MIN_AUN_CORE_SDK, resolveAunCoreSdkPkg, isAunSdkVersionOk, isValidAid, aidCreate, agentmdPut, buildInitialAgentMd, } from '../channels/aun-ops.js';
20
20
  const execFileAsync = promisify(execFile);
21
21
  export async function npmInstallGlobal(pkg) {
22
22
  const npmCmd = isWindows ? 'npm.cmd' : 'npm';
@@ -580,91 +580,9 @@ export async function cmdInitWechat() {
580
580
  process.exit(1);
581
581
  }
582
582
  // ==================== AUN ====================
583
- // 最低 @agentunion/fastaun 版本要求
584
- const MIN_AUN_CORE_SDK = [0, 2, 14];
585
- const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
586
- function compareVersion(a, min) {
587
- const parts = a.split('.').map(n => parseInt(n, 10));
588
- if (parts.length < 3 || parts.some(isNaN))
589
- return false;
590
- if (parts[0] !== min[0])
591
- return parts[0] > min[0];
592
- if (parts[1] !== min[1])
593
- return parts[1] > min[1];
594
- return parts[2] >= min[2];
595
- }
596
- export function resolveAunCoreSdkPkg() {
597
- const pkgName = AUN_CORE_SDK_PKG;
598
- // Strategy 1: walk up node_modules from this file (no require.resolve — avoids ESM exports issues)
599
- try {
600
- let dir = path.dirname(fileURLToPath(import.meta.url));
601
- while (true) {
602
- const candidate = path.join(dir, 'node_modules', pkgName, 'package.json');
603
- if (fs.existsSync(candidate)) {
604
- const data = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
605
- if (data.name === pkgName)
606
- return { version: data.version, path: candidate };
607
- }
608
- const parent = path.dirname(dir);
609
- if (parent === dir)
610
- break;
611
- dir = parent;
612
- }
613
- }
614
- catch { /* fall through */ }
615
- // Strategy 2: npm root -g fallback (globally installed SDK)
616
- try {
617
- const npmCmd = isWindows ? 'npm.cmd' : 'npm';
618
- const globalRoot = execFileSync(npmCmd, ['root', '-g'], {
619
- encoding: 'utf-8', timeout: 10000, shell: isWindows,
620
- }).trim();
621
- const pkgPath = path.join(globalRoot, pkgName, 'package.json');
622
- if (fs.existsSync(pkgPath)) {
623
- const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
624
- return { version: data.version, path: pkgPath };
625
- }
626
- }
627
- catch { /* not found */ }
628
- return null;
629
- }
630
- /**
631
- * Download AUN CA root certificate to ~/.aun/CA/root/root.crt.
632
- * Idempotent: skips if file already exists. Returns true if a cert is on disk
633
- * after the call (either pre-existing or freshly downloaded).
634
- *
635
- * Must be called BEFORE constructing any AUNClient that needs to verify
636
- * gateway-issued certificates (e.g. for uploadAgentMd) — the SDK loads trusted
637
- * roots from disk at client construction time and won't pick up later writes.
638
- */
639
- export async function downloadCaRoot(aunPath, gatewayUrl, indent = '') {
640
- const caDir = path.join(aunPath, 'CA', 'root');
641
- const caCertPath = path.join(caDir, 'root.crt');
642
- if (fs.existsSync(caCertPath))
643
- return true;
644
- if (!gatewayUrl)
645
- return false;
646
- try {
647
- fs.mkdirSync(caDir, { recursive: true });
648
- const gwHttp = gatewayUrl.replace(/^wss?:/, 'https:').replace(/\/aun$/, '');
649
- const resp = await fetch(`${gwHttp}/pki/chain`, { redirect: 'follow' });
650
- if (!resp.ok) {
651
- console.warn(`${indent}⚠ CA 根证书下载失败: HTTP ${resp.status}`);
652
- return false;
653
- }
654
- const body = await resp.text();
655
- if (!body.includes('BEGIN CERTIFICATE')) {
656
- console.warn(`${indent}⚠ CA 根证书响应内容无效,跳过写入`);
657
- return false;
658
- }
659
- fs.writeFileSync(caCertPath, body);
660
- console.log(`${indent}✓ CA 根证书已下载`);
661
- return true;
662
- }
663
- catch (e) {
664
- console.warn(`${indent}⚠ CA 根证书下载失败: ${e},可稍后手动下载`);
665
- return false;
666
- }
667
- }
583
+ //
584
+ // AUN 原子操作(aidCreate, agentmdPut, downloadCaRoot, isValidAid, ...)
585
+ // 已迁移至 src/channels/aun-ops.ts。本节仅保留交互式 UI 编排。
668
586
  export async function checkAunEnvironment(rl) {
669
587
  console.log('\n🔍 AUN 环境检查...\n');
670
588
  const minVer = MIN_AUN_CORE_SDK.join('.');
@@ -688,7 +606,7 @@ export async function checkAunEnvironment(rl) {
688
606
  console.log('');
689
607
  return true;
690
608
  }
691
- if (compareVersion(installed.version, MIN_AUN_CORE_SDK)) {
609
+ if (isAunSdkVersionOk(installed.version)) {
692
610
  console.log(` ✓ ${AUN_CORE_SDK_PKG} v${installed.version}`);
693
611
  console.log('');
694
612
  return true;
@@ -711,95 +629,8 @@ export async function checkAunEnvironment(rl) {
711
629
  console.log('');
712
630
  return true;
713
631
  }
714
- export function isValidAid(name) {
715
- const labels = name.split('.');
716
- return labels.length >= 3 && labels.every(l => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(l));
717
- }
718
- /**
719
- * Non-interactive AID creation + agent.md publish.
720
- * Reuses the same logic as `evolclaw init --non-interactive --channel aun`.
721
- *
722
- * Returns the created AID string, or throws on failure.
723
- */
724
- export async function createAidSilent(opts) {
725
- const aunPath = path.join(os.homedir(), '.aun');
726
- const aidDir = path.join(aunPath, 'AIDs', opts.aid);
727
- // Skip creation if AID already exists locally
728
- if (fs.existsSync(aidDir) && fs.existsSync(path.join(aidDir, 'private'))) {
729
- return { aid: opts.aid, alreadyExisted: true };
730
- }
731
- const { AUNClient, GatewayDiscovery } = await import('@agentunion/fastaun');
732
- let client = new AUNClient({ aun_path: aunPath });
733
- const result = await client.auth.createAid({ aid: opts.aid });
734
- // Download CA root cert (if not already present)
735
- const caDownloaded = await downloadCaRoot(aunPath, result.gateway || '');
736
- // Rebuild client with CA cert + AID identity for uploadAgentMd
737
- const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
738
- if (caDownloaded && fs.existsSync(caCertPath)) {
739
- try {
740
- await client.close();
741
- }
742
- catch { /* ignore */ }
743
- client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath, aid: opts.aid });
744
- }
745
- // Set gateway URL for uploadAgentMd
746
- let gatewayUrl = result.gateway || '';
747
- if (!gatewayUrl) {
748
- try {
749
- const discovery = new GatewayDiscovery({});
750
- gatewayUrl = await discovery.discover(`https://${opts.aid}/.well-known/aun-gateway`);
751
- }
752
- catch { /* fall through */ }
753
- }
754
- if (gatewayUrl) {
755
- client._gatewayUrl = gatewayUrl;
756
- }
757
- // Write initial agent.md (initialized: false, name = aid first label)
758
- const agentName = opts.aid.split('.')[0];
759
- const agentMdContent = `---\naid: "${opts.aid}"\nname: "${agentName}"\ntype: "ai"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
760
- const agentMdPath = path.join(aidDir, 'agent.md');
761
- try {
762
- await client.auth.uploadAgentMd(agentMdContent);
763
- }
764
- catch (e) {
765
- // Non-fatal: first connection will auto-retry
766
- }
767
- fs.writeFileSync(agentMdPath, agentMdContent, 'utf-8');
768
- try {
769
- await client.close();
770
- }
771
- catch { /* ignore */ }
772
- if (!fs.existsSync(agentMdPath)) {
773
- throw new Error(`agent.md write verification failed: ${agentMdPath}`);
774
- }
775
- return { aid: opts.aid, alreadyExisted: false };
776
- }
777
- /**
778
- * Append a new AUN instance to the config's channels.aun array and save.
779
- * Handles upgrade from single-object to array format.
780
- */
781
- export function appendAunInstance(config, inst) {
782
- if (!config.channels)
783
- config.channels = {};
784
- const newInst = {
785
- name: inst.name,
786
- enabled: inst.enabled ?? true,
787
- aid: inst.aid,
788
- ...(inst.owner && { owner: inst.owner }),
789
- };
790
- if (Array.isArray(config.channels.aun)) {
791
- config.channels.aun.push(newInst);
792
- }
793
- else if (config.channels.aun) {
794
- const oldInst = { ...config.channels.aun, name: config.channels.aun.name || 'aun' };
795
- config.channels.aun = [oldInst, newInst];
796
- }
797
- else {
798
- config.channels.aun = [newInst];
799
- }
800
- const p = resolvePaths();
801
- fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
802
- }
632
+ // isValidAid, createAidSilent → 已迁移至 src/channels/aun-ops.ts
633
+ // appendAunInstance 已迁移至 src/channels/aun-ops.ts
803
634
  export async function setupAunAid(rl, _config) {
804
635
  let aid = '';
805
636
  // Outer loop: allows retrying with a different AID
@@ -829,64 +660,35 @@ export async function setupAunAid(rl, _config) {
829
660
  console.log(' 已跳过 AID 创建(启动时可能连接失败)');
830
661
  break;
831
662
  }
832
- // Create AID using TS SDK directly
663
+ // Create AID + agent.md via atomic ops
833
664
  console.log(' 正在创建 AID...');
834
665
  let failed = false;
835
666
  try {
836
- const { AUNClient, GatewayDiscovery } = await import('@agentunion/fastaun');
837
- let client = new AUNClient({ aun_path: aunPath });
838
- const result = await client.auth.createAid({ aid });
667
+ const result = await aidCreate(aid);
839
668
  console.log(` ✓ AID ${result.aid} 创建成功`);
840
- // 下载 CA 根证书(如果本地不存在),从 SDK 返回的实际网关 URL 派生
841
- const caDownloaded = await downloadCaRoot(aunPath, result.gateway || '', ' ');
842
- // 重建 client:传 root_ca_path 以验证 server cert,传 aid 以加载身份
843
- const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
844
- if (caDownloaded && fs.existsSync(caCertPath)) {
845
- try {
846
- await client.close();
847
- }
848
- catch { /* ignore */ }
849
- client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath, aid });
850
- }
851
- // 设置 gateway URL(从 createAid 返回值或 well-known 自动发现)
852
- let gatewayUrl = result.gateway || '';
853
- if (!gatewayUrl) {
854
- try {
855
- const discovery = new GatewayDiscovery({});
856
- gatewayUrl = await discovery.discover(`https://${aid}/.well-known/aun-gateway`);
857
- }
858
- catch { /* fall through */ }
859
- }
860
- if (gatewayUrl) {
861
- client._gatewayUrl = gatewayUrl;
862
- }
863
- // Collect agent.md info and publish
669
+ // Collect agent.md type and upload
864
670
  const typeInput = (await ask(rl, ' Agent 类型 human/ai [ai]: ')).trim().toLowerCase();
865
671
  const agentType = typeInput === 'human' ? 'human' : 'ai';
866
- const agentName = aid.split('.')[0];
867
- const agentMdContent = `---\naid: "${aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
868
- const agentMdPath = path.join(aidDir, 'agent.md');
672
+ const content = buildInitialAgentMd({ aid, type: agentType });
869
673
  try {
870
- await client.auth.uploadAgentMd(agentMdContent);
871
- console.log(' ✓ agent.md 已发布');
674
+ await agentmdPut(content, { aid, client: result.client });
675
+ console.log(' ✓ agent.md 已发布并写入本地');
872
676
  }
873
677
  catch (e) {
874
678
  console.log(` ⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
875
- }
876
- fs.writeFileSync(agentMdPath, agentMdContent, 'utf-8');
877
- if (!fs.existsSync(agentMdPath)) {
679
+ // Still write local copy as fallback
878
680
  try {
879
- await client.close();
681
+ fs.mkdirSync(aidDir, { recursive: true });
682
+ fs.writeFileSync(path.join(aidDir, 'agent.md'), content, 'utf-8');
683
+ console.log(' ✓ agent.md 已写入本地');
684
+ }
685
+ catch (we) {
686
+ console.log(` ✗ agent.md 本地写入失败: ${String(we.message || we).slice(0, 100)}`);
687
+ failed = true;
880
688
  }
881
- catch { /* ignore */ }
882
- console.log(` ✗ agent.md 本地写入校验失败: ${agentMdPath}`);
883
- failed = true;
884
- }
885
- else {
886
- console.log(' ✓ agent.md 已写入本地');
887
689
  }
888
690
  try {
889
- await client.close();
691
+ await result.client.close();
890
692
  }
891
693
  catch { /* ignore */ }
892
694
  }
@@ -1610,3 +1412,71 @@ export async function cmdInitWecom() {
1610
1412
  console.log(` 配置已写入: ${p.config}`);
1611
1413
  console.log(`\n现在可以启动服务: evolclaw restart`);
1612
1414
  }
1415
+ export function getChannelCredentialCollector(type) {
1416
+ switch (type) {
1417
+ case 'feishu':
1418
+ return async () => {
1419
+ const result = await runFeishuQrFlow();
1420
+ if (!result)
1421
+ return null;
1422
+ return { appId: result.appId, appSecret: result.appSecret, enabled: true };
1423
+ };
1424
+ case 'wechat':
1425
+ return async () => {
1426
+ const result = await runWechatQrFlow();
1427
+ if (!result)
1428
+ return null;
1429
+ return { baseUrl: result.baseUrl, token: result.token, enabled: true };
1430
+ };
1431
+ case 'aun':
1432
+ return async () => {
1433
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1434
+ try {
1435
+ if (!await checkAunEnvironment(rl))
1436
+ return null;
1437
+ const result = await setupAunAid(rl, {});
1438
+ if (!result)
1439
+ return null;
1440
+ return { aid: result.aid, owner: result.owner, enabled: true };
1441
+ }
1442
+ finally {
1443
+ rl.close();
1444
+ }
1445
+ };
1446
+ case 'dingtalk':
1447
+ return async () => {
1448
+ const result = await runDingtalkQrFlowSimple();
1449
+ if (!result)
1450
+ return null;
1451
+ return { clientId: result.clientId, clientSecret: result.clientSecret, enabled: true };
1452
+ };
1453
+ case 'qqbot':
1454
+ return async () => {
1455
+ const result = await runQQBotBindFlowSimple();
1456
+ if (!result)
1457
+ return null;
1458
+ return { appId: result.appId, clientSecret: result.clientSecret, enabled: true };
1459
+ };
1460
+ case 'wecom':
1461
+ return async () => {
1462
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1463
+ const ask = (q) => new Promise(r => rl.question(q, r));
1464
+ try {
1465
+ console.log('企业微信 AI Bot 配置\n');
1466
+ console.log('请在企业微信管理后台 → AI Bot 页面获取 Bot ID 和 Secret\n');
1467
+ const botId = (await ask(' Bot ID: ')).trim();
1468
+ if (!botId)
1469
+ return null;
1470
+ const secret = (await ask(' Secret: ')).trim();
1471
+ if (!secret)
1472
+ return null;
1473
+ return { botId, secret, enabled: true };
1474
+ }
1475
+ finally {
1476
+ rl.close();
1477
+ }
1478
+ };
1479
+ default:
1480
+ return null;
1481
+ }
1482
+ }
@@ -396,7 +396,7 @@ export async function cmdInit(options) {
396
396
  // 非交互式模式
397
397
  if (options?.nonInteractive) {
398
398
  const config = JSON.parse(fs.readFileSync(sampleSrc, 'utf-8'));
399
- const defaultPath = options.defaultPath || path.join(os.homedir(), 'evolclaw-project');
399
+ const defaultPath = options.defaultPath || path.join(os.homedir(), 'projects', 'default');
400
400
  if (!fs.existsSync(defaultPath))
401
401
  fs.mkdirSync(defaultPath, { recursive: true });
402
402
  config.projects.defaultPath = defaultPath;
@@ -405,56 +405,32 @@ export async function cmdInit(options) {
405
405
  throw new Error('--aun-aid is required for AUN channel (e.g. --aun-aid mybot.agentid.pub)');
406
406
  }
407
407
  if (options.channel === 'aun' && options.aunAid) {
408
- // 自动安装 AUN SDK
409
- const { resolveAunCoreSdkPkg, npmInstallGlobal, downloadCaRoot } = await import('./init-channel.js');
410
- if (!resolveAunCoreSdkPkg()) {
411
- console.log('正在安装 @agentunion/fastaun...');
412
- await npmInstallGlobal('@agentunion/fastaun@latest');
413
- }
414
- // 创建 AID(如果本地不存在)
408
+ const { ensureAunSdk, aidCreate, agentmdPut, buildInitialAgentMd } = await import('../channels/aun-ops.js');
409
+ await ensureAunSdk();
415
410
  const aunPath = path.join(os.homedir(), '.aun');
416
411
  const aidDir = path.join(aunPath, 'AIDs', options.aunAid);
417
412
  if (!fs.existsSync(path.join(aidDir, 'private'))) {
418
- const { AUNClient } = await import('@agentunion/fastaun');
419
- let client = new AUNClient({ aun_path: aunPath });
420
- // SDK 通过 well-known 自动发现网关
421
- const result = await client.auth.createAid({ aid: options.aunAid });
422
- // 下载 CA 根证书(如果本地不存在),从 SDK 返回的实际网关 URL 派生
423
- const caDownloaded = await downloadCaRoot(aunPath, result.gateway || '');
424
- // 关键:SDK 默认 rootCaPath=null,只读取包内 bundled certs。
425
- // 必须显式传 root_ca_path 指向刚下载的 root.crt,uploadAgentMd 才能验证 server cert。
426
- // 同时传 aid,否则新 client 不知道该加载哪个身份,uploadAgentMd 会报
427
- // "no local identity found, call auth.createAid() first"。
428
- const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
429
- if (caDownloaded && fs.existsSync(caCertPath)) {
413
+ const result = await aidCreate(options.aunAid);
414
+ try {
415
+ const content = buildInitialAgentMd({ aid: options.aunAid });
430
416
  try {
431
- await client.close();
417
+ await agentmdPut(content, { aid: options.aunAid, client: result.client });
418
+ }
419
+ catch (e) {
420
+ console.warn(`⚠ agent.md 网络发布失败(首次连接将自动重试): ${String(e?.message || e).slice(0, 100)}`);
421
+ fs.mkdirSync(aidDir, { recursive: true });
422
+ fs.writeFileSync(path.join(aidDir, 'agent.md'), content, 'utf-8');
423
+ }
424
+ if (!fs.existsSync(path.join(aidDir, 'agent.md'))) {
425
+ throw new Error(`agent.md 写入校验失败: ${path.join(aidDir, 'agent.md')}`);
432
426
  }
433
- catch { }
434
- client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath, aid: options.aunAid });
435
- }
436
- // 写入初始 agent.md(initialized: false)
437
- const agentName = options.aunAid.split('.')[0];
438
- const agentMd = `---\naid: "${options.aunAid}"\nname: "${agentName}"\ntype: "ai"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\ninitialized: false\n---\n`;
439
- const agentMdPath = path.join(aidDir, 'agent.md');
440
- try {
441
- await client.auth.uploadAgentMd(agentMd);
442
- }
443
- catch (e) {
444
- console.warn(`⚠ agent.md 网络发布失败(首次连接将自动重试): ${String(e?.message || e).slice(0, 100)}`);
445
427
  }
446
- fs.writeFileSync(agentMdPath, agentMd, 'utf-8');
447
- if (!fs.existsSync(agentMdPath)) {
428
+ finally {
448
429
  try {
449
- await client.close();
430
+ await result.client.close();
450
431
  }
451
432
  catch { }
452
- throw new Error(`agent.md 写入校验失败: ${agentMdPath}`);
453
- }
454
- try {
455
- await client.close();
456
433
  }
457
- catch { }
458
434
  }
459
435
  config.channels.aun = {
460
436
  enabled: true,
@@ -483,7 +459,7 @@ export async function cmdInit(options) {
483
459
  }
484
460
  console.log('📝 交互式配置\n');
485
461
  // 通用配置
486
- const defaultSuggestion = path.join(os.homedir(), 'evolclaw-project');
462
+ const defaultSuggestion = path.join(os.homedir(), 'projects', 'default');
487
463
  let defaultPath = (await ask(rl, ` 默认项目路径 [${defaultSuggestion}]: `)).trim();
488
464
  if (!defaultPath)
489
465
  defaultPath = defaultSuggestion;
@@ -4,6 +4,9 @@ import { resolvePaths } from '../paths.js';
4
4
  const LOG_DIR = resolvePaths().logs;
5
5
  let currentLevel = process.env.LOG_LEVEL || 'INFO';
6
6
  const LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
7
+ const HOUR_MS = 60 * 60 * 1000;
8
+ const RETAIN_HOURS = 12;
9
+ const LOG_FILE_RE = /^evolclaw-\d{8}-\d{2}\.log$/;
7
10
  const config = {
8
11
  messageLog: process.env.MESSAGE_LOG === 'true',
9
12
  eventLog: process.env.EVENT_LOG === 'true'
@@ -11,8 +14,60 @@ const config = {
11
14
  if (!fs.existsSync(LOG_DIR)) {
12
15
  fs.mkdirSync(LOG_DIR, { recursive: true });
13
16
  }
17
+ /** 获取当前小时标识 YYYYMMDD-HH */
18
+ function currentHourTag() {
19
+ const d = new Date();
20
+ const pad = (n) => String(n).padStart(2, '0');
21
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}`;
22
+ }
23
+ /** 清理超过 RETAIN_HOURS 的旧日志文件 */
24
+ function cleanupOldLogs() {
25
+ const cutoff = Date.now() - RETAIN_HOURS * HOUR_MS;
26
+ try {
27
+ for (const name of fs.readdirSync(LOG_DIR)) {
28
+ if (!LOG_FILE_RE.test(name))
29
+ continue;
30
+ try {
31
+ const full = path.join(LOG_DIR, name);
32
+ if (fs.statSync(full).mtimeMs < cutoff)
33
+ fs.unlinkSync(full);
34
+ }
35
+ catch { }
36
+ }
37
+ }
38
+ catch { }
39
+ }
40
+ let mainHourTag = currentHourTag();
41
+ let mainStream = fs.createWriteStream(path.join(LOG_DIR, `evolclaw-${mainHourTag}.log`), { flags: 'a' });
42
+ // 同时保留 evolclaw.log 软链接指向当前文件,方便 tail -f
43
+ function updateSymlink() {
44
+ const link = path.join(LOG_DIR, 'evolclaw.log');
45
+ const target = `evolclaw-${mainHourTag}.log`;
46
+ try {
47
+ fs.unlinkSync(link);
48
+ }
49
+ catch { }
50
+ try {
51
+ fs.symlinkSync(target, link);
52
+ }
53
+ catch { }
54
+ }
55
+ updateSymlink();
56
+ // 启动时清理一次,之后每小时清理
57
+ cleanupOldLogs();
58
+ const cleanupTimer = setInterval(cleanupOldLogs, HOUR_MS);
59
+ cleanupTimer.unref?.();
60
+ function rotateMainIfNeeded() {
61
+ const tag = currentHourTag();
62
+ if (tag === mainHourTag)
63
+ return;
64
+ mainStream.end();
65
+ mainHourTag = tag;
66
+ mainStream = fs.createWriteStream(path.join(LOG_DIR, `evolclaw-${mainHourTag}.log`), { flags: 'a' });
67
+ updateSymlink();
68
+ cleanupOldLogs();
69
+ }
14
70
  const streams = {
15
- main: fs.createWriteStream(path.join(LOG_DIR, 'evolclaw.log'), { flags: 'a' }),
16
71
  message: config.messageLog ? fs.createWriteStream(path.join(LOG_DIR, 'messages.log'), { flags: 'a' }) : null,
17
72
  event: config.eventLog ? fs.createWriteStream(path.join(LOG_DIR, 'events.log'), { flags: 'a' }) : null
18
73
  };
@@ -33,9 +88,10 @@ export function localTimestamp() {
33
88
  function log(level, ...args) {
34
89
  if (!shouldLog(level))
35
90
  return;
91
+ rotateMainIfNeeded();
36
92
  const timestamp = localTimestamp();
37
93
  const msg = `[${timestamp}] [${level}] ${args.join(' ')}`;
38
- write(streams.main, msg);
94
+ mainStream.write(msg + '\n');
39
95
  }
40
96
  /**
41
97
  * 设置日志级别(config 加载后调用,覆盖环境变量默认值)
@@ -46,23 +46,64 @@ export PATH=~/.npm-global/bin:$PATH
46
46
  npm install -g evolclaw
47
47
  ```
48
48
 
49
- ### 步骤 3:准备配置信息
49
+ ### 步骤 3:检查是否已在运行
50
+
51
+ npm 安装完成后,先检查是否有正在运行的 evolclaw 进程。升级或重装场景下,旧进程仍在使用旧版本代码,必须停止后才能让新版本生效。
52
+
53
+ ```bash
54
+ evolclaw status
55
+ ```
56
+
57
+ 判断逻辑:
58
+
59
+ - 输出包含 `running` / `PID` / `已启动` 等表明服务在运行 → **询问用户**是否停止:
60
+
61
+ > 检测到 evolclaw 正在运行(PID: xxx)。继续安装需要停止当前服务以应用新版本,是否停止?(y/N)
62
+
63
+ 用户确认后执行:
64
+
65
+ ```bash
66
+ evolclaw stop
67
+ ```
68
+
69
+ 用户拒绝则中止安装流程,提醒其手动 `evolclaw stop` 后重新发起。
70
+
71
+ - 输出表明未运行(`not running` / `stopped` / 无 PID)→ 直接进入下一步。
72
+
73
+ ### 步骤 4:准备配置信息
50
74
 
51
75
  | 参数 | 说明 | 示例 |
52
76
  |------|------|------|
53
77
  | `--aun-aid` | **必填**,Agent 自身的 AID,作为 bot 在 AUN 网络上的独立身份 | `mybot.agentid.pub` |
54
78
  | `--aun-owner` | **必填**,Owner 的 AID,用于接收欢迎消息和管理权限 | `alice.agentid.pub` |
55
- | `--default-path` | 默认项目路径(可选,默认当前目录) | `/home/user/myproject` |
79
+ | `--default-path` | 默认项目路径(bot 会话未显式 `/bind` 时使用的工作目录) | `/home/user/myproject` |
56
80
 
57
81
  **注意**:`--aun-aid` 和 `--aun-owner` 必须是不同的 AID。Agent 需要独立身份,不能与 Owner 相同。
58
82
 
59
83
  如果用户在触发安装时已提供 Owner AID(例如消息中包含"将 xxx 添加为 Owner ID"),直接使用该值,否则询问用户。Agent AID 必须单独询问用户,不能默认使用 Owner AID。
60
84
 
61
- ### 步骤 4:非交互式初始化
85
+ **默认项目路径必须询问用户,提供三个选项:**
86
+
87
+ 1. **推荐默认**:`~/projects/default`(EvolClaw 约定的默认工作区;若不存在会自动创建)
88
+ 2. **当前 Claude Code 运行目录**:即本次会话启动 `claude` 的工作目录(通过 `pwd` 获取)——适合希望 bot 直接在当前项目里工作的场景
89
+ 3. **自定义路径**:用户指定绝对路径(须是已存在的目录,或接受后自动创建)
90
+
91
+ 示例提问:
92
+
93
+ > 请选择默认项目目录(bot 未显式 `/bind` 时使用的工作目录):
94
+ > 1) `~/projects/default`(推荐,EvolClaw 默认工作区)
95
+ > 2) 当前目录:`<pwd 输出>`
96
+ > 3) 自定义路径(请提供绝对路径)
97
+
98
+ 用户未选择时,默认取选项 1。选项 3 需校验路径是绝对路径;目录不存在则 `mkdir -p` 创建。
99
+
100
+ ### 步骤 5:非交互式初始化
101
+
102
+ 将用户选择的目录作为 `--default-path` 传入:
62
103
 
63
104
  ```bash
64
105
  evolclaw init --non-interactive \
65
- --default-path "$PWD" \
106
+ --default-path <用户选择的绝对路径> \
66
107
  --channel aun \
67
108
  --aun-aid <AID> \
68
109
  --aun-owner <Owner AID>
@@ -76,7 +117,7 @@ evolclaw init --non-interactive \
76
117
  5. 写入初始 `agent.md`(`initialized: false`)
77
118
  6. 写入配置(含 owner 字段)
78
119
 
79
- ### 步骤 5:启动前验证
120
+ ### 步骤 6:启动前验证
80
121
 
81
122
  读取配置文件确认关键字段:
82
123
 
@@ -92,13 +133,13 @@ cat ~/.evolclaw/data/evolclaw.json
92
133
 
93
134
  如发现缺失或异常,向用户说明并提供修复方案。
94
135
 
95
- ### 步骤 6:启动服务
136
+ ### 步骤 7:启动服务
96
137
 
97
138
  ```bash
98
139
  evolclaw start
99
140
  ```
100
141
 
101
- ### 步骤 7:验证运行状态
142
+ ### 步骤 8:验证运行状态
102
143
 
103
144
  ```bash
104
145
  evolclaw status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",