evolclaw 3.1.2 → 3.1.4

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -6
  3. package/assets/.env.template +4 -0
  4. package/assets/config.json.template +6 -0
  5. package/assets/wechat-group-qr.jpeg +0 -0
  6. package/dist/agents/claude-runner.js +1 -1
  7. package/dist/agents/codex-runner.js +75 -19
  8. package/dist/agents/gemini-runner.js +0 -2
  9. package/dist/agents/kit-renderer.js +85 -22
  10. package/dist/aun/aid/agentmd.js +67 -74
  11. package/dist/aun/aid/client.js +22 -7
  12. package/dist/aun/aid/identity.js +314 -28
  13. package/dist/aun/aid/index.js +2 -2
  14. package/dist/aun/rpc/connection.js +8 -10
  15. package/dist/channels/aun.js +53 -41
  16. package/dist/cli/agent.js +28 -28
  17. package/dist/cli/bench.js +8 -14
  18. package/dist/cli/help.js +23 -0
  19. package/dist/cli/index.js +398 -73
  20. package/dist/cli/init-channel.js +2 -3
  21. package/dist/cli/init.js +13 -6
  22. package/dist/cli/link-rules.js +2 -1
  23. package/dist/cli/net-check.js +10 -11
  24. package/dist/core/command-handler.js +621 -541
  25. package/dist/core/evolagent.js +31 -0
  26. package/dist/core/message/im-renderer.js +10 -0
  27. package/dist/core/message/message-bridge.js +123 -24
  28. package/dist/core/message/message-processor.js +61 -31
  29. package/dist/core/relation/peer-identity.js +64 -21
  30. package/dist/core/session/session-manager.js +191 -44
  31. package/dist/core/trigger/manager.js +37 -0
  32. package/dist/index.js +4 -1
  33. package/dist/paths.js +87 -16
  34. package/dist/utils/npm-ops.js +18 -11
  35. package/kits/eck_manifest.json +9 -9
  36. package/kits/rules/02-navigation.md +1 -0
  37. package/kits/rules/05-venue.md +2 -2
  38. package/kits/rules/06-channel.md +2 -18
  39. package/kits/templates/system-fragments/baseagent.md +8 -2
  40. package/kits/templates/system-fragments/channel.md +20 -8
  41. package/kits/templates/system-fragments/identity.md +5 -6
  42. package/kits/templates/system-fragments/relation.md +10 -5
  43. package/kits/templates/system-fragments/session.md +20 -0
  44. package/kits/templates/system-fragments/venue.md +5 -3
  45. package/package.json +4 -2
  46. package/dist/net-check.js +0 -640
  47. package/dist/watch-msg.js +0 -544
  48. package/kits/templates/system-fragments/runtime.md +0 -19
@@ -1,4 +1,4 @@
1
- import { AUNClient, GatewayDiscovery, E2EEError } from '@agentunion/fastaun';
1
+ import { GatewayDiscovery, E2EEError } from '@agentunion/fastaun';
2
2
  import crypto from 'crypto';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
@@ -6,13 +6,14 @@ import os from 'os';
6
6
  import { logger, localTimestamp } from '../utils/logger.js';
7
7
  import { LogWriter } from '../utils/log-writer.js';
8
8
  import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
9
- import { resolvePaths, getPackageRoot, agentMdPath as agentMdPathFn, agentDir as agentDirPath } from '../paths.js';
9
+ import { resolvePaths, getPackageRoot, agentMdPath as agentMdPathFn, agentDir as agentDirPath, resolveRoot } from '../paths.js';
10
10
  import { saveToUploads, sanitizeFileName } from '../utils/media-cache.js';
11
11
  import { appendAidEvent } from '../utils/instance-registry.js';
12
12
  import { appendMessageLog, buildOutboundEntry } from '../core/message/message-log.js';
13
13
  import { chatDirPath } from '../core/session/session-fs-store.js';
14
14
  import { appendAidLifecycle } from '../aun/aid/identity.js';
15
- import { loadAgent, saveAgent } from '../config-store.js';
15
+ import { createAunClient } from '../aun/aid/client.js';
16
+ import { loadAgent, saveAgent, loadProcessConfig } from '../config-store.js';
16
17
  import { getProcessStartTime } from '../utils/process-introspect.js';
