evolclaw 3.1.3 → 3.1.5

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 (100) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/assets/.env.template +4 -0
  3. package/assets/config.json.template +6 -0
  4. package/assets/wechat-group-qr.jpeg +0 -0
  5. package/dist/agents/claude-runner.js +348 -156
  6. package/dist/agents/kit-renderer.js +211 -42
  7. package/dist/aun/aid/agentmd.js +75 -139
  8. package/dist/aun/aid/client.js +1 -14
  9. package/dist/aun/aid/identity.js +381 -54
  10. package/dist/aun/aid/index.js +3 -2
  11. package/dist/aun/aid/store.js +74 -0
  12. package/dist/aun/msg/p2p.js +26 -2
  13. package/dist/aun/rpc/connection.js +23 -35
  14. package/dist/channels/aun.js +92 -144
  15. package/dist/channels/dingtalk.js +1 -0
  16. package/dist/channels/feishu.js +270 -190
  17. package/dist/channels/qqbot.js +1 -0
  18. package/dist/channels/wechat.js +1 -0
  19. package/dist/channels/wecom.js +1 -0
  20. package/dist/cli/agent.js +26 -27
  21. package/dist/cli/bench.js +45 -34
  22. package/dist/cli/help.js +23 -0
  23. package/dist/cli/index.js +538 -77
  24. package/dist/cli/init-channel.js +7 -4
  25. package/dist/cli/link-rules.js +2 -1
  26. package/dist/cli/model.js +324 -0
  27. package/dist/cli/net-check.js +138 -56
  28. package/dist/cli/watch-msg.js +7 -7
  29. package/dist/cli/watch-web/debug-log.js +18 -0
  30. package/dist/cli/watch-web/server.js +306 -0
  31. package/dist/cli/watch-web/sources/aid.js +63 -0
  32. package/dist/cli/watch-web/sources/msg.js +70 -0
  33. package/dist/cli/watch-web/sources/session.js +638 -0
  34. package/dist/cli/watch-web/sources/types.js +10 -0
  35. package/dist/cli/watch-web/static/app.js +546 -0
  36. package/dist/cli/watch-web/static/index.html +54 -0
  37. package/dist/cli/watch-web/static/style.css +247 -0
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +87 -93
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -4
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/message-bridge.js +6 -6
  44. package/dist/core/message/message-log.js +2 -2
  45. package/dist/core/message/message-processor.js +104 -118
  46. package/dist/core/message/stream-idle-monitor.js +21 -0
  47. package/dist/core/model/model-catalog.js +215 -0
  48. package/dist/core/model/model-scope.js +250 -0
  49. package/dist/core/relation/peer-identity.js +78 -44
  50. package/dist/core/relation/peer-key.js +16 -0
  51. package/dist/core/session/session-fs-store.js +34 -55
  52. package/dist/core/session/session-key.js +24 -0
  53. package/dist/core/session/session-manager.js +312 -251
  54. package/dist/core/session/session-mapper.js +9 -4
  55. package/dist/core/trigger/manager.js +37 -0
  56. package/dist/core/trigger/scheduler.js +2 -1
  57. package/dist/index.js +10 -3
  58. package/dist/ipc.js +22 -0
  59. package/dist/paths.js +87 -16
  60. package/dist/utils/npm-ops.js +18 -11
  61. package/kits/docs/GUIDE.md +2 -2
  62. package/kits/docs/INDEX.md +11 -7
  63. package/kits/docs/channels/aun.md +56 -17
  64. package/kits/docs/channels/feishu.md +41 -12
  65. package/kits/docs/context-assembly.md +181 -0
  66. package/kits/docs/evolclaw/agent.md +49 -0
  67. package/kits/docs/evolclaw/aid.md +49 -0
  68. package/kits/docs/evolclaw/ctl.md +46 -0
  69. package/kits/docs/evolclaw/group.md +82 -0
  70. package/kits/docs/evolclaw/msg.md +86 -0
  71. package/kits/docs/evolclaw/rpc.md +35 -0
  72. package/kits/docs/evolclaw/storage.md +49 -0
  73. package/kits/docs/venues/aun-group.md +10 -0
  74. package/kits/docs/venues/aun-private.md +10 -0
  75. package/kits/docs/venues/client-desktop.md +10 -0
  76. package/kits/docs/venues/client-mobile.md +10 -0
  77. package/kits/docs/venues/feishu-group.md +13 -0
  78. package/kits/docs/venues/feishu-private.md +9 -0
  79. package/kits/docs/venues/group.md +11 -0
  80. package/kits/docs/venues/private.md +10 -0
  81. package/kits/eck_manifest.json +75 -39
  82. package/kits/rules/01-overview.md +20 -10
  83. package/kits/rules/05-venue.md +2 -2
  84. package/kits/rules/06-channel.md +30 -27
  85. package/kits/templates/system-fragments/baseagent.md +7 -1
  86. package/kits/templates/system-fragments/channel.md +4 -1
  87. package/kits/templates/system-fragments/identity.md +4 -4
  88. package/kits/templates/system-fragments/relation.md +8 -5
  89. package/kits/templates/system-fragments/session.md +27 -0
  90. package/kits/templates/system-fragments/venue.md +13 -1
  91. package/package.json +13 -6
  92. package/dist/aun/aid/lifecycle-log.js +0 -33
  93. package/dist/net-check.js +0 -640
  94. package/dist/utils/aid-lifecycle-log.js +0 -33
  95. package/dist/watch-msg.js +0 -544
  96. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  97. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  98. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  99. package/kits/docs/evolclaw/tools.md +0 -25
  100. package/kits/templates/system-fragments/eckruntime.md +0 -14
