evolclaw 3.1.4 → 3.1.6

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 (99) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/agents/claude-runner.js +398 -161
  3. package/dist/agents/kit-renderer.js +191 -25
  4. package/dist/aun/aid/agentmd.js +75 -103
  5. package/dist/aun/aid/client.js +1 -29
  6. package/dist/aun/aid/identity.js +105 -64
  7. package/dist/aun/aid/index.js +2 -1
  8. package/dist/aun/aid/store.js +74 -0
  9. package/dist/aun/msg/group.js +2 -2
  10. package/dist/aun/msg/p2p.js +26 -2
  11. package/dist/aun/rpc/connection.js +23 -30
  12. package/dist/channels/aun.js +174 -99
  13. package/dist/channels/dingtalk.js +2 -1
  14. package/dist/channels/feishu.js +301 -199
  15. package/dist/channels/qqbot.js +2 -1
  16. package/dist/channels/wechat.js +2 -1
  17. package/dist/channels/wecom.js +2 -1
  18. package/dist/cli/agent.js +21 -16
  19. package/dist/cli/bench.js +41 -28
  20. package/dist/cli/help.js +8 -0
  21. package/dist/cli/index.js +176 -87
  22. package/dist/cli/init-channel.js +5 -1
  23. package/dist/cli/init.js +37 -21
  24. package/dist/cli/link-rules.js +1 -7
  25. package/dist/cli/model.js +549 -0
  26. package/dist/cli/net-check.js +133 -50
  27. package/dist/cli/watch-msg.js +7 -7
  28. package/dist/cli/watch-web/debug-log.js +18 -0
  29. package/dist/cli/watch-web/server.js +306 -0
  30. package/dist/cli/watch-web/sources/aid.js +63 -0
  31. package/dist/cli/watch-web/sources/msg.js +70 -0
  32. package/dist/cli/watch-web/sources/session.js +638 -0
  33. package/dist/cli/watch-web/sources/types.js +10 -0
  34. package/dist/cli/watch-web/static/app.js +546 -0
  35. package/dist/cli/watch-web/static/index.html +54 -0
  36. package/dist/cli/watch-web/static/style.css +247 -0
  37. package/dist/config-store.js +1 -22
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +261 -133
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -22
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/im-renderer.js +9 -20
  44. package/dist/core/message/message-bridge.js +13 -9
  45. package/dist/core/message/message-log.js +2 -2
  46. package/dist/core/message/message-processor.js +211 -123
  47. package/dist/core/message/stream-idle-monitor.js +21 -0
  48. package/dist/core/model/model-catalog.js +215 -0
  49. package/dist/core/model/model-scope.js +250 -0
  50. package/dist/core/relation/peer-identity.js +58 -55
  51. package/dist/core/relation/peer-key.js +16 -0
  52. package/dist/core/session/session-fs-store.js +34 -55
  53. package/dist/core/session/session-key.js +24 -0
  54. package/dist/core/session/session-manager.js +308 -251
  55. package/dist/core/session/session-mapper.js +9 -4
  56. package/dist/core/trigger/manager.js +3 -3
  57. package/dist/core/trigger/parser.js +4 -4
  58. package/dist/core/trigger/scheduler.js +22 -7
  59. package/dist/index.js +61 -7
  60. package/dist/ipc.js +23 -1
  61. package/dist/utils/error-utils.js +6 -0
  62. package/dist/utils/process-introspect.js +7 -5
  63. package/kits/docs/GUIDE.md +2 -2
  64. package/kits/docs/INDEX.md +8 -8
  65. package/kits/docs/channels/aun.md +56 -17
  66. package/kits/docs/channels/feishu.md +41 -12
  67. package/kits/docs/context-assembly.md +182 -0
  68. package/kits/docs/evolclaw/INDEX.md +43 -0
  69. package/kits/docs/evolclaw/agent.md +49 -0
  70. package/kits/docs/evolclaw/aid.md +49 -0
  71. package/kits/docs/evolclaw/ctl.md +46 -0
  72. package/kits/docs/evolclaw/group.md +89 -0
  73. package/kits/docs/evolclaw/model.md +51 -0
  74. package/kits/docs/evolclaw/msg.md +91 -0
  75. package/kits/docs/evolclaw/rpc.md +35 -0
  76. package/kits/docs/evolclaw/storage.md +49 -0
  77. package/kits/docs/venues/aun-group.md +10 -0
  78. package/kits/docs/venues/aun-private.md +10 -0
  79. package/kits/docs/venues/client-desktop.md +10 -0
  80. package/kits/docs/venues/client-mobile.md +10 -0
  81. package/kits/docs/venues/feishu-group.md +13 -0
  82. package/kits/docs/venues/feishu-private.md +9 -0
  83. package/kits/docs/venues/group.md +23 -0
  84. package/kits/docs/venues/private.md +10 -0
  85. package/kits/eck_manifest.json +81 -36
  86. package/kits/rules/01-overview.md +20 -10
  87. package/kits/rules/06-channel.md +34 -27
  88. package/kits/templates/system-fragments/baseagent.md +7 -1
  89. package/kits/templates/system-fragments/channel.md +7 -5
  90. package/kits/templates/system-fragments/commands.md +19 -0
  91. package/kits/templates/system-fragments/session.md +19 -3
  92. package/kits/templates/system-fragments/venue.md +24 -0
  93. package/package.json +10 -5
  94. package/dist/aun/aid/lifecycle-log.js +0 -33
  95. package/dist/utils/aid-lifecycle-log.js +0 -33
  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