17
18
  import * as outbox from '../aun/outbox.js';
18
19
  import { guessMime, formatSize } from '../utils/media-cache.js';
@@ -445,6 +446,10 @@ export class AUNChannel {
445
446
  'text', 'quote', 'image', 'video', 'voice', 'file', 'json',
446
447
  'merge', 'link', 'location', 'personal_card',
447
448
  ]);
449
+ /** Menu protocol 请求类型:自定义消息快速路径,绕过白名单直接分发到 bridge */
450
+ static MENU_REQUEST_TYPES = new Set([
451
+ 'menu.list', 'menu.query', 'menu.options', 'menu.update', 'menu.action',
452
+ ]);
448
453
  // Reconnect state
449
454
  // SDK 自己跑无限指数退避(1s → 5min);TS 层只在 SDK 够不到的两类场景下接管:
450
455
  // 1. flap:短命 connected 反复出现(SDK 不记忆跨轮 base delay,会从 1s 重新开始)
@@ -541,9 +546,12 @@ export class AUNChannel {
541
546
  this.client = null;
542
547
  }
543
548
  this.connected = false;
544
- const aunPath = this.config.keystorePath || path.join(os.homedir(), '.aun');
549
+ const aunPath = this.config.keystorePath || resolveRoot();
545
550
  const aidName = this.config.aid;
546
- const encryptionSeed = this.config.encryptionSeed || process.env.AUN_ENCRYPTION_SEED || undefined;
551
+ const encryptionSeed = loadProcessConfig().aun?.encryptionSeed
552
+ || process.env.AUN_ENCRYPTION_SEED
553
+ || 'evol';
554
+ // Migration from ~/.aun is handled by ensureDataDirs() at startup with a marker file.
547
555
  // Gateway URL 解析:优先用配置的 gatewayUrl,否则通过 well-known 自动发现
548
556
  let gateway = this.config.gatewayUrl || '';