package/dist/cli/agent.js CHANGED
@@ -2,7 +2,7 @@ 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';
@@ -46,12 +46,11 @@ function deriveAgentProjectPath(rootPath, aid) {
46
46
  return `${candidate}~${i}`;
47
47
  }
48
48
  function readAgentMdIdentity(aid) {
49
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
50
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
49
+ const agentMdFilePath = getAgentMdPathFromPaths(aid);
51
50
  try {
52
- if (!fs.existsSync(agentMdPath))
51
+ if (!fs.existsSync(agentMdFilePath))
53
52
  return { name: null, description: null };
54
- const content = fs.readFileSync(agentMdPath, 'utf-8');
53
+ const content = fs.readFileSync(agentMdFilePath, 'utf-8');
55
54
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
56
55
  if (!fmMatch)
57
56
  return { name: null, description: null };
@@ -68,8 +67,7 @@ function readAgentMdIdentity(aid) {
68
67
  }
69
68
  }
70
69
  function getAgentMdPath(aid) {
71
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
72
- return path.join(aunPath, 'AIDs', aid, 'agent.md');
70
+ return getAgentMdPathFromPaths(aid);
73
71
  }
74
72
  function getNestedValue(obj, keyPath) {
75
73
  const keys = keyPath.split('.');
@@ -391,10 +389,8 @@ export async function agentCreateInteractive(opts = {}) {
391
389
  if (agentDescription) {
392
390
  content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
393
391
  }
394
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
395
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
396
- fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
397
- fs.writeFileSync(agentMdPath, content, 'utf-8');
392
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
393
+ // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
398
394
  // Upload with retry (3 attempts, 2s delay between retries)
399
395
  const MAX_ATTEMPTS = 3;
400
396
  const RETRY_DELAY_MS = 2000;
@@ -423,11 +419,14 @@ export async function agentCreateInteractive(opts = {}) {
423
419
  catch (e) {
424
420
  console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
425
421
  }
426
- // Attempt hot-load via IPC (if daemon is running)
422
+ // Attempt hot-load via IPC (if daemon is running).
423
+ // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
424
+ // >3s, so use a generous timeout to avoid a false "service not running"
425
+ // report while the daemon actually finishes bringing the agent online.
427
426
  let hotLoaded = false;
428
427
  let hotLoadError;
429
428
  try {
430
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid });
429
+ const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
431
430
  if (ipcResult?.ok) {
432
431
  hotLoaded = true;
433
432
  }
@@ -547,10 +546,8 @@ export async function agentCreateNonInteractive(opts) {
547
546
  if (agentDescription) {
548
547
  content = content.replace(/^description:\s*".*?"$/m, `description: "${agentDescription}"`);
549
548
  }
550
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
551
- const agentMdPath = path.join(aunPath, 'AIDs', opts.aid, 'agent.md');
552
- fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
553
- fs.writeFileSync(agentMdPath, content, 'utf-8');
549
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
550
+ // agentmdPut 会写本地文件到 agentMdPath(aid) 并调用 publishAgentMd
554
551
  const MAX_ATTEMPTS = 3;
555
552
  const RETRY_DELAY_MS = 2000;
556
553
  let lastError;
@@ -575,11 +572,14 @@ export async function agentCreateNonInteractive(opts) {
575
572
  catch (e) {
576
573
  console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
577
574
  }
578
- // Attempt hot-load via IPC (if daemon is running)
575
+ // Attempt hot-load via IPC (if daemon is running).
576
+ // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
577
+ // >3s, so use a generous timeout to avoid a false "service not running"
578
+ // report while the daemon actually finishes bringing the agent online.
579
579
  let hotLoaded = false;
580
580
  let hotLoadError;
581
581
  try {
582
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid });
582
+ const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
583
583
  if (ipcResult?.ok) {
584
584
  hotLoaded = true;
585
585
  }
@@ -603,7 +603,7 @@ export async function agentCreateNonInteractive(opts) {
603
603
  export async function agentSyncAids() {
604
604
  const p = resolvePaths();
605
605
  const { aidList } = await import('../aun/aid/index.js');
606
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
606
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
607
607
  const allAids = aidList(aunPath);
608
608
  const localAids = allAids.filter(a => a.hasPrivateKey).map(a => a.aid);
609
609
  if (localAids.length === 0) {
@@ -835,7 +835,7 @@ export async function agentChannelUpsert(opts) {
835
835
  return {
836
836
  ok: true,
837
837
  aid: opts.aid,
838
- channelKey: `${opts.channel.type}#${encodeURIComponent(opts.aid)}#${opts.channel.name}`,
838
+ channelKey: `${opts.channel.type}#${opts.aid}#${opts.channel.name}`,
839
839
  reloaded,
840
840
  };
841
841
  }
@@ -869,12 +869,12 @@ export async function agentDelete(aid, purge = false) {
869
869
  }
870
870
  // ==================== agentRename ====================
871
871
  export async function agentRename(aid, newName) {
872
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
873
- const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
874
- if (!fs.existsSync(agentMdPath)) {
872
+ const aunPath = process.env.AUN_HOME || defaultAunPath();
873
+ const agentMdFilePath = getAgentMdPathFromPaths(aid);
874
+ if (!fs.existsSync(agentMdFilePath)) {
875
875
  return { ok: false, error: `agent.md not found for ${aid}. Run: evolclaw aid agentmd put ${aid}` };
876
876
  }
877
- let content = fs.readFileSync(agentMdPath, 'utf-8');
877
+ let content = fs.readFileSync(agentMdFilePath, 'utf-8');
878
878
  const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
879
879
  if (!fmMatch) {
880
880
  return { ok: false, error: `agent.md has no valid frontmatter for ${aid}` };
@@ -889,8 +889,7 @@ export async function agentRename(aid, newName) {
889
889
  newFm = `name: "${newName}"\n${fm}`;
890
890
  }
891
891
  content = fmMatch[1] + newFm + fmMatch[3] + content.slice(fmMatch[0].length);
892
- fs.writeFileSync(agentMdPath, content, 'utf-8');
893
- // Upload
892
+ // agentmdPut 会写本地文件并 publishAgentMd
894
893
  let uploaded = false;
895
894
  try {
896
895
  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 { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
11
+ import { isHelpFlag } from './help.js';
10
12
  const execFileAsync = promisify(execFile);
11
13
  // ==================== ANSI ====================
12
14
  const GREEN = '\x1b[32m';
@@ -246,42 +248,48 @@ function computeMetrics(results, received, durationSec) {
246
248
  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
- 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');
256
- const tasks = aids.map(aid => async () => {
251
+ async function benchAuth(aids, concurrency, aunPath) {
252
+ const tasks = aids.map((aid, index) => async () => {
257
253
  const start = Date.now();
254
+ const slotId = `${SLOT.bench}-${index}`;
255
+ let store = null;
256
+ let client = null;
258
257
  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);
263
- await client.auth.createAid({ aid });
264
- const authResult = await client.auth.authenticate({ aid });
265
- const accessToken = authResult?.access_token ?? client._access_token;
266
- const gateway = client._gatewayUrl ?? authResult?.gateway ?? '';
267
- await client.connect({ access_token: accessToken, gateway, slot_id: slotId ?? '', connection_kind: 'short' }, { auto_reconnect: false });
258
+ store = await getAidStore({ slotId, aunPath });
259
+ client = await loadClient(store, aid);
260
+ await client.connect({ auto_reconnect: false });
261
+ const gateway = ''; // Gateway URL not exposed in 0.4.3
262
+ const authMs = Date.now() - start;
268
263
  try {
269
264
  await client.close();
270
265
  }
271
266
  catch { }
272
- return { aid, ok: true, authMs: Date.now() - start, gateway };
267
+ try {
268
+ store.close();
269
+ }
270
+ catch { }
271
+ return { aid, ok: true, authMs, gateway };
273
272
  }
274
273
  catch (e) {
274
+ if (client)
275
+ try {
276
+ await client.close();
277
+ }
278
+ catch { }
279
+ if (store)
280
+ try {
281
+ store.close();
282
+ }
283
+ catch { }
275
284
  return { aid, ok: false, authMs: Date.now() - start, error: e.message };
276
285
  }
277
286
  });
278
287
  return runPool(tasks, concurrency);
279
288
  }
280
- async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
289
+ async function benchSwitch(aids, rounds, aunPath, useCli, encrypt) {
281
290
  const results = [];
282
291
  const start = Date.now();
283
292
  let seq = 0;
284
- const slot = slotId ?? 'bench';
285
293
  for (let round = 0; round < rounds; round++) {
286
294
  for (let i = 0; i < aids.length; i++) {
287
295
  const from = aids[i];
@@ -292,8 +300,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
292
300
  let ok = false;
293
301
  let serverTimestamp;
294
302
  if (useCli) {
303
+ const slotId = `${SLOT.bench}-${i}`;
295
304
  try {
296
- const res = await withTimeout(cliSend(from, to, text, slot, encrypt), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
305
+ const res = await withTimeout(cliSend(from, to, text, slotId, encrypt), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
297
306
  ok = res.ok;
298
307
  serverTimestamp = res.timestamp;
299
308
  }
@@ -302,8 +311,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
302
311
  }
303
312
  }
304
313
  else {
314
+ const slotId = `${SLOT.bench}-${i}`;
305
315
  try {
306
- const res = await withTimeout(msgSend({ from, to, body: { mode: 'text', text }, slotId: slot, aunPath, encrypt }), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
316
+ const res = await withTimeout(msgSend({ from, to, body: { mode: 'text', text }, slotId, aunPath, encrypt }), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
307
317
  ok = res.ok;
308
318
  serverTimestamp = res.ok ? res.timestamp : undefined;
309
319
  }
@@ -363,7 +373,7 @@ async function cliAuth(aid, slotId) {
363
373
  }
364
374
  // ==================== Main Command ====================
365
375
  export async function cmdBench(args) {
366
- if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
376
+ if (isHelpFlag(args[0])) {
367
377
  console.log(`用法: evolclaw bench [options]
368
378
 
369
379
  AUN 消息系统性能基准测试。使用多个本地 AID 并发互发消息,
@@ -398,7 +408,6 @@ Options:
398
408
  const defaultWait = encrypt ? '10' : '3';
399
409
  const waitSec = Math.max(1, parseInt(getArgValue(args, '--wait') || defaultWait, 10));
400
410
  const sessionId = crypto.randomBytes(4).toString('hex');
401
- const benchSlot = `bench-${sessionId}`;
402
411
  if (!formatJson) {
403
412
  console.log(`\n${BOLD} evolclaw bench${RST} — AUN 消息性能基准测试`);
404
413
  console.log(` ${'━'.repeat(50)}`);
@@ -411,14 +420,14 @@ Options:
411
420
  const aids = [];
412
421
  // AID is usable if: has private key + cert not expired + key/cert public key match
413
422
  const { aidShow } = await import('../aun/aid/identity.js');
414
- const resolvedAunPath = aunPath ?? path.join(os.homedir(), '.aun');
423
+ const resolvedAunPath = aunPath ?? defaultAunPath();
415
424
  const usableAids = [];
416
425
  const skippedAids = [];
417
426
  for (const a of allAids) {
418
427
  if (!a.hasPrivateKey)
419
428
  continue;
420
429
  try {
421
- const info = aidShow(a.aid, { aunPath });
430
+ const info = await aidShow(a.aid, { aunPath });
422
431
  if (!info.certExpiresAt) {
423
432
  skippedAids.push({ aid: a.aid, reason: '无证书' });
424
433
  continue;
@@ -501,14 +510,15 @@ Options:
501
510
  authTasks.push(...aids);
502
511
  let authResults;
503
512
  if (cliMode) {
504
- const cliAuthTasks = authTasks.map(aid => async () => {
505
- const r = await cliAuth(aid, benchSlot);
513
+ const cliAuthTasks = authTasks.map((aid, index) => async () => {
514
+ const slotId = `${SLOT.bench}-${index}`;
515
+ const r = await cliAuth(aid, slotId);
506
516
  return { aid, ok: r.ok, authMs: r.authMs };
507
517
  });
508
518
  authResults = await runPool(cliAuthTasks, concurrency);
509
519
  }
510
520
  else {
511
- authResults = await benchAuth(authTasks, concurrency, aunPath, benchSlot);
521
+ authResults = await benchAuth(authTasks, concurrency, aunPath);
512
522
  }
513
523
  const authOk = authResults.filter(r => r.ok);
514
524
  const authFail = authResults.filter(r => !r.ok);
@@ -598,7 +608,7 @@ Options:
598
608
  // Suppress SDK error logs during send phase (we track errors ourselves)
599
609
  const origError2 = console.error;
600
610
  console.error = () => { };
601
- const sendFns = tasks.map(t => async () => {
611
+ const sendFns = tasks.map((t, taskIndex) => async () => {
602
612
  let lastError;
603
613
  let retries = 0;
604
614
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -611,10 +621,11 @@ Options:
611
621
  ? buildFileChunkText(t.seq, fileChunks.length, sendTs, sessionId, fileChunks[t.seq]?.toString('base64') ?? '')
612
622
  : buildMessageText(t.seq, t.sizeClass, sendTs, sessionId);
613
623
  const t0 = Date.now();
624
+ const slotId = `${SLOT.bench}-${taskIndex % concurrency}`;
614
625
  try {
615
626
  const sendPromise = cliMode
616
- ? cliSend(t.from, t.to, text, benchSlot, encrypt)
617
- : msgSend({ from: t.from, to: t.to, body: { mode: 'text', text }, slotId: benchSlot, aunPath, encrypt });
627
+ ? cliSend(t.from, t.to, text, slotId, encrypt)
628
+ : msgSend({ from: t.from, to: t.to, body: { mode: 'text', text }, slotId, aunPath, encrypt });
618
629
  const res = await withTimeout(sendPromise, SEND_TIMEOUT_MS, `${t.from.split('.')[0]}→${t.to.split('.')[0]}`);
619
630
  if (cliMode) {
620
631
  const cliRes = res;
@@ -928,7 +939,7 @@ Options:
928
939
  if (!formatJson)
929
940
  console.log(`${DIM} Phase 5: 频繁切换账号收发测试${RST}`);
930
941
  const switchRounds = Math.max(2, Math.min(rounds, 5));
931
- const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, benchSlot, encrypt);
942
+ const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, encrypt);
932
943
  const switchOkResults = switchOut.results.filter(r => r.ok);
933
944
  const switchLatencies = switchOkResults
934
945
  .filter(r => r.serverTimestamp !== undefined)
@@ -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
+ }