package/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import { migrateProject } from '../config-store.js';
11
11
  import { cmdInit } from './init.js';
12
12
  import { ipcQuery } from '../ipc.js';
13
13
  import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './init-channel.js';
14
- import { isHelpFlag, wantsHelp } from './help.js';
14
+ import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
15
15
  import * as platform from '../utils/cross-platform.js';
16
16
  import { EventBus } from '../core/event-bus.js';
17
17
  import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
@@ -292,6 +292,13 @@ async function cmdStart() {
292
292
  // 旧配置自动迁移(evolclaw.json → 新结构)
293
293
  const { autoMigrateIfNeeded } = await import('../config-store.js');
294
294
  autoMigrateIfNeeded();
295
+ // 未初始化时自动引导
296
+ const defaults = loadDefaults();
297
+ if (!defaults || !defaults.baseagents || Object.keys(defaults.baseagents).length === 0) {
298
+ console.log('⚡ 未检测到初始化配置,自动启动初始化向导...\n');
299
+ await cmdInit();
300
+ return;
301
+ }
295
302
  // 检查至少有一个 self-agent
296
303
  const { agents, skipped } = loadAllAgents();
297
304
  if (agents.length === 0) {
@@ -313,7 +320,7 @@ async function cmdStart() {
313
320
  const aliveMains = status.mains.filter(m => m.alive);
314
321
  if (aliveMains.length > 0) {
315
322
  const first = aliveMains[0];
316
- console.log(`❌ EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
323
+ console.log(` EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
317
324
  console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
318
325
  console.log(` 启动方式: ${first.record.launchedBy}`);
319
326
  // 报告 AID 状态
@@ -371,14 +378,7 @@ async function cmdStart() {
371
378
  const checkReady = () => {
372
379
  // ready signal 出现(优先检查,避免 Windows 上误判进程状态)
373
380
  if (fs.existsSync(p.readySignal)) {
374
- const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
375
- let aunVer = 'unknown';
376
- try {
377
- const aunPkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'node_modules', '@agentunion', 'fastaun', 'package.json'), 'utf-8'));
378
- aunVer = aunPkg.version;
379
- }
380
- catch { /* ignore */ }
381
- console.log(`✓ EvolClaw v${pkg.version} started successfully (PID: ${childPid}) fastaun v${aunVer}`);
381
+ console.log(`✓ EvolClaw started successfully (PID: ${childPid})`);
382
382
  console.log(` EVOLCLAW_HOME: ${resolveRoot()}`);
383
383
  console.log(` Logs: ${p.logs}/`);
384
384
  // 从主日志提取渠道连接摘要
@@ -495,7 +495,6 @@ async function cmdStop() {
495
495
  }
496
496
  async function cmdRestart(opts = {}) {
497
497
  const cmdStartedAt = Date.now();
498
- printStartupInfo();
499
498
  console.log('🔄 Restarting EvolClaw...');
500
499
  // 版本检查与自动升级
501
500
  console.log('📦 Checking for updates...');
@@ -884,8 +883,8 @@ async function cmdStatus() {
884
883
  const configChannelNames = new Set();
885
884
  for (const cfg of agents) {
886
885
  for (const inst of cfg.channels) {
887
- // effective key: <type>#<urlEncode(selfPeerId)>#<name>
888
- configChannelNames.add(`${inst.type}#${encodeURIComponent(cfg.aid)}#${inst.name}`);
886
+ // effective key: <type>#<selfAID>#<name>
887
+ configChannelNames.add(`${inst.type}#${cfg.aid}#${inst.name}`);
889
888
  }
890
889
  }
891
890
  for (const s of allSessions) {
@@ -1014,7 +1013,7 @@ async function cmdStatus() {
1014
1013
  }
1015
1014
  }
1016
1015
  /**
1017
- * 把 channel fingerprint 列表(`<type>#<selfPeerId>#<name>`)折叠成展示用摘要。
1016
+ * 把 channel fingerprint 列表(`<type>#<selfAID>#<name>`)折叠成展示用摘要。
1018
1017
  *
1019
1018
  * 聚合规则:
1020
1019
  * - 按 type 分组
@@ -1414,6 +1413,7 @@ async function cmdWatchMenu() {
1414
1413
  { key: 'log', label: 'log', desc: 'real-time log tail' },
1415
1414
  { key: 'aid', label: 'aid', desc: 'AID connection stats' },
1416
1415
  { key: 'msg', label: 'msg', desc: 'message inspector' },
1416
+ { key: 'web', label: 'web', desc: 'browser dashboard (aid/msg/session)' },
1417
1417
  ];
1418
1418
  let index = 0;
1419
1419
  const useColor = !!process.stdout.isTTY;
@@ -1477,6 +1477,9 @@ async function cmdWatchMenu() {
1477
1477
  const { cmdWatchMsg } = await import('./watch-msg.js');
1478
1478
  await cmdWatchMsg();
1479
1479
  }
1480
+ else if (chosen === 'web') {
1481
+ await cmdWatchWeb();
1482
+ }
1480
1483
  resolve();
1481
1484
  }
1482
1485
  };
@@ -2098,6 +2101,24 @@ async function cmdWatchAid() {
2098
2101
  }
2099
2102
  platform.onShutdown(cleanup);
2100
2103
  }
2104
+ async function cmdWatchWeb() {
2105
+ // ecweb 是独立插件包(可执行命令),按需安装。
2106
+ // 复用 npm-ops.npmInstallGlobal(含 EACCES→sudo 回退、Windows npm.cmd、超时)。
2107
+ const { execFileSync } = await import('child_process');
2108
+ const home = resolvePaths().root;
2109
+ if (!platform.commandExists('ecweb')) {
2110
+ process.stdout.write('📦 ecweb 未安装,正在从 npm 安装...\n');
2111
+ const { npmInstallGlobal } = await import('../utils/npm-ops.js');
2112
+ try {
2113
+ await npmInstallGlobal('ecweb');
2114
+ }
2115
+ catch (e) {
2116
+ process.stderr.write(`❌ 安装 ecweb 失败: ${e?.stderr || e?.message || e}\n 可手动安装: npm install -g ecweb\n`);
2117
+ process.exit(1);
2118
+ }
2119
+ }
2120
+ execFileSync('ecweb', ['--home', home], { stdio: 'inherit' });
2121
+ }
2101
2122
  async function cmdRestartMonitor() {
2102
2123
  const p = resolvePaths();
2103
2124
  const restartLog = path.join(p.logs, 'restart.log');
@@ -2472,14 +2493,13 @@ function archiveSelfHealLog(p, log) {
2472
2493
  * Searches across all channel types (feishu, wechat, aun) for a matching instance.
2473
2494
  */
2474
2495
  function resolveInstanceConfig(instanceName) {
2475
- // 新结构:channel key 是 <type>#<selfPeerId>#<name>,解析后从对应 agent 的 channels[] 找
2496
+ // 新结构:channel key 是 <type>#<selfAID>#<name>,解析后从对应 agent 的 channels[] 找
2476
2497
  const parts = instanceName.split('#');
2477
2498
  if (parts.length === 3) {
2478
- const [type, encodedSelfPeerId, name] = parts;
2479
- const selfPeerId = decodeURIComponent(encodedSelfPeerId);
2499
+ const [type, selfAID, name] = parts;
2480
2500
  const { agents } = loadAllAgents();
2481
- // AUN channel 的 selfPeerId 就是 agent.aid
2482
- const agent = agents.find(a => a.aid === selfPeerId);
2501
+ // AUN channel 的 selfAID 就是 agent.aid
2502
+ const agent = agents.find(a => a.aid === selfAID);
2483
2503
  if (!agent)
2484
2504
  return null;
2485
2505
  const inst = agent.channels.find((c) => c.type === type && c.name === name);
@@ -2625,8 +2645,6 @@ async function cmdMv(oldDir, newDir) {
2625
2645
  console.log('✓ 项目目录已移动');
2626
2646
  if (r.evolclawDbUpdated > 0)
2627
2647
  console.log(`✓ EvolClaw 会话存储已更新 (${r.evolclawDbUpdated} 条记录)`);
2628
- if (r.evolclawConfigUpdated)
2629
- console.log('✓ agent config projects.list 已更新');
2630
2648
  console.log('\n迁移完成!');
2631
2649
  }
2632
2650
  catch (e) {
@@ -2800,7 +2818,7 @@ Agent:
2800
2818
  // ==================== Agent ====================
2801
2819
  async function cmdAgent(args) {
2802
2820
  const sub = args[0];
2803
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
2821
+ const formatJson = getArgValue(args, '--format') === 'json';
2804
2822
  if (!sub || isHelpFlag(sub)) {
2805
2823
  console.log(`用法: evolclaw agent <command>
2806
2824
 
@@ -3375,7 +3393,7 @@ function resolveAunPath(args) {
3375
3393
  }
3376
3394
  async function cmdAid(args) {
3377
3395
  const sub = args[0];
3378
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3396
+ const formatJson = getArgValue(args, '--format') === 'json';
3379
3397
  const aunPath = resolveAunPath(args);
3380
3398
  if (!sub || isHelpFlag(sub)) {
3381
3399
  console.log(`用法: evolclaw aid <command>
@@ -3467,7 +3485,7 @@ Options:
3467
3485
  console.log('无匹配 AID');
3468
3486
  return;
3469
3487
  }
3470
- console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}:`);
3488
+ console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}(${aunPath ?? resolveRoot()}):`);
3471
3489
  for (const a of aids) {
3472
3490
  const keyIcon = a.hasPrivateKey ? '🔑' : ' ';
3473
3491
  let signIcon = ' ';
@@ -3533,40 +3551,63 @@ Options:
3533
3551
  }
3534
3552
  if (sub === 'new') {
3535
3553
  if (wantsHelp(args)) {
3536
- console.log(`用法: evolclaw aid new <完整AID>
3554
+ console.log(`用法: evolclaw aid new <完整AID> [--force]
3537
3555
 
3538
3556
  创建新 AID 身份:生成 ECDSA 密钥对、向 Issuer 申请证书、构建并上传初始 agent.md。
3539
3557
 
3540
- 例: evolclaw aid new reviewer.agentid.pub`);
3558
+ 选项:
3559
+ --force 强制重新注册,覆盖已存在的身份(即使签名验证失败)
3560
+
3561
+ 例: evolclaw aid new reviewer.agentid.pub
3562
+ evolclaw aid new reviewer.agentid.pub --force`);
3541
3563
  return;
3542
3564
  }
3543
3565
  const aid = args[1];
3566
+ const force = args.includes('--force');
3544
3567
  if (!aid) {
3545
- console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
3568
+ console.error('用法: evolclaw aid new <完整AID> [--force]\n例: evolclaw aid new reviewer.agentid.pub');
3546
3569
  process.exit(1);
3547
3570
  }
3548
3571
  if (!isValidAid(aid)) {
3549
3572
  console.error(`❌ 无效 AID 格式: ${aid}`);
3550
3573
  process.exit(1);
3551
3574
  }
3552
- const result = await aidCreate(aid, { aunPath });
3553
- if (!result.alreadyExisted) {
3554
- const content = buildInitialAgentMd({ aid });
3575
+ try {
3576
+ const result = await aidCreate(aid, { aunPath, force });
3577
+ if (!result.alreadyExisted) {
3578
+ const content = buildInitialAgentMd({ aid });
3579
+ try {
3580
+ await agentmdPut(content, { aid, aunPath });
3581
+ console.log('✓ agent.md 已发布');
3582
+ }
3583
+ catch (e) {
3584
+ console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
3585
+ }
3586
+ }
3555
3587
  try {
3556
- await agentmdPut(content, { aid, client: result.client, aunPath });
3557
- console.log('✓ agent.md 已发布');
3588
+ await result.client.close();
3558
3589
  }
3559
- catch (e) {
3560
- console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
3590
+ catch { }
3591
+ try {
3592
+ result.store?.close();
3561
3593
  }
3594
+ catch { }
3595
+ const verb = result.alreadyExisted ? '已存在且有效' : (force ? '已重新创建' : '已创建');
3596
+ console.log(`✓ ${aid} ${verb}`);
3597
+ console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3562
3598
  }
3563
- try {
3564
- await result.client.close();
3599
+ catch (e) {
3600
+ if (e.code === 'AID_INVALID') {
3601
+ console.error(`❌ ${e.message}`);
3602
+ process.exit(1);
3603
+ }
3604
+ if (e.code === -32052 || e.constructor?.name === 'IdentityConflictError') {
3605
+ console.error(`❌ AID ${aid} 已在服务端注册,但本地密钥无法匹配。\n` +
3606
+ `该 AID 可能由其他设备创建,无法在本地恢复。请选择其他名称。`);
3607
+ process.exit(1);
3608
+ }
3609
+ throw e;
3565
3610
  }
3566
- catch { }
3567
- const verb = result.alreadyExisted ? '已存在' : '已创建';
3568
- console.log(`✓ ${aid} ${verb}`);
3569
- console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3570
3611
  return;
3571
3612
  }
3572
3613
  if (sub === 'delete') {
@@ -3853,6 +3894,10 @@ async function cmdRpc(args) {
3853
3894
 
3854
3895
  每行 JSON 格式: {"method":"<namespace.method>","params":{...}}
3855
3896
 
3897
+ Options:
3898
+ --app <name> 指定应用 slot(独立消费通道)。仅对 message.pull / group.pull
3899
+ 等消费类方法有意义——隔离 seq 游标与消息过滤;默认与 daemon 共享通道。
3900
+
3856
3901
  示例:
3857
3902
  evolclaw rpc --as alice.agentid.pub --params '{"method":"message.send","params":{"to":"bob.agentid.pub","payload":{"type":"text","text":"hello"}}}'
3858
3903
  evolclaw rpc --as alice.agentid.pub --params calls.jsonl`);
@@ -3861,6 +3906,7 @@ async function cmdRpc(args) {
3861
3906
  const asIdx = args.indexOf('--as');
3862
3907
  const paramsIdx = args.indexOf('--params');
3863
3908
  const aunPath = resolveAunPath(args);
3909
+ const appSlot = getArgValue(args, '--app');
3864
3910
  if (asIdx === -1 || asIdx + 1 >= args.length) {
3865
3911
  console.error('❌ 缺少 --as <aid>');
3866
3912
  process.exit(1);
@@ -3905,11 +3951,11 @@ async function cmdRpc(args) {
3905
3951
  }
3906
3952
  const { rpcCall, rpcBatch } = await import('../aun/rpc/index.js');
3907
3953
  if (calls.length === 1) {
3908
- const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath });
3954
+ const result = await rpcCall(aid, calls[0].method, calls[0].params, { aunPath, slotId: appSlot });
3909
3955
  console.log(JSON.stringify(result));
3910
3956
  }
3911
3957
  else {
3912
- const results = await rpcBatch(aid, calls, { aunPath });
3958
+ const results = await rpcBatch(aid, calls, { aunPath, slotId: appSlot });
3913
3959
  for (const r of results) {
3914
3960
  console.log(JSON.stringify(r));
3915
3961
  }
@@ -3919,7 +3965,7 @@ async function cmdRpc(args) {
3919
3965
  async function cmdStorage(args) {
3920
3966
  const sub = args[0];
3921
3967
  const aunPath = resolveAunPath(args);
3922
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3968
+ const formatJson = getArgValue(args, '--format') === 'json';
3923
3969
  if (!sub || isHelpFlag(sub)) {
3924
3970
  console.log(`用法: evolclaw storage <command> <aid> [options]
3925
3971
 
@@ -4066,10 +4112,9 @@ Commands:
4066
4112
  async function cmdMsg(args) {
4067
4113
  const sub = args[0];
4068
4114
  const aunPath = resolveAunPath(args);
4069
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4115
+ const formatJson = getArgValue(args, '--format') === 'json';
4070
4116
  const appIdx = args.indexOf('--app');
4071
4117
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4072
- const asDaemon = args.includes('--as-daemon');
4073
4118
  if (!sub || isHelpFlag(sub)) {
4074
4119
  console.log(`用法: evolclaw msg <command> <from-aid> [args...] [options]
4075
4120
 
@@ -4079,19 +4124,20 @@ Commands:
4079
4124
  send <from> <to> --link <url> [--title T] 发送链接卡片
4080
4125
  send <from> <to> --payload <json> 发送自定义 payload
4081
4126
  pull <from> [--after-seq N] [--limit N] 拉取收件箱
4082
- ack <from> <seq> --app <name> 确认已读(必须传 --app)
4127
+ ack <from> <seq> [--app <name>] 确认已读
4083
4128
  recall <from> <message-id> [<message-id>...] 撤回消息
4084
4129
  online <from> <target-aid> [<target-aid>...] 查询在线状态
4085
4130
 
4086
4131
  Options:
4087
- --app <name> 指定应用 slot(隔离 ack 游标)
4088
- --as-daemon ack 时显式以 daemon 身份(高危,会污染 daemon 游标)
4132
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4089
4133
  --format json 输出 JSON 格式
4090
4134
  --encrypt 启用端到端加密
4091
4135
  --thread <id> 指定话题 ID(用于多话题路由)
4092
4136
  --content-type <mime> 显式覆盖 MIME(仅 --file 模式)
4093
4137
  --text <说明> 附件说明文字(仅 --file 模式)
4094
4138
  --transcript <text> 语音转写(仅 --as voice)
4139
+ -- end-of-options:其后所有参数按正文处理
4140
+ (用于发送恰好等于某 flag 的文本,如 send a b -- --encrypt)
4095
4141
 
4096
4142
  示例:
4097
4143
  evolclaw msg send alice.agentid.pub bob.agentid.pub "hello"
@@ -4211,7 +4257,7 @@ Options:
4211
4257
  }
4212
4258
  if (sub === 'pull') {
4213
4259
  if (!appSlot) {
4214
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot(可能与 daemon 看到同一批消息)');
4260
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
4215
4261
  }
4216
4262
  const afterSeqStr = getArgValue(args, '--after-seq');
4217
4263
  const limitStr = getArgValue(args, '--limit');
@@ -4253,7 +4299,7 @@ Options:
4253
4299
  if (sub === 'ack') {
4254
4300
  const seqStr = args[2];
4255
4301
  if (!seqStr) {
4256
- console.error('用法: evolclaw msg ack <from> <seq> --app <name>');
4302
+ console.error('用法: evolclaw msg ack <from> <seq> [--app <name>]');
4257
4303
  process.exit(1);
4258
4304
  }
4259
4305
  const seq = Number(seqStr);
@@ -4261,10 +4307,8 @@ Options:
4261
4307
  console.error(`❌ seq 必须是数字: ${seqStr}`);
4262
4308
  process.exit(1);
4263
4309
  }
4264
- if (!appSlot && !asDaemon) {
4265
- console.error(' ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
4266
- console.error(' 理由: 不传 --app 会推进 daemon 共享的 ack 游标,导致 daemon 丢消息');
4267
- process.exit(1);
4310
+ if (!appSlot) {
4311
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
4268
4312
  }
4269
4313
  const result = await msgAck({ from, seq, ...commonOpts });
4270
4314
  if (!result.ok) {
@@ -4352,10 +4396,9 @@ Options:
4352
4396
  async function cmdGroup(args) {
4353
4397
  const sub = args[0];
4354
4398
  const aunPath = resolveAunPath(args);
4355
- const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4399
+ const formatJson = getArgValue(args, '--format') === 'json';
4356
4400
  const appIdx = args.indexOf('--app');
4357
4401
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4358
- const asDaemon = args.includes('--as-daemon');
4359
4402
  if (!sub || isHelpFlag(sub)) {
4360
4403
  console.log(`用法: evolclaw group <command> <from-aid> [args...] [options]
4361
4404
 
@@ -4364,7 +4407,7 @@ async function cmdGroup(args) {
4364
4407
  send <from> <group-id> --file <path> [--as <type>] 发送群文件
4365
4408
  send <from> <group-id> --payload <json> 发送自定义 payload
4366
4409
  pull <from> <group-id> [--after-seq N] [--limit N] 拉取群消息
4367
- ack <from> <group-id> <seq> --app <name> 确认已读(必须传 --app)
4410
+ ack <from> <group-id> <seq> [--app <name>] 确认已读
4368
4411
 
4369
4412
  群管理:
4370
4413
  create <from> <name> [--visibility public|private] [--description D] [--join-mode M] 创建群
@@ -4382,11 +4425,13 @@ async function cmdGroup(args) {
4382
4425
  online <from> <group-id> 查看在线成员
4383
4426
 
4384
4427
  Options:
4385
- --app <name> 指定应用 slot(隔离 ack 游标)
4386
- --as-daemon ack 时显式以 daemon 身份(高危)
4428
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4387
4429
  --format json 输出 JSON 格式
4388
- --mention <aid> 发送时 @ 某个成员(可多次)
4430
+ --encrypt 启用端到端加密(仅 send)
4431
+ --mention <aid> 发送时 @ 某个成员(可多次,或用逗号分隔多个 aid)
4389
4432
  --mention-all 发送时 @ 所有人
4433
+ -- end-of-options:其后所有参数按正文处理
4434
+ (用于发送恰好等于某 flag 的文本,如 send a g -- --encrypt)
4390
4435
 
4391
4436
  示例:
4392
4437
  evolclaw group create alice.agentid.pub "Dev Team" --visibility private
@@ -4418,12 +4463,23 @@ Options:
4418
4463
  }
4419
4464
  return gid;
4420
4465
  };
4421
- // 收集 --mention(可多次)
4466
+ // 收集 --mention(可多次;每次的值支持逗号分隔多个 aid)
4422
4467
  const collectMentions = () => {
4423
4468
  const mentions = [];
4424
- for (let i = 0; i < args.length - 1; i++) {
4425
- if (args[i] === '--mention') {
4426
- mentions.push({ aid: args[i + 1] });
4469
+ for (let i = 0; i < args.length; i++) {
4470
+ if (args[i] !== '--mention')
4471
+ continue;
4472
+ const val = args[i + 1];
4473
+ if (val === undefined || val.startsWith('--')) {
4474
+ console.error(`❌ --mention 后面缺少 <aid>`);
4475
+ process.exit(1);
4476
+ }
4477
+ for (const aid of val.split(',').map(s => s.trim()).filter(Boolean)) {
4478
+ if (!isValidAid(aid)) {
4479
+ console.error(`❌ --mention 的 aid 无效: ${aid}`);
4480
+ process.exit(1);
4481
+ }
4482
+ mentions.push({ aid });
4427
4483
  }
4428
4484
  }
4429
4485
  if (args.includes('--mention-all')) {
@@ -4496,7 +4552,7 @@ Options:
4496
4552
  if (sub === 'pull') {
4497
4553
  const groupId = requireGroupId();
4498
4554
  if (!appSlot) {
4499
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot');
4555
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
4500
4556
  }
4501
4557
  const afterSeqStr = getArgValue(args, '--after-seq');
4502
4558
  const limitStr = getArgValue(args, '--limit');
@@ -4517,7 +4573,7 @@ Options:
4517
4573
  const groupId = requireGroupId();
4518
4574
  const seqStr = args[3];
4519
4575
  if (!seqStr) {
4520
- console.error('用法: evolclaw group ack <from> <group-id> <seq> --app <name>');
4576
+ console.error('用法: evolclaw group ack <from> <group-id> <seq> [--app <name>]');
4521
4577
  process.exit(1);
4522
4578
  }
4523
4579
  const seq = Number(seqStr);
@@ -4525,9 +4581,8 @@ Options:
4525
4581
  console.error(`❌ seq 必须是数字: ${seqStr}`);
4526
4582
  process.exit(1);
4527
4583
  }
4528
- if (!appSlot && !asDaemon) {
4529
- console.error(' group ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
4530
- process.exit(1);
4584
+ if (!appSlot) {
4585
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
4531
4586
  }
4532
4587
  const result = await groupAck({ from, groupId, seq, ...commonOpts });
4533
4588
  outputResult(result, () => {
@@ -4718,32 +4773,50 @@ Options:
4718
4773
  process.exit(1);
4719
4774
  }
4720
4775
  // ==================== Main ====================
4721
- function getArgValue(args, flag) {
4722
- const idx = args.indexOf(flag);
4723
- return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
4724
- }
4725
4776
  /**
4726
- * 收集位置参数(从 startIdx 开始),跳过 flag 及其值。
4727
- * 已知"取值"的 flag 会消耗下一个 arg;已知"开关"的 flag 只占自身。
4777
+ * 收集位置参数(从 startIdx 开始)。
4778
+ *
4779
+ * flag 判定采用**精确匹配已知 flag 集合**,而非 `startsWith('--')`——
4780
+ * 这样"正文恰好以 -- 开头"(如消息文本 `--file 坏了`)不会被误当 flag 吞掉。
4781
+ * 仅当 token 精确等于某个已知 flag 时才按 flag 处理:
4782
+ * - VALUE_FLAGS:消耗自身 + 下一个 arg(flag 的值)
4783
+ * - BOOLEAN_FLAGS:仅消耗自身
4784
+ * 其余以 -- 开头但不在集合中的 token,一律视为正文。
4785
+ *
4786
+ * 另支持 POSIX `--` end-of-options 分隔符:遇到单独的 `--` 后,
4787
+ * 其后所有 token 无条件按正文处理(用于发送精确等于某 flag 的文本,如 `-- --encrypt`)。
4728
4788
  */
4789
+ const VALUE_FLAGS = new Set([
4790
+ '--format', '--app', '--after-seq', '--limit', '--file', '--link',
4791
+ '--payload', '--title', '--description', '--text', '--transcript',
4792
+ '--as', '--content-type', '--mention', '--visibility', '--join-mode',
4793
+ '--group-id', '--name', '--message', '--answer', '--page', '--size',
4794
+ '--aun-path', '--thread',
4795
+ ]);
4796
+ const BOOLEAN_FLAGS = new Set([
4797
+ '--encrypt', '--mention-all',
4798
+ ]);
4729
4799
  function collectPositional(args, startIdx) {
4730
- const VALUE_FLAGS = new Set([
4731
- '--format', '--app', '--after-seq', '--limit', '--file', '--link',
4732
- '--payload', '--title', '--description', '--text', '--transcript',
4733
- '--as', '--content-type', '--mention', '--visibility', '--join-mode',
4734
- '--group-id', '--name', '--message', '--answer', '--page', '--size',
4735
- '--aun-path',
4736
- ]);
4737
4800
  const out = [];
4801
+ let endOfFlags = false;
4738
4802
  for (let i = startIdx; i < args.length; i++) {
4739
4803
  const a = args[i];
4740
- if (a.startsWith('--')) {
4741
- if (VALUE_FLAGS.has(a))
4742
- i++; // 跳过 flag 的值
4743
- // else: 开关 flag,自身已被跳过
4804
+ if (endOfFlags) {
4805
+ out.push(a);
4744
4806
  continue;
4745
4807
  }
4746
- out.push(a);
4808
+ if (a === '--') {
4809
+ endOfFlags = true;
4810
+ continue;
4811
+ }
4812
+ if (VALUE_FLAGS.has(a)) {
4813
+ i++;
4814
+ continue;
4815
+ } // 精确匹配取值 flag:跳过其值
4816
+ if (BOOLEAN_FLAGS.has(a)) {
4817
+ continue;
4818
+ } // 精确匹配开关 flag:仅跳过自身
4819
+ out.push(a); // 其余(含以 -- 开头的未知 token)= 正文
4747
4820
  }
4748
4821
  return out;
4749
4822
  }
@@ -4857,6 +4930,9 @@ export async function main(args) {
4857
4930
  else if (args[1] === 'log') {
4858
4931
  cmdWatch();
4859
4932
  }
4933
+ else if (args[1] === 'web' || args[1] === 'session') {
4934
+ await cmdWatchWeb();
4935
+ }
4860
4936
  else if (!args[1]) {
4861
4937
  await cmdWatchMenu();
4862
4938
  }
@@ -4920,6 +4996,11 @@ export async function main(args) {
4920
4996
  await cmdGroup(args.slice(1));
4921
4997
  break;
4922
4998
  }
4999
+ case 'model': {
5000
+ const { cmdModel } = await import('./model.js');
5001
+ await cmdModel(args.slice(1));
5002
+ break;
5003
+ }
4923
5004
  case 'bench': {
4924
5005
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
4925
5006
  suppressSdkLogs();
@@ -4970,6 +5051,14 @@ Commands:
4970
5051
  --force (覆盖已有 config.json)
4971
5052
  agent reload 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
4972
5053
  agent reload <n> 热重载指定 agent 配置
5054
+ model 模型管理(查看/切换,作用域:全局/agent/关系/会话)
5055
+ model list 列出可用模型,标注各作用域命中
5056
+ model current 显示实际生效的模型 + 来源
5057
+ model info <id> 查看单个模型详情(价格/上下文/模态等)
5058
+ model use <id> 切换模型(--self/--peer/--session 决定作用域)
5059
+ model effort <lv> 设置推理强度
5060
+ model reset 清除指定作用域设置,回落上一级
5061
+ model help 查看完整用法
4973
5062
  aid AID 身份管理
4974
5063
  aid list 列出本地所有 AID
4975
5064
  aid show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
@@ -463,7 +463,7 @@ export async function setupAunAid(rl, _config) {
463
463
  const agentType = typeInput === 'human' ? 'human' : 'ai';
464
464
  const content = buildInitialAgentMd({ aid, type: agentType });
465
465
  try {
466
- await agentmdPut(content, { aid, client: result.client });
466
+ await agentmdPut(content, { aid });
467
467
  console.log(' ✓ agent.md 已发布并写入本地');
468
468
  }
469
469
  catch (e) {
@@ -484,6 +484,10 @@ export async function setupAunAid(rl, _config) {
484
484
  await result.client.close();
485
485
  }
486
486
  catch { /* ignore */ }
487
+ try {
488
+ result.store?.close();
489
+ }
490
+ catch { /* ignore */ }
487
491
  }
488
492
  catch (e) {
489
493
  const msg = e.message || String(e);