549
557
  if (!gateway) {
@@ -565,39 +573,39 @@ export class AUNChannel {
565
573
  logger.info(`${this.logPrefix()} Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
566
574
  // Create client with FileSecretStore (AES-256-GCM)
567
575
  // 不传 encryption_seed 时,SDK 自动从 {aun_path}/.seed 文件派生密钥(与 aun_cli.py 对齐)
568
- const rootCaPath = path.join(aunPath, 'CA', 'root', 'root.crt');
569
- this.client = new AUNClient({
570
- aun_path: aunPath,
571
- root_ca_path: rootCaPath,
572
- ...(encryptionSeed && { encryption_seed: encryptionSeed }),
573
- }, this.config.aunSdkLog ?? true);
576
+ const client = await createAunClient({
577
+ aunPath,
578
+ encryptionSeed,
579
+ aunSdkLog: this.config.aunSdkLog ?? true,
580
+ });
581
+ this.client = client;
574
582
  // Set gateway URL (internal property, same as Python SDK)
575
- this.client._gatewayUrl = gateway;
583
+ client._gatewayUrl = gateway;
576
584
  // Register event handlers before connecting
577
- this.client.on('message.received', (data) => {
585
+ client.on('message.received', (data) => {
578
586
  this.trace('IN', 'message.received', data);
579
587
  const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
580
588
  const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
581
589
  logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
582
590
  this.handleIncomingPrivateMessage(data);
583
591
  });
584
- this.client.on('group.message_created', (data) => {
592
+ client.on('group.message_created', (data) => {
585
593
  this.trace('IN', 'group.message_created', data);
586
594
  const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
587
595
  const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
588
596
  logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
589
597
  this.handleIncomingGroupMessage(data);
590
598
  });
591
- this.client.on('connection.state', (data) => {
599
+ client.on('connection.state', (data) => {
592
600
  // trace is handled inside handleConnectionState with throttling
593
601
  this.handleConnectionState(data);
594
602
  });
595
603
  // gateway 被踢/服务端主动断开(含同槽位互踢的 self/new extra_info)
596
- this.client.on('gateway.disconnect', (data) => {
604
+ client.on('gateway.disconnect', (data) => {
597
605
  this.trace('IN', 'gateway.disconnect', data);
598
606
  this.handleGatewayDisconnect(data);
599
607
  });
600
- this.client.on('message.recalled', (data) => {
608
+ client.on('message.recalled', (data) => {
601
609
  this.trace('IN', 'message.recalled', data);
602
610
  if (data && typeof data === 'object') {
603
611
  const ids = data.message_ids;
@@ -611,12 +619,12 @@ export class AUNChannel {
611
619
  }
612
620
  }
613
621
  });
614
- this.client.on('message.undecryptable', (data) => {
622
+ client.on('message.undecryptable', (data) => {
615
623
  this.trace('IN', 'message.undecryptable', data);
616
624
  const d = data;
617
625
  logger.warn(`${this.logPrefix()} Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
618
626
  });
619
- this.client.on('group.message_undecryptable', (data) => {
627
+ client.on('group.message_undecryptable', (data) => {
620
628
  this.trace('IN', 'group.message_undecryptable', data);
621
629
  const d = data;
622
630
  logger.warn(`${this.logPrefix()} Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
@@ -624,7 +632,7 @@ export class AUNChannel {
624
632
  // Authenticate
625
633
  // Workaround: SDK 0.3.x _loadIdentityOrRaise doesn't set identity.aid from requested aid,
626
634
  // causing gateway "missing aid" error. Patch to backfill aid on loaded identity.
627
- const authFlow = this.client._auth;
635
+ const authFlow = client._auth;
628
636
  if (authFlow && typeof authFlow._loadIdentityOrRaise === 'function') {
629
637
  const origLoad = authFlow._loadIdentityOrRaise.bind(authFlow);
630
638
  authFlow._loadIdentityOrRaise = (aid) => {
@@ -638,12 +646,12 @@ export class AUNChannel {
638
646
  try {
639
647
  logger.info(`${this.logPrefix()} Authenticating as ${aidName}...`);
640
648
  this.trace('OUT', 'auth.authenticate', { aid: aidName });
641
- const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
649
+ const auth = await client.auth.authenticate(aidName ? { aid: aidName } : undefined);
642
650
  this.trace('OUT', 'auth.authenticate.ok', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
643
651
  this.trace('IN', 'auth.result', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
644
652
  accessToken = auth.access_token;
645
653
  const resolvedGateway = auth.gateway || gateway;
646
- this.client._gatewayUrl = resolvedGateway;
654
+ client._gatewayUrl = resolvedGateway;
647
655
  logger.info(`${this.logPrefix()} Authenticated as ${auth.aid ?? '?'}, gateway=${resolvedGateway}`);
648
656
  }
649
657
  catch (e) {
@@ -670,12 +678,12 @@ export class AUNChannel {
670
678
  agentName: this.config.agentName,
671
679
  channelName: this.config.channelName,
672
680
  });
673
- this.trace('OUT', 'client.connect', { gateway: this.client._gatewayUrl, extra_info: extraInfo });
674
- await this.client.connect({ access_token: accessToken, gateway: this.client._gatewayUrl, extra_info: extraInfo },
681
+ this.trace('OUT', 'client.connect', { gateway: client._gatewayUrl, extra_info: extraInfo });
682
+ await client.connect({ access_token: accessToken, gateway: client._gatewayUrl, extra_info: extraInfo },
675
683
  // max_attempts=0 = 无限重试(与 Go/Python 对齐),交由 SDK 自己跑指数退避
676
684
  // initial_delay=1s,max_delay=300s(5min 封顶)
677
685
  { auto_reconnect: true, retry: { max_attempts: 0, initial_delay: 1.0, max_delay: 300.0 } });
678
- this.trace('OUT', 'client.connect.ok', { aid: this.client.aid });
686
+ this.trace('OUT', 'client.connect.ok', { aid: client.aid });
679
687
  this._aid = this.client.aid ?? undefined;
680
688
  const deviceId = this.client._device_id ?? '';
681
689
  this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
@@ -781,9 +789,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
781
789
  fs.mkdirSync(path.dirname(agentMdLocalPath), { recursive: true });
782
790
  fs.writeFileSync(agentMdLocalPath, newAgentMd, 'utf-8');
783
791
  logger.info(`${this.logPrefix()} Updated agent.md for ${aidName}`);
784
- // Publish to AUN network via auth.uploadAgentMd
792
+ // Publish to AUN network via publishAgentMd (auto-sign)
785
793
  try {
786
- await this.client.auth.uploadAgentMd(newAgentMd);
794
+ await this.client.publishAgentMd();
787
795
  logger.info(`${this.logPrefix()} Published agent.md to AUN network`);
788
796
  }
789
797
  catch (e) {
@@ -800,8 +808,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
800
808
  2. **查看帮助**:发送 \`/help\` 查看所有可用命令
801
809
  3. **切换项目**:发送 \`/project <项目名>\` 切换到其他项目
802
810
  4. **查看状态**:发送 \`/status\` 查看当前会话状态
803
- 5. **查看 Agent 信息**:发送 \`/agentmd\` 查看 agent.md 内容
804
- 6. **会话管理**:发送 \`/session\` 查看和切换会话
811
+ 5. **会话管理**:发送 \`/session\` 查看和切换会话
805
812
 
806
813
  💡 **提示**:
807
814
  - 直接发送消息即可与 Claude/Codex 对话
@@ -963,8 +970,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
963
970
  // action_card_reply 已在 extractTextPayload 中消费,不分发给 agent
964
971
  if (p2pPayloadType === 'action_card_reply')
965
972
  return;
966
- // menu.query:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
967
- if (p2pPayloadType === 'menu.query') {
973
+ // menu.* 协议:自定义消息快速路径,需要原始 payload JSON 传递给 bridge
974
+ if (AUNChannel.MENU_REQUEST_TYPES.has(p2pPayloadType)) {
968
975
  this.acknowledgeImmediately(messageId, seq);
969
976
  this.dispatchMessage({
970
977
  channelId: chatId, userId: fromAid,
@@ -1069,8 +1076,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
1069
1076
  {
1070
1077
  const payloadObj = (payload && typeof payload === 'object') ? payload : null;
1071
1078
  const payloadType = payloadObj?.type ?? '';
1072
- // menu.query:自定义消息快速路径
1073
- if (payloadType === 'menu.query') {
1079
+ // menu.* 协议:自定义消息快速路径
1080
+ if (AUNChannel.MENU_REQUEST_TYPES.has(payloadType)) {
1074
1081
  this.acknowledgeImmediately(messageId, seq);
1075
1082
  this.dispatchMessage({
1076
1083
  channelId: groupId, userId: senderAid,
@@ -2373,11 +2380,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2373
2380
  if (!this.client)
2374
2381
  return { type: null };
2375
2382
  try {
2376
- const md = await this.client.auth.downloadAgentMd(aid);
2377
- const typeMatch = md.match(/^type:\s*["']?(\w+)["']?/m);
2378
- const nameMatch = md.match(/^name:\s*["']?(.+?)["']?\s*$/m);
2379
- const type = typeMatch?.[1] === 'human' ? 'human' : 'ai';
2380
- const name = nameMatch?.[1]?.trim() || undefined;
2383
+ const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
2384
+ const identity = await PeerIdentityCache.resolve('aun', aid, selfAgentDir, this.client, false);
2385
+ const type = identity.type === 'human' ? 'human' : 'ai';
2386
+ const name = identity.name || undefined;
2381
2387
  const info = { type, name };
2382
2388
  this.peerInfoCache.set(aid, info);
2383
2389
  setTimeout(() => this.peerInfoCache.delete(aid), 30 * 60 * 1000);
@@ -2385,7 +2391,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2385
2391
  }
2386
2392
  catch (e) {
2387
2393
  logger.debug(`${this.logPrefix()} fetchPeerInfo failed for ${aid}: ${e}`);
2388
- return { type: null }; // no agent.md → unknown
2394
+ return { type: null };
2389
2395
  }
2390
2396
  }
2391
2397
  /** 同步取 peerInfo 缓存,未命中返回 undefined,不发起任何网络请求。 */
@@ -2401,12 +2407,18 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2401
2407
  async uploadAgentMd(content) {
2402
2408
  if (!this.client)
2403
2409
  throw new Error('not connected');
2404
- await this.client.auth.uploadAgentMd(content);
2410
+ const { agentMdPath } = await import('../paths.js');
2411
+ const localPath = agentMdPath(this.config.aid);
2412
+ fs.mkdirSync(path.dirname(localPath), { recursive: true });
2413
+ fs.writeFileSync(localPath, content, 'utf-8');
2414
+ await this.client.publishAgentMd();
2405
2415
  }
2406
2416
  async downloadAgentMd(aid) {
2407
2417
  if (!this.client)
2408
2418
  throw new Error('not connected');
2409
- return this.client.auth.downloadAgentMd(aid);
2419
+ const { agentmdSync } = await import('../aun/aid/agentmd.js');
2420
+ const result = await agentmdSync(aid, { client: this.client });
2421
+ return result.content ?? '';
2410
2422
  }
2411
2423
  }
2412
2424
  // Plugin implementation
package/dist/cli/agent.js CHANGED
@@ -2,12 +2,13 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import readline from 'readline';
5
- import { resolvePaths } from '../paths.js';
5
+ import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
6
6
  import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
7
7
  import { ipcQuery } from '../ipc.js';
8
8
  import { CONFIG_SCHEMA_VERSION } from '../types.js';
9
9
  import { isValidChannelName } from '../core/channel-loader.js';
10
10
  import { commandExists } from '../utils/cross-platform.js';
11
+ import { isCodexSdkAvailable } from '../agents/codex-runner.js';
11
12
  // ==================== Helpers ====================
12
13
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
13
14
  const BASEAGENT_ENV_KEY = {
@@ -15,8 +16,13 @@ const BASEAGENT_ENV_KEY = {
15
16
  codex: 'OPENAI_API_KEY',
16
17
  gemini: 'GEMINI_API_KEY',
17
18
  };
19
+ function isBaseagentAvailable(baseagent) {
20
+ if (baseagent === 'codex')
21
+ return isCodexSdkAvailable();
22
+ return commandExists(baseagent);
23
+ }
18
24
  function detectAvailableBaseagents() {
19
- return BASEAGENT_CANDIDATES.filter(b => commandExists(b));
25
+ return BASEAGENT_CANDIDATES.filter(isBaseagentAvailable);
20
26
  }
21
27
  function pickDefaultBaseagent(available) {
22
28
  if (available.length === 0)
@@ -40,12 +46,11 @@ function deriveAgentProjectPath(rootPath, aid) {
40
46
  return `${candidate}~${i}`;
41
47
  }
42
48
  function readAgentMdIdentity(aid) {
43
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
44
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
49
+ const agentMdFilePath = getAgentMdPathFromPaths(aid);
45
50
  try {
46
- if (!fs.existsSync(agentMdPath))
51
+ if (!fs.existsSync(agentMdFilePath))
47
52
  return { name: null, description: null };
48
- const content = fs.readFileSync(agentMdPath, 'utf-8');
53
+ const content = fs.readFileSync(agentMdFilePath, 'utf-8');
49
54
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
50
55
  if (!fmMatch)
51
56
  return { name: null, description: null };
@@ -62,8 +67,7 @@ function readAgentMdIdentity(aid) {
62
67
  }
63
68
  }
64
69
  function getAgentMdPath(aid) {
65
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
66
- return path.join(aunPath, 'AIDs', aid, 'agent.md');
70
+ return getAgentMdPathFromPaths(aid);
67
71
  }
68
72
  function getNestedValue(obj, keyPath) {
69
73
  const keys = keyPath.split('.');
@@ -316,7 +320,7 @@ export async function agentCreateInteractive(opts = {}) {
316
320
  // Baseagent
317
321
  const available = detectAvailableBaseagents();
318
322
  if (available.length === 0) {
319
- return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
323
+ return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or optional dependency @openai/codex-sdk.` };
320
324
  }
321
325
  const defaultBa = pickDefaultBaseagent(available);
322
326
  let baseagent;
@@ -333,7 +337,7 @@ export async function agentCreateInteractive(opts = {}) {
333
337
  continue;
334
338
  }
335
339
  if (!available.includes(input)) {
336
- console.log(` ${input} not detected on PATH. Available: ${available.join('/')}`);
340
+ console.log(` ${input} is not available in the current environment. Available: ${available.join('/')}`);
337
341
  continue;
338
342
  }
339
343
  chosen = input;
@@ -385,10 +389,8 @@ export async function agentCreateInteractive(opts = {}) {
385
389
  if (agentDescription) {
386
390
  content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
387
391
  }
388
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
389
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
390
- fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
391
- fs.writeFileSync(agentMdPath, content, 'utf-8');
392
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
393
+ // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
392
394
  // Upload with retry (3 attempts, 2s delay between retries)
393
395
  const MAX_ATTEMPTS = 3;
394
396
  const RETRY_DELAY_MS = 2000;
@@ -463,7 +465,7 @@ export async function agentCreateNonInteractive(opts) {
463
465
  // Baseagent
464
466
  const available = detectAvailableBaseagents();
465
467
  if (available.length === 0) {
466
- return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
468
+ return { ok: false, error: `No usable baseagent detected. Install claude/gemini CLI or optional dependency @openai/codex-sdk.` };
467
469
  }
468
470
  let baseagent;
469
471
  if (opts.baseagent) {
@@ -471,7 +473,7 @@ export async function agentCreateNonInteractive(opts) {
471
473
  return { ok: false, error: `Invalid baseagent: ${opts.baseagent} (options: ${BASEAGENT_CANDIDATES.join('/')})` };
472
474
  }
473
475
  if (!available.includes(opts.baseagent)) {
474
- return { ok: false, error: `${opts.baseagent} not detected on PATH (available: ${available.join('/')})` };
476
+ return { ok: false, error: `${opts.baseagent} is not available in the current environment (available: ${available.join('/')})` };
475
477
  }
476
478
  baseagent = opts.baseagent;
477
479
  }
@@ -541,10 +543,8 @@ export async function agentCreateNonInteractive(opts) {
541
543
  if (agentDescription) {
542
544
  content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
543
545
  }
544
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
545
- const agentMdPath = path.join(aunPath, 'AIDs', opts.aid, 'agent.md');
546
- fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
547
- fs.writeFileSync(agentMdPath, content, 'utf-8');
546
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
547
+ // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
548
548
  const MAX_ATTEMPTS = 3;
549
549
  const RETRY_DELAY_MS = 2000;
550
550
  let lastError;
@@ -592,11 +592,12 @@ export async function agentCreateNonInteractive(opts) {
592
592
  hotLoadError,
593
593
  };
594
594
  }
595
- // ==================== agentSyncAids ====================
595
+ // ==================== agentSyncAids (deprecated) ====================
596
+ /** @deprecated sync-aids 已废弃,不再从 CLI 调用 */
596
597
  export async function agentSyncAids() {
597
598
  const p = resolvePaths();
598
599
  const { aidList } = await import('../aun/aid/index.js');
599
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
600
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
600
601
  const allAids = aidList(aunPath);
601
602
  const localAids = allAids.filter(a => a.hasPrivateKey).map(a => a.aid);
602
603
  if (localAids.length === 0) {
@@ -862,12 +863,12 @@ export async function agentDelete(aid, purge = false) {
862
863
  }
863
864
  // ==================== agentRename ====================
864
865
  export async function agentRename(aid, newName) {
865
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
866
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
867
- if (!fs.existsSync(agentMdPath)) {
866
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
867
+ const agentMdFilePath = getAgentMdPathFromPaths(aid);
868
+ if (!fs.existsSync(agentMdFilePath)) {
868
869
  return { ok: false, error: `agent.md not found for ${aid}. Run: evolclaw aid agentmd put ${aid}` };
869
870
  }
870
- let content = fs.readFileSync(agentMdPath, 'utf-8');
871
+ let content = fs.readFileSync(agentMdFilePath, 'utf-8');
871
872
  const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
872
873
  if (!fmMatch) {
873
874
  return { ok: false, error: `agent.md has no valid frontmatter for ${aid}` };
@@ -882,8 +883,7 @@ export async function agentRename(aid, newName) {
882
883
  newFm = `name: "${newName}"\n${fm}`;
883
884
  }
884
885
  content = fmMatch[1] + newFm + fmMatch[3] + content.slice(fmMatch[0].length);
885
- fs.writeFileSync(agentMdPath, content, 'utf-8');
886
- // Upload
886
+ // agentmdPut 会写本地文件并 publishAgentMd
887
887
  let uploaded = false;
888
888
  try {
889
889
  const { agentmdPut } = await import('../aun/aid/index.js');
package/dist/cli/bench.js CHANGED
@@ -6,7 +6,9 @@ import { execFile } from 'child_process';
6
6
  import { promisify } from 'util';
7
7
  import { aidList, aidCreate } from '../aun/aid/identity.js';
8
8
  import { msgSend, msgPull } from '../aun/msg/index.js';
9
- import { getPackageRoot } from '../paths.js';
9
+ import { getPackageRoot, aunPath as defaultAunPath } from '../paths.js';
10
+ import { createAunClient } from '../aun/aid/client.js';
11
+ import { isHelpFlag } from './help.js';
10
12
  const execFileAsync = promisify(execFile);
11
13
  // ==================== ANSI ====================
12
14
  const GREEN = '\x1b[32m';
@@ -247,19 +249,11 @@ function filterBySize(results, received, cls, durationSec) {
247
249
  return computeMetrics(results.filter(r => r.sizeClass === cls), received.filter(r => r.sizeClass === cls), durationSec);
248
250
  }
249
251
  async function benchAuth(aids, concurrency, aunPath, slotId) {
250
- const { AUNClient } = await import('@agentunion/fastaun');
251
- const path = (await import('path')).default;
252
- const fs = (await import('fs')).default;
253
- const os = (await import('os')).default;
254
- const resolvedAunPath = aunPath ?? path.join(os.homedir(), '.aun');
255
- const caCertPath = path.join(resolvedAunPath, 'CA', 'root', 'root.crt');
252
+ const resolvedAunPath = aunPath ?? defaultAunPath();
256
253
  const tasks = aids.map(aid => async () => {
257
254
  const start = Date.now();
258
255
  try {
259
- const clientOpts = { aun_path: resolvedAunPath, debug: false };
260
- if (fs.existsSync(caCertPath))
261
- clientOpts.root_ca_path = caCertPath;
262
- const client = new AUNClient(clientOpts);
256
+ const client = await createAunClient({ aunPath: resolvedAunPath });
263
257
  await client.auth.createAid({ aid });
264
258
  const authResult = await client.auth.authenticate({ aid });
265
259
  const accessToken = authResult?.access_token ?? client._access_token;
@@ -363,7 +357,7 @@ async function cliAuth(aid, slotId) {
363
357
  }
364
358
  // ==================== Main Command ====================
365
359
  export async function cmdBench(args) {
366
- if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
360
+ if (isHelpFlag(args[0])) {
367
361
  console.log(`用法: evolclaw bench [options]
368
362
 
369
363
  AUN 消息系统性能基准测试。使用多个本地 AID 并发互发消息,
@@ -411,14 +405,14 @@ Options:
411
405
  const aids = [];
412
406
  // AID is usable if: has private key + cert not expired + key/cert public key match
413
407
  const { aidShow } = await import('../aun/aid/identity.js');
414
- const resolvedAunPath = aunPath ?? path.join(os.homedir(), '.aun');
408
+ const resolvedAunPath = aunPath ?? defaultAunPath();
415
409
  const usableAids = [];
416
410
  const skippedAids = [];
417
411
  for (const a of allAids) {
418
412
  if (!a.hasPrivateKey)
419
413
  continue;
420
414
  try {
421
- const info = aidShow(a.aid, { aunPath });
415
+ const info = await aidShow(a.aid, { aunPath });
422
416
  if (!info.certExpiresAt) {
423
417
  skippedAids.push({ aid: a.aid, reason: '无证书' });
424
418
  continue;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CLI help 检测 helper。
3
+ *
4
+ * 把 7 种历史写法统一为两个语义清晰的函数:
5
+ * - isHelpFlag(token):单 token 是否是 help 标记('help' / '--help' / '-h')
6
+ * - wantsHelp(args):args 任意位置出现 help 标记
7
+ *
8
+ * 两套 API 服务于不同语义:
9
+ * - 顶层路由(如 cmdAid 看到第一个 token 是子命令名时)必须用 isHelpFlag(sub),
10
+ * 否则 `ec aid delete --help` 会被顶层吞掉,永远到不了 delete 自己的 help。
11
+ * - 单层命令(如 cmdLinkRules、cmdAid 的具体 sub 处理块内)用 wantsHelp(args)
12
+ * 更宽松,任意位置都识别。
13
+ */
14
+ const HELP_TOKENS = new Set(['help', '--help', '-h']);
15
+ export function isHelpFlag(token) {
16
+ return token !== undefined && HELP_TOKENS.has(token);
17
+ }
18
+ export function wantsHelp(args) {
19
+ for (const a of args)
20
+ if (HELP_TOKENS.has(a))
21
+ return true;
22
+ return false;
23
+ }