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/index.js CHANGED
@@ -11,6 +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
15
  import * as platform from '../utils/cross-platform.js';
15
16
  import { EventBus } from '../core/event-bus.js';
16
17
  import { tryUpgrade, tryUpgradeAunSdk } from '../utils/npm-ops.js';
@@ -883,8 +884,8 @@ async function cmdStatus() {
883
884
  const configChannelNames = new Set();
884
885
  for (const cfg of agents) {
885
886
  for (const inst of cfg.channels) {
886
- // effective key: <type>#<urlEncode(selfPeerId)>#<name>
887
- configChannelNames.add(`${inst.type}#${encodeURIComponent(cfg.aid)}#${inst.name}`);
887
+ // effective key: <type>#<selfAID>#<name>
888
+ configChannelNames.add(`${inst.type}#${cfg.aid}#${inst.name}`);
888
889
  }
889
890
  }
890
891
  for (const s of allSessions) {
@@ -1013,7 +1014,7 @@ async function cmdStatus() {
1013
1014
  }
1014
1015
  }
1015
1016
  /**
1016
- * 把 channel fingerprint 列表(`<type>#<selfPeerId>#<name>`)折叠成展示用摘要。
1017
+ * 把 channel fingerprint 列表(`<type>#<selfAID>#<name>`)折叠成展示用摘要。
1017
1018
  *
1018
1019
  * 聚合规则:
1019
1020
  * - 按 type 分组
@@ -1413,6 +1414,7 @@ async function cmdWatchMenu() {
1413
1414
  { key: 'log', label: 'log', desc: 'real-time log tail' },
1414
1415
  { key: 'aid', label: 'aid', desc: 'AID connection stats' },
1415
1416
  { key: 'msg', label: 'msg', desc: 'message inspector' },
1417
+ { key: 'web', label: 'web', desc: 'browser dashboard (aid/msg/session)' },
1416
1418
  ];
1417
1419
  let index = 0;
1418
1420
  const useColor = !!process.stdout.isTTY;
@@ -1476,6 +1478,9 @@ async function cmdWatchMenu() {
1476
1478
  const { cmdWatchMsg } = await import('./watch-msg.js');
1477
1479
  await cmdWatchMsg();
1478
1480
  }
1481
+ else if (chosen === 'web') {
1482
+ await cmdWatchWeb();
1483
+ }
1479
1484
  resolve();
1480
1485
  }
1481
1486
  };
@@ -2097,6 +2102,97 @@ async function cmdWatchAid() {
2097
2102
  }
2098
2103
  platform.onShutdown(cleanup);
2099
2104
  }
2105
+ async function cmdWatchWeb() {
2106
+ const p = resolvePaths();
2107
+ fs.mkdirSync(p.instanceDir, { recursive: true });
2108
+ const useColor = !!process.stdout.isTTY;
2109
+ const RST = useColor ? '\x1b[0m' : '';
2110
+ const DIM = useColor ? '\x1b[2m' : '';
2111
+ const BOLD = useColor ? '\x1b[1m' : '';
2112
+ const CYAN = useColor ? '\x1b[36m' : '';
2113
+ const GREEN = useColor ? '\x1b[32m' : '';
2114
+ const YELLOW = useColor ? '\x1b[33m' : '';
2115
+ const logLine = (line) => {
2116
+ const t = new Date();
2117
+ const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}`;
2118
+ process.stdout.write(`${DIM}${ts}${RST} ${line}\n`);
2119
+ };
2120
+ // 调试日志文件:每次运行 watch web 时清空,便于建立调试闭环
2121
+ // 查看 sessions 调试日志 → 读这个文件
2122
+ const logFile = path.join(p.logs, 'watch-web.log');
2123
+ try {
2124
+ fs.mkdirSync(p.logs, { recursive: true });
2125
+ fs.writeFileSync(logFile, `# watch-web debug log\n# started ${new Date().toISOString()} pid=${process.pid}\n`);
2126
+ }
2127
+ catch { /* best effort */ }
2128
+ const fileLog = (line) => {
2129
+ const t = new Date();
2130
+ const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}.${String(t.getMilliseconds()).padStart(3, '0')}`;
2131
+ try {
2132
+ fs.appendFileSync(logFile, `${ts} ${line.replace(/\x1b\[[0-9;]*m/g, '')}\n`);
2133
+ }
2134
+ catch { /* ignore */ }
2135
+ };
2136
+ // 同时输出到终端和日志文件
2137
+ const log = (line) => { logLine(line); fileLog(line); };
2138
+ const { startWatchWebServer } = await import('./watch-web/server.js');
2139
+ let handle;
2140
+ try {
2141
+ handle = await startWatchWebServer({ log });
2142
+ }
2143
+ catch (e) {
2144
+ console.error(`❌ 启动 Web 服务失败: ${e?.message || e}`);
2145
+ process.exit(1);
2146
+ }
2147
+ // 注册 instance 文件
2148
+ const instanceFile = path.join(p.instanceDir, `watch-web-${process.pid}.json`);
2149
+ fs.writeFileSync(instanceFile, JSON.stringify({
2150
+ pid: process.pid, startedAt: Date.now(), startedAtIso: new Date().toISOString(),
2151
+ type: 'watch-web', port: handle.port,
2152
+ }, null, 2));
2153
+ // 列出本机访问地址
2154
+ const os = await import('os');
2155
+ const ifaces = os.networkInterfaces();
2156
+ const lanIps = [];
2157
+ for (const list of Object.values(ifaces)) {
2158
+ for (const ni of list || []) {
2159
+ if (ni.family === 'IPv4' && !ni.internal)
2160
+ lanIps.push(ni.address);
2161
+ }
2162
+ }
2163
+ process.stdout.write(`\n${BOLD}${CYAN}🔭 EvolClaw Watch Web${RST}\n\n`);
2164
+ process.stdout.write(` ${BOLD}配对码:${RST} ${GREEN}${BOLD}${handle.pairingCode}${RST} ${DIM}(5 分钟内有效,配对后 token 缓存 24h 自动续期)${RST}\n\n`);
2165
+ process.stdout.write(` ${BOLD}本机:${RST} http://localhost:${handle.port}\n`);
2166
+ for (const ip of lanIps) {
2167
+ process.stdout.write(` ${BOLD}局域网:${RST} http://${ip}:${handle.port}\n`);
2168
+ }
2169
+ process.stdout.write(`\n ${DIM}绑定 0.0.0.0,远程可访问。按任意键退出。${RST}\n`);
2170
+ process.stdout.write(` ${DIM}调试日志: ${logFile}${RST}\n\n`);
2171
+ const cleanup = () => {
2172
+ try {
2173
+ fs.unlinkSync(instanceFile);
2174
+ }
2175
+ catch { }
2176
+ handle.close().finally(() => process.exit(0));
2177
+ };
2178
+ process.on('exit', () => { try {
2179
+ fs.unlinkSync(instanceFile);
2180
+ }
2181
+ catch { } });
2182
+ process.on('SIGINT', cleanup);
2183
+ process.on('SIGTERM', cleanup);
2184
+ platform.onShutdown(cleanup);
2185
+ // 按任意键退出
2186
+ if (process.stdin.isTTY) {
2187
+ process.stdin.setRawMode(true);
2188
+ process.stdin.resume();
2189
+ process.stdin.on('data', (key) => {
2190
+ logLine(`${YELLOW}收到退出指令,关闭服务…${RST}`);
2191
+ cleanup();
2192
+ });
2193
+ }
2194
+ await new Promise(() => { });
2195
+ }
2100
2196
  async function cmdRestartMonitor() {
2101
2197
  const p = resolvePaths();
2102
2198
  const restartLog = path.join(p.logs, 'restart.log');
@@ -2471,14 +2567,13 @@ function archiveSelfHealLog(p, log) {
2471
2567
  * Searches across all channel types (feishu, wechat, aun) for a matching instance.
2472
2568
  */
2473
2569
  function resolveInstanceConfig(instanceName) {
2474
- // 新结构:channel key 是 <type>#<selfPeerId>#<name>,解析后从对应 agent 的 channels[] 找
2570
+ // 新结构:channel key 是 <type>#<selfAID>#<name>,解析后从对应 agent 的 channels[] 找
2475
2571
  const parts = instanceName.split('#');
2476
2572
  if (parts.length === 3) {
2477
- const [type, encodedSelfPeerId, name] = parts;
2478
- const selfPeerId = decodeURIComponent(encodedSelfPeerId);
2573
+ const [type, selfAID, name] = parts;
2479
2574
  const { agents } = loadAllAgents();
2480
- // AUN channel 的 selfPeerId 就是 agent.aid
2481
- const agent = agents.find(a => a.aid === selfPeerId);
2575
+ // AUN channel 的 selfAID 就是 agent.aid
2576
+ const agent = agents.find(a => a.aid === selfAID);
2482
2577
  if (!agent)
2483
2578
  return null;
2484
2579
  const inst = agent.channels.find((c) => c.type === type && c.name === name);
@@ -2765,7 +2860,7 @@ Agent:
2765
2860
  process.exit(1);
2766
2861
  }
2767
2862
  // help 不需要连接服务,直接复用无参数时的帮助输出
2768
- if (args[0] === 'help') {
2863
+ if (isHelpFlag(args[0])) {
2769
2864
  return cmdCtl([]);
2770
2865
  }
2771
2866
  const sessionId = process.env.EVOLCLAW_SESSION_ID;
@@ -2800,7 +2895,7 @@ Agent:
2800
2895
  async function cmdAgent(args) {
2801
2896
  const sub = args[0];
2802
2897
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
2803
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
2898
+ if (!sub || isHelpFlag(sub)) {
2804
2899
  console.log(`用法: evolclaw agent <command>
2805
2900
 
2806
2901
  Commands:
@@ -2818,6 +2913,7 @@ Commands:
2818
2913
 
2819
2914
  Options:
2820
2915
  --format json 输出 JSON 格式
2916
+ --help, -h 各子命令均支持,查看详细用法
2821
2917
 
2822
2918
  示例:
2823
2919
  evolclaw agent list
@@ -2832,7 +2928,13 @@ Options:
2832
2928
  }
2833
2929
  const { agentList, agentShow, agentCreateInteractive, agentCreateNonInteractive, agentReload, agentEnable, agentDisable, agentGet, agentSet, agentDelete, agentRename, } = await import('./agent.js');
2834
2930
  // --- list ---
2835
- if (!sub || sub === 'list') {
2931
+ if (sub === 'list') {
2932
+ if (wantsHelp(args)) {
2933
+ console.log(`用法: evolclaw agent list [--format json]
2934
+
2935
+ 列出所有 agent,显示名称、状态、渠道、项目、基座、最后活跃时间。`);
2936
+ return;
2937
+ }
2836
2938
  const result = await agentList();
2837
2939
  if (!result.ok) {
2838
2940
  if (formatJson) {
@@ -2893,6 +2995,24 @@ Options:
2893
2995
  }
2894
2996
  // --- new ---
2895
2997
  if (sub === 'new') {
2998
+ if (wantsHelp(args)) {
2999
+ console.log(`用法: evolclaw agent new [aid] 交互式创建
3000
+ evolclaw agent new <aid> --non-interactive [选项]
3001
+
3002
+ 非交互模式选项:
3003
+ --baseagent <claude|codex|gemini> 默认: PATH 中第一个可用
3004
+ --project <absolute path> 必填
3005
+ --owner <aid>
3006
+ --name <display-name>
3007
+ --description <text>
3008
+ --force 覆盖已有 config.json
3009
+ --format json 输出 JSON
3010
+
3011
+ 示例:
3012
+ evolclaw agent new mybot.agentid.pub
3013
+ evolclaw agent new mybot.agentid.pub --non-interactive --project /abs/path --baseagent claude`);
3014
+ return;
3015
+ }
2896
3016
  const name = args[1];
2897
3017
  const nonInteractive = args.includes('--non-interactive');
2898
3018
  if (nonInteractive) {
@@ -2989,6 +3109,14 @@ Options:
2989
3109
  // }
2990
3110
  // --- reload ---
2991
3111
  if (sub === 'reload') {
3112
+ if (wantsHelp(args)) {
3113
+ console.log(`用法: evolclaw agent reload [aid] [--format json]
3114
+
3115
+ 热重载 agent 配置。
3116
+ 无参数 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
3117
+ <aid> 仅热重载指定 agent`);
3118
+ return;
3119
+ }
2992
3120
  const target = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
2993
3121
  const result = await agentReload(target);
2994
3122
  if (!result.ok) {
@@ -3016,6 +3144,12 @@ Options:
3016
3144
  }
3017
3145
  // --- enable ---
3018
3146
  if (sub === 'enable') {
3147
+ if (wantsHelp(args)) {
3148
+ console.log(`用法: evolclaw agent enable <aid> [--format json]
3149
+
3150
+ 启用 agent。若服务运行中会热重载,否则下次 evolclaw start 时生效。`);
3151
+ return;
3152
+ }
3019
3153
  const aid = args[1];
3020
3154
  if (!aid) {
3021
3155
  console.error('用法: evolclaw agent enable <aid>');
@@ -3041,6 +3175,12 @@ Options:
3041
3175
  }
3042
3176
  // --- disable ---
3043
3177
  if (sub === 'disable') {
3178
+ if (wantsHelp(args)) {
3179
+ console.log(`用法: evolclaw agent disable <aid> [--format json]
3180
+
3181
+ 停用 agent。若服务运行中会热重载离线,否则在配置中标记为禁用。`);
3182
+ return;
3183
+ }
3044
3184
  const aid = args[1];
3045
3185
  if (!aid) {
3046
3186
  console.error('用法: evolclaw agent disable <aid>');
@@ -3066,6 +3206,16 @@ Options:
3066
3206
  }
3067
3207
  // --- get ---
3068
3208
  if (sub === 'get') {
3209
+ if (wantsHelp(args)) {
3210
+ console.log(`用法: evolclaw agent get <aid> <key> [--format json]
3211
+
3212
+ 读取单个配置字段。key 支持点路径,如 "channels.aun.enabled"。
3213
+
3214
+ 示例:
3215
+ evolclaw agent get mybot.agentid.pub active_baseagent
3216
+ evolclaw agent get mybot.agentid.pub channels.aun.enabled`);
3217
+ return;
3218
+ }
3069
3219
  const aid = args[1];
3070
3220
  const key = args[2];
3071
3221
  if (!aid || !key) {
@@ -3093,6 +3243,16 @@ Options:
3093
3243
  }
3094
3244
  // --- set ---
3095
3245
  if (sub === 'set') {
3246
+ if (wantsHelp(args)) {
3247
+ console.log(`用法: evolclaw agent set <aid> <key> <value> [--format json]
3248
+
3249
+ 修改单个配置字段。key 支持点路径。修改后若服务运行中会自动热重载。
3250
+
3251
+ 示例:
3252
+ evolclaw agent set mybot.agentid.pub active_baseagent codex
3253
+ evolclaw agent set mybot.agentid.pub channels.aun.enabled true`);
3254
+ return;
3255
+ }
3096
3256
  const aid = args[1];
3097
3257
  const key = args[2];
3098
3258
  const val = args[3];
@@ -3120,6 +3280,15 @@ Options:
3120
3280
  }
3121
3281
  // --- rename ---
3122
3282
  if (sub === 'rename') {
3283
+ if (wantsHelp(args)) {
3284
+ console.log(`用法: evolclaw agent rename <aid> <name> [--format json]
3285
+
3286
+ 修改 agent 显示名称。同时更新本地 agent.md 并尝试重新上传。
3287
+
3288
+ 示例:
3289
+ evolclaw agent rename mybot.agentid.pub "My Bot"`);
3290
+ return;
3291
+ }
3123
3292
  const aid = args[1];
3124
3293
  const newName = args[2];
3125
3294
  if (!aid || !newName) {
@@ -3146,6 +3315,14 @@ Options:
3146
3315
  }
3147
3316
  // --- delete ---
3148
3317
  if (sub === 'delete') {
3318
+ if (wantsHelp(args)) {
3319
+ console.log(`用法: evolclaw agent delete <aid> [--purge] [--format json]
3320
+
3321
+ 删除 agent 的配置。
3322
+ --purge 同时清除该 agent 的会话、消息、日志等运行时数据
3323
+ 默认 仅删除 config.json,运行时数据保留`);
3324
+ return;
3325
+ }
3149
3326
  const aid = args[1];
3150
3327
  if (!aid) {
3151
3328
  console.error('用法: evolclaw agent delete <aid> [--purge]');
@@ -3172,6 +3349,12 @@ Options:
3172
3349
  }
3173
3350
  // --- show ---
3174
3351
  if (sub === 'show') {
3352
+ if (wantsHelp(args)) {
3353
+ console.log(`用法: evolclaw agent show <aid> [--format json]
3354
+
3355
+ 查看 agent 详情:身份、配置、连接状态、会话路径等。`);
3356
+ return;
3357
+ }
3175
3358
  const aid = args[1];
3176
3359
  if (!aid) {
3177
3360
  console.error('用法: evolclaw agent show <aid>');
@@ -3285,63 +3468,134 @@ function resolveAunPath(args) {
3285
3468
  return process.env.AUN_HOME || undefined;
3286
3469
  }
3287
3470
  async function cmdAid(args) {
3288
- const sub = args[0] || 'list';
3471
+ const sub = args[0];
3289
3472
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3290
3473
  const aunPath = resolveAunPath(args);
3291
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
3474
+ if (!sub || isHelpFlag(sub)) {
3292
3475
  console.log(`用法: evolclaw aid <command>
3293
3476
 
3294
3477
  Commands:
3295
- list 列出本地所有 AID
3296
- show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
3478
+ list 列出本地所有 AID(实测 sign+verify)
3479
+ show <aid> 查看本地 AID 详情(证书、私钥、签名能力)
3297
3480
  new <aid> 创建新 AID 身份
3298
- delete <aid> 删除本地 AID(无网络注销)
3481
+ delete <aid> 删除指定本地 AID(无网络注销)
3482
+ delete --orphan 批量清理无私钥的外部 AID 缓存
3483
+ delete --no-cert 批量清理无私钥也无公钥证书的孤儿目录
3484
+ delete --unrecoverable 批量清理云端公钥已变更、本地不可恢复的 AID
3485
+ 批量删除默认 dry-run,加 --yes 执行
3299
3486
  lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
3300
3487
  agentmd put <aid> 读本地 agent.md → 签名 → 上传
3301
3488
  agentmd get <aid> 下载 agent.md → 验签 → 本地持久化
3302
3489
 
3303
3490
  Options:
3304
3491
  --format json 输出 JSON 格式
3492
+ --help, -h 各子命令均支持,查看详细用法
3305
3493
 
3306
3494
  示例:
3307
3495
  evolclaw aid list
3308
3496
  evolclaw aid show toleiliang2.agentid.pub
3309
3497
  evolclaw aid new reviewer.agentid.pub
3498
+ evolclaw aid delete --help
3310
3499
  evolclaw aid delete old.agentid.pub
3500
+ evolclaw aid delete --orphan
3501
+ evolclaw aid delete --unrecoverable --yes
3311
3502
  evolclaw aid lookup someone.agentid.pub
3312
3503
  evolclaw aid agentmd put mybot.agentid.pub
3313
3504
  evolclaw aid agentmd get someone.agentid.pub`);
3314
3505
  return;
3315
3506
  }
3316
- const { aidList, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
3507
+ const { aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, agentmdPut, agentmdGet, buildInitialAgentMd, isValidAid } = await import('../aun/aid/index.js');
3317
3508
  if (sub === 'list') {
3318
- const aids = aidList(aunPath);
3509
+ if (wantsHelp(args)) {
3510
+ console.log(`用法: evolclaw aid list [筛选选项] [--no-verify] [--format json]
3511
+
3512
+ 列出本地 AID 并跑 sign+verify 自检。
3513
+
3514
+ 筛选选项(可组合,不指定 = 列出 mine + broken + peer-cert):
3515
+ --mine 仅本地可用身份(实测可签名+验签通过)
3516
+ --broken 仅有私钥但不可用(公钥不匹配 / 证书过期 / sign 失败)
3517
+ --peer-cert 仅对端 AID(无私钥,有公钥证书)
3518
+ --no-cert 仅无私钥无证书的目录(默认隐藏,需用 aid delete --no-cert 清理)
3519
+
3520
+ 选项:
3521
+ --no-verify 跳过 sign+verify 实测,仅静态扫描(更快,mine/broken 仅按静态判定近似)
3522
+ --format json JSON 格式输出
3523
+
3524
+ 输出图标:
3525
+ 🔑 有私钥
3526
+ ✅ 实测可签名/验签
3527
+ ❌ 不可签名(公钥不匹配 / sign 失败 / verify 失败等)
3528
+ ⌛ 证书过期
3529
+ 📜 有公钥证书
3530
+ 📄 有 agent.md
3531
+
3532
+ 示例:
3533
+ evolclaw aid list 列出 mine + broken + peer-cert
3534
+ evolclaw aid list --mine 仅可用身份
3535
+ evolclaw aid list --mine --broken 所有有私钥的 AID
3536
+ evolclaw aid list --no-cert 仅无私钥无证书的孤儿目录
3537
+ evolclaw aid list --no-verify 跳过实测,快速静态扫描`);
3538
+ return;
3539
+ }
3540
+ const wantMine = args.includes('--mine');
3541
+ const wantBroken = args.includes('--broken');
3542
+ const wantPeerCert = args.includes('--peer-cert');
3543
+ const wantNoCert = args.includes('--no-cert');
3544
+ const noVerify = args.includes('--no-verify');
3545
+ const anyFilter = wantMine || wantBroken || wantPeerCert || wantNoCert;
3546
+ // 默认: mine + broken + peer-cert(隐藏 no-cert,需显式 --no-cert 才列)
3547
+ const showMine = anyFilter ? wantMine : true;
3548
+ const showBroken = anyFilter ? wantBroken : true;
3549
+ const showPeerCert = anyFilter ? wantPeerCert : true;
3550
+ const showNoCert = anyFilter ? wantNoCert : false;
3551
+ const all = noVerify ? aidList(aunPath) : await aidListVerified(aunPath);
3552
+ const aids = all.filter(a => (showMine && a.category === 'mine') ||
3553
+ (showBroken && a.category === 'broken') ||
3554
+ (showPeerCert && a.category === 'peer-cert') ||
3555
+ (showNoCert && a.category === 'no-cert'));
3319
3556
  if (formatJson) {
3320
3557
  console.log(JSON.stringify(aids, null, 2));
3321
3558
  return;
3322
3559
  }
3323
3560
  if (aids.length === 0) {
3324
- console.log('本地无 AID');
3561
+ console.log('无匹配 AID');
3325
3562
  return;
3326
3563
  }
3327
- console.log('本地 AID:');
3564
+ console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}(${aunPath ?? resolveRoot()}):`);
3328
3565
  for (const a of aids) {
3329
- const icons = [
3330
- a.hasPrivateKey ? '🔑' : ' ',
3331
- a.hasAgentMd ? '📄' : ' ',
3332
- ].join('');
3333
- console.log(` ${icons} ${a.aid}`);
3334
- }
3335
- console.log('\n🔑=私钥 📄=agent.md');
3566
+ const keyIcon = a.hasPrivateKey ? '🔑' : ' ';
3567
+ let signIcon = ' ';
3568
+ // --no-verify signVerified 始终为 null,用 canSign 作为静态近似
3569
+ const effectiveOk = noVerify ? a.canSign : a.signVerified === true;
3570
+ const effectiveFail = noVerify ? (a.hasPrivateKey && !a.canSign) : (a.hasPrivateKey && a.signVerified === false);
3571
+ if (effectiveOk)
3572
+ signIcon = '';
3573
+ else if (a.hasPrivateKey && a.certExpired)
3574
+ signIcon = '⌛';
3575
+ else if (effectiveFail)
3576
+ signIcon = '❌';
3577
+ const certIcon = a.hasCert ? '📜' : ' ';
3578
+ const mdIcon = a.hasAgentMd ? '📄' : ' ';
3579
+ const tail = !noVerify && a.signVerified === false && a.signError && !(a.keyMatchesCert === false || a.certExpired || !a.hasPrivateKey || !a.hasCert)
3580
+ ? ` (${a.signError})` : '';
3581
+ console.log(` ${keyIcon} ${signIcon} ${certIcon} ${mdIcon} ${a.aid}${tail}`);
3582
+ }
3583
+ console.log('\n🔑=私钥 ✅=可签名/验签 ❌=不可签名 ⌛=证书过期 📜=公钥证书 📄=agent.md');
3336
3584
  return;
3337
3585
  }
3338
3586
  if (sub === 'show') {
3587
+ if (wantsHelp(args)) {
3588
+ console.log(`用法: evolclaw aid show <aid> [--format json]
3589
+
3590
+ 查看本地 AID 详情:私钥/证书/agent.md 状态、签名能力实测。`);
3591
+ return;
3592
+ }
3339
3593
  const aid = args[1];
3340
3594
  if (!aid) {
3341
3595
  console.error('用法: evolclaw aid show <aid>');
3342
3596
  process.exit(1);
3343
3597
  }
3344
- const info = aidShow(aid, { aunPath });
3598
+ const info = await aidShow(aid, { aunPath });
3345
3599
  if (formatJson) {
3346
3600
  console.log(JSON.stringify(info, null, 2));
3347
3601
  return;
@@ -3349,58 +3603,246 @@ Options:
3349
3603
  console.log(`AID: ${info.aid}`);
3350
3604
  console.log(` 私钥: ${info.hasPrivateKey ? '有' : '无'}`);
3351
3605
  console.log(` agent.md: ${info.hasAgentMd ? '有' : '无'}`);
3352
- console.log(` 证书到期: ${info.certExpiresAt ?? '无证书'}`);
3606
+ if (info.hasAgentMd) {
3607
+ const sigLabel = info.agentMdSignature === 'verified' ? '✓ 已验签'
3608
+ : info.agentMdSignature === 'unsigned' ? '⚠ 未签名'
3609
+ : info.agentMdSignature === 'invalid' ? `✗ 签名无效${info.agentMdSignatureReason ? ': ' + info.agentMdSignatureReason : ''}`
3610
+ : '? 未知';
3611
+ console.log(` 签名状态: ${sigLabel}`);
3612
+ }
3613
+ console.log(` 证书到期: ${info.certExpiresAt ?? '无证书'}${info.certExpired ? ' (已过期!)' : ''}`);
3353
3614
  if (info.certSubject)
3354
3615
  console.log(` 证书主体: ${info.certSubject}`);
3616
+ if (info.keyMatchesCert === false)
3617
+ console.log(` 密钥/证书: ✗ 公钥不匹配(cert.pem 与 key.json 公钥不一致)`);
3618
+ else if (info.keyMatchesCert === true)
3619
+ console.log(` 密钥/证书: ✓ 公钥一致`);
3620
+ if (info.signVerified === true)
3621
+ console.log(` 可签名/验签: ✓ 实测通过`);
3622
+ else if (info.signVerified === false)
3623
+ console.log(` 可签名/验签: ✗ 失败${info.signError ? `(${info.signError})` : ''}`);
3624
+ else
3625
+ console.log(` 可签名/验签: ? 未知`);
3355
3626
  return;
3356
3627
  }
3357
3628
  if (sub === 'new') {
3629
+ if (wantsHelp(args)) {
3630
+ console.log(`用法: evolclaw aid new <完整AID> [--force]
3631
+
3632
+ 创建新 AID 身份:生成 ECDSA 密钥对、向 Issuer 申请证书、构建并上传初始 agent.md。
3633
+
3634
+ 选项:
3635
+ --force 强制重新注册,覆盖已存在的身份(即使签名验证失败)
3636
+
3637
+ 例: evolclaw aid new reviewer.agentid.pub
3638
+ evolclaw aid new reviewer.agentid.pub --force`);
3639
+ return;
3640
+ }
3358
3641
  const aid = args[1];
3642
+ const force = args.includes('--force');
3359
3643
  if (!aid) {
3360
- console.error('用法: evolclaw aid new <完整AID>\n例: evolclaw aid new reviewer.agentid.pub');
3644
+ console.error('用法: evolclaw aid new <完整AID> [--force]\n例: evolclaw aid new reviewer.agentid.pub');
3361
3645
  process.exit(1);
3362
3646
  }
3363
3647
  if (!isValidAid(aid)) {
3364
3648
  console.error(`❌ 无效 AID 格式: ${aid}`);
3365
3649
  process.exit(1);
3366
3650
  }
3367
- const result = await aidCreate(aid, { aunPath });
3368
- if (!result.alreadyExisted) {
3369
- const content = buildInitialAgentMd({ aid });
3651
+ try {
3652
+ const result = await aidCreate(aid, { aunPath, force });
3653
+ if (!result.alreadyExisted) {
3654
+ const content = buildInitialAgentMd({ aid });
3655
+ try {
3656
+ await agentmdPut(content, { aid, aunPath });
3657
+ console.log('✓ agent.md 已发布');
3658
+ }
3659
+ catch (e) {
3660
+ console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
3661
+ }
3662
+ }
3370
3663
  try {
3371
- await agentmdPut(content, { aid, client: result.client, aunPath });
3372
- console.log('✓ agent.md 已发布');
3664
+ await result.client.close();
3373
3665
  }
3374
- catch (e) {
3375
- console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
3666
+ catch { }
3667
+ try {
3668
+ result.store?.close();
3376
3669
  }
3670
+ catch { }
3671
+ const verb = result.alreadyExisted ? '已存在且有效' : (force ? '已重新创建' : '已创建');
3672
+ console.log(`✓ ${aid} ${verb}`);
3673
+ console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3377
3674
  }
3378
- try {
3379
- await result.client.close();
3675
+ catch (e) {
3676
+ if (e.code === 'AID_INVALID') {
3677
+ console.error(`❌ ${e.message}`);
3678
+ process.exit(1);
3679
+ }
3680
+ if (e.code === -32052 || e.constructor?.name === 'IdentityConflictError') {
3681
+ console.error(`❌ AID ${aid} 已在服务端注册,但本地密钥无法匹配。\n` +
3682
+ `该 AID 可能由其他设备创建,无法在本地恢复。请选择其他名称。`);
3683
+ process.exit(1);
3684
+ }
3685
+ throw e;
3380
3686
  }
3381
- catch { }
3382
- const verb = result.alreadyExisted ? '已存在' : '已创建';
3383
- console.log(`✓ ${aid} ${verb}`);
3384
- console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3385
3687
  return;
3386
3688
  }
3387
3689
  if (sub === 'delete') {
3388
- const aid = args[1];
3389
- if (!aid) {
3390
- console.error('用法: evolclaw aid delete <aid>');
3690
+ if (wantsHelp(args)) {
3691
+ console.log(`用法: evolclaw aid delete <子命令>
3692
+
3693
+ 单个删除:
3694
+ evolclaw aid delete <aid> 删除指定 AID 的本地数据(无网络注销)
3695
+
3696
+ 批量删除(默认 dry-run,加 --yes 才真删):
3697
+ evolclaw aid delete --orphan 删除所有"无私钥"的本地缓存(外部 AID)
3698
+ evolclaw aid delete --no-cert 删除所有"无私钥也无公钥证书"的目录
3699
+ 条件:!hasPrivateKey && !hasCert
3700
+ 这些目录最多只剩 agent.md 或 SQLite 残留,
3701
+ 对验签和加密通信都没用,删除安全。
3702
+ evolclaw aid delete --unrecoverable 删除所有不可恢复的 AID
3703
+ 条件:本地 sign+verify 实测失败
3704
+ 且 PKI 探测确认云端公钥也不等本地 key.json
3705
+
3706
+ 选项:
3707
+ --yes 跳过 dry-run,立即执行
3708
+ --skip-pki --unrecoverable 时跳过 PKI 探测,仅依据本地 sign+verify 失败判断(危险,可能误删可恢复 AID)
3709
+ --format json 输出 JSON 格式
3710
+
3711
+ 示例:
3712
+ evolclaw aid delete old.agentid.pub
3713
+ evolclaw aid delete --orphan 列出会被清理的孤儿
3714
+ evolclaw aid delete --orphan --yes 实际清理
3715
+ evolclaw aid delete --no-cert 列出无证书孤儿目录
3716
+ evolclaw aid delete --no-cert --yes 实际清理
3717
+ evolclaw aid delete --unrecoverable 联网探测后列出无救 AID
3718
+ evolclaw aid delete --unrecoverable --yes`);
3719
+ return;
3720
+ }
3721
+ const yes = args.includes('--yes');
3722
+ const skipPki = args.includes('--skip-pki');
3723
+ const orphan = args.includes('--orphan');
3724
+ const noCert = args.includes('--no-cert');
3725
+ const unrecoverable = args.includes('--unrecoverable');
3726
+ const modes = [orphan, noCert, unrecoverable].filter(Boolean).length;
3727
+ if (modes > 1) {
3728
+ console.error('❌ --orphan / --no-cert / --unrecoverable 互斥,不能同时使用');
3391
3729
  process.exit(1);
3392
3730
  }
3393
- const deleted = aidDelete(aid, { aunPath });
3394
- if (deleted) {
3395
- console.log(`✓ ${aid} 已删除`);
3731
+ // 单个 aid 删除:保留原有行为
3732
+ if (modes === 0) {
3733
+ const aid = args[1];
3734
+ if (!aid) {
3735
+ console.error('用法: evolclaw aid delete <aid>\n evolclaw aid delete --orphan | --no-cert | --unrecoverable [--yes]\n evolclaw aid delete --help 查看完整用法');
3736
+ process.exit(1);
3737
+ }
3738
+ const deleted = aidDelete(aid, { aunPath });
3739
+ if (deleted) {
3740
+ console.log(`✓ ${aid} 已删除`);
3741
+ }
3742
+ else {
3743
+ console.error(`❌ 本地不存在: ${aid}`);
3744
+ process.exit(1);
3745
+ }
3746
+ return;
3747
+ }
3748
+ // 批量模式:先选出候选
3749
+ const { probePkiRecoverability } = await import('../aun/aid/index.js');
3750
+ const candidates = [];
3751
+ if (orphan) {
3752
+ const aids = aidList(aunPath);
3753
+ for (const a of aids) {
3754
+ if (!a.hasPrivateKey)
3755
+ candidates.push({ aid: a.aid, reason: 'no private key (external AID cache)' });
3756
+ }
3757
+ }
3758
+ else if (noCert) {
3759
+ const aids = aidList(aunPath);
3760
+ for (const a of aids) {
3761
+ if (!a.hasPrivateKey && !a.hasCert) {
3762
+ const traits = [a.hasAgentMd ? 'agent.md' : null].filter(Boolean).join(', ');
3763
+ candidates.push({ aid: a.aid, reason: `no private key, no cert${traits ? ` (only: ${traits})` : ''}` });
3764
+ }
3765
+ }
3396
3766
  }
3397
3767
  else {
3398
- console.error(`❌ 本地不存在: ${aid}`);
3399
- process.exit(1);
3768
+ // unrecoverable: 必须先做 sign+verify 实测
3769
+ if (!formatJson)
3770
+ console.log('扫描中: 本地签名/验签实测...');
3771
+ const aids = await aidListVerified(aunPath);
3772
+ const localBroken = aids.filter(a => a.hasPrivateKey && a.signVerified === false);
3773
+ if (skipPki) {
3774
+ for (const a of localBroken) {
3775
+ candidates.push({ aid: a.aid, reason: `sign+verify failed (${a.signError ?? 'unknown'}) [--skip-pki: 未联网验证]` });
3776
+ }
3777
+ }
3778
+ else {
3779
+ if (!formatJson)
3780
+ console.log(`扫描中: 对 ${localBroken.length} 个本地损坏 AID 做 PKI 探测...`);
3781
+ for (const a of localBroken) {
3782
+ const r = await probePkiRecoverability(a.aid, { aunPath });
3783
+ if (r.kind === 'unrecoverable') {
3784
+ candidates.push({ aid: a.aid, reason: `local broken; PKI: ${r.reason}`, pki: 'unrecoverable' });
3785
+ }
3786
+ else if (r.kind === 'no-server-record') {
3787
+ candidates.push({ aid: a.aid, reason: `local broken; PKI: ${r.reason}`, pki: 'no-server-record' });
3788
+ }
3789
+ else {
3790
+ // recoverable / no-key / unknown 一律保守不删
3791
+ if (!formatJson)
3792
+ console.log(` · 跳过 ${a.aid}: PKI=${r.kind}${('reason' in r) ? ' — ' + r.reason : ''}`);
3793
+ }
3794
+ }
3795
+ }
3400
3796
  }
3797
+ if (formatJson) {
3798
+ console.log(JSON.stringify({
3799
+ mode: orphan ? 'orphan' : noCert ? 'no-cert' : 'unrecoverable',
3800
+ dryRun: !yes,
3801
+ skipPki: unrecoverable ? skipPki : undefined,
3802
+ candidates,
3803
+ }, null, 2));
3804
+ if (yes) {
3805
+ for (const c of candidates)
3806
+ aidDelete(c.aid, { aunPath });
3807
+ }
3808
+ return;
3809
+ }
3810
+ if (candidates.length === 0) {
3811
+ console.log(orphan ? '✓ 无孤儿 AID' : noCert ? '✓ 无无证书孤儿目录' : '✓ 无不可恢复 AID');
3812
+ return;
3813
+ }
3814
+ console.log(`\n${yes ? '将删除' : '候选删除(dry-run)'}:${candidates.length} 个 AID`);
3815
+ for (const c of candidates) {
3816
+ console.log(` - ${c.aid}`);
3817
+ console.log(` ${c.reason}`);
3818
+ }
3819
+ if (!yes) {
3820
+ console.log('\n(dry-run,未真删除。加 --yes 执行真删。)');
3821
+ return;
3822
+ }
3823
+ let ok = 0;
3824
+ let fail = 0;
3825
+ for (const c of candidates) {
3826
+ const deleted = aidDelete(c.aid, { aunPath });
3827
+ if (deleted) {
3828
+ console.log(` ✓ 删除 ${c.aid}`);
3829
+ ok++;
3830
+ }
3831
+ else {
3832
+ console.log(` ✗ 失败 ${c.aid}(已不存在?)`);
3833
+ fail++;
3834
+ }
3835
+ }
3836
+ console.log(`\n完成:成功 ${ok},失败 ${fail}`);
3401
3837
  return;
3402
3838
  }
3403
3839
  if (sub === 'lookup') {
3840
+ if (wantsHelp(args)) {
3841
+ console.log(`用法: evolclaw aid lookup <aid> [--format json]
3842
+
3843
+ 远程探测 AID:是否注册、所在网关、是否有 agent.md(不验签,仅获取)。`);
3844
+ return;
3845
+ }
3404
3846
  const aid = args[1];
3405
3847
  if (!aid) {
3406
3848
  console.error('用法: evolclaw aid lookup <aid>');
@@ -3438,6 +3880,13 @@ Options:
3438
3880
  if (sub === 'agentmd') {
3439
3881
  const verb = args[1];
3440
3882
  const aid = args[2];
3883
+ if (!verb || isHelpFlag(verb) || wantsHelp(args)) {
3884
+ console.log(`用法: evolclaw aid agentmd <put|get> <aid> [--format json]
3885
+
3886
+ put <aid> 读本地 agent.md → 用本地私钥签名 → 上传到 PKI
3887
+ get <aid> 从 PKI 下载 agent.md → 验签 → 持久化到本地`);
3888
+ return;
3889
+ }
3441
3890
  if (verb === 'put') {
3442
3891
  if (!aid) {
3443
3892
  console.error('用法: evolclaw aid agentmd put <aid>');
@@ -3509,7 +3958,7 @@ Options:
3509
3958
  }
3510
3959
  // ==================== RPC ====================
3511
3960
  async function cmdRpc(args) {
3512
- if (args[0] === 'help' || args.length === 0) {
3961
+ if (args.length === 0 || isHelpFlag(args[0])) {
3513
3962
  console.log(`用法: evolclaw rpc --as <aid> --params <params>
3514
3963
 
3515
3964
  通用 AUN RPC 调用。
@@ -3588,7 +4037,7 @@ async function cmdStorage(args) {
3588
4037
  const sub = args[0];
3589
4038
  const aunPath = resolveAunPath(args);
3590
4039
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3591
- if (!sub || sub === 'help') {
4040
+ if (!sub || isHelpFlag(sub)) {
3592
4041
  console.log(`用法: evolclaw storage <command> <aid> [options]
3593
4042
 
3594
4043
  Commands:
@@ -3737,8 +4186,7 @@ async function cmdMsg(args) {
3737
4186
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3738
4187
  const appIdx = args.indexOf('--app');
3739
4188
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
3740
- const asDaemon = args.includes('--as-daemon');
3741
- if (!sub || sub === 'help') {
4189
+ if (!sub || isHelpFlag(sub)) {
3742
4190
  console.log(`用法: evolclaw msg <command> <from-aid> [args...] [options]
3743
4191
 
3744
4192
  Commands:
@@ -3747,13 +4195,12 @@ Commands:
3747
4195
  send <from> <to> --link <url> [--title T] 发送链接卡片
3748
4196
  send <from> <to> --payload <json> 发送自定义 payload
3749
4197
  pull <from> [--after-seq N] [--limit N] 拉取收件箱
3750
- ack <from> <seq> --app <name> 确认已读(必须传 --app)
4198
+ ack <from> <seq> [--app <name>] 确认已读
3751
4199
  recall <from> <message-id> [<message-id>...] 撤回消息
3752
4200
  online <from> <target-aid> [<target-aid>...] 查询在线状态
3753
4201
 
3754
4202
  Options:
3755
- --app <name> 指定应用 slot(隔离 ack 游标)
3756
- --as-daemon ack 时显式以 daemon 身份(高危,会污染 daemon 游标)
4203
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
3757
4204
  --format json 输出 JSON 格式
3758
4205
  --encrypt 启用端到端加密
3759
4206
  --thread <id> 指定话题 ID(用于多话题路由)
@@ -3879,7 +4326,7 @@ Options:
3879
4326
  }
3880
4327
  if (sub === 'pull') {
3881
4328
  if (!appSlot) {
3882
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot(可能与 daemon 看到同一批消息)');
4329
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
3883
4330
  }
3884
4331
  const afterSeqStr = getArgValue(args, '--after-seq');
3885
4332
  const limitStr = getArgValue(args, '--limit');
@@ -3921,7 +4368,7 @@ Options:
3921
4368
  if (sub === 'ack') {
3922
4369
  const seqStr = args[2];
3923
4370
  if (!seqStr) {
3924
- console.error('用法: evolclaw msg ack <from> <seq> --app <name>');
4371
+ console.error('用法: evolclaw msg ack <from> <seq> [--app <name>]');
3925
4372
  process.exit(1);
3926
4373
  }
3927
4374
  const seq = Number(seqStr);
@@ -3929,10 +4376,8 @@ Options:
3929
4376
  console.error(`❌ seq 必须是数字: ${seqStr}`);
3930
4377
  process.exit(1);
3931
4378
  }
3932
- if (!appSlot && !asDaemon) {
3933
- console.error(' ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
3934
- console.error(' 理由: 不传 --app 会推进 daemon 共享的 ack 游标,导致 daemon 丢消息');
3935
- process.exit(1);
4379
+ if (!appSlot) {
4380
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
3936
4381
  }
3937
4382
  const result = await msgAck({ from, seq, ...commonOpts });
3938
4383
  if (!result.ok) {
@@ -4023,8 +4468,7 @@ async function cmdGroup(args) {
4023
4468
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4024
4469
  const appIdx = args.indexOf('--app');
4025
4470
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4026
- const asDaemon = args.includes('--as-daemon');
4027
- if (!sub || sub === 'help') {
4471
+ if (!sub || isHelpFlag(sub)) {
4028
4472
  console.log(`用法: evolclaw group <command> <from-aid> [args...] [options]
4029
4473
 
4030
4474
  消息:
@@ -4032,7 +4476,7 @@ async function cmdGroup(args) {
4032
4476
  send <from> <group-id> --file <path> [--as <type>] 发送群文件
4033
4477
  send <from> <group-id> --payload <json> 发送自定义 payload
4034
4478
  pull <from> <group-id> [--after-seq N] [--limit N] 拉取群消息
4035
- ack <from> <group-id> <seq> --app <name> 确认已读(必须传 --app)
4479
+ ack <from> <group-id> <seq> [--app <name>] 确认已读
4036
4480
 
4037
4481
  群管理:
4038
4482
  create <from> <name> [--visibility public|private] [--description D] [--join-mode M] 创建群
@@ -4050,8 +4494,7 @@ async function cmdGroup(args) {
4050
4494
  online <from> <group-id> 查看在线成员
4051
4495
 
4052
4496
  Options:
4053
- --app <name> 指定应用 slot(隔离 ack 游标)
4054
- --as-daemon ack 时显式以 daemon 身份(高危)
4497
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4055
4498
  --format json 输出 JSON 格式
4056
4499
  --mention <aid> 发送时 @ 某个成员(可多次)
4057
4500
  --mention-all 发送时 @ 所有人
@@ -4164,7 +4607,7 @@ Options:
4164
4607
  if (sub === 'pull') {
4165
4608
  const groupId = requireGroupId();
4166
4609
  if (!appSlot) {
4167
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot');
4610
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
4168
4611
  }
4169
4612
  const afterSeqStr = getArgValue(args, '--after-seq');
4170
4613
  const limitStr = getArgValue(args, '--limit');
@@ -4185,7 +4628,7 @@ Options:
4185
4628
  const groupId = requireGroupId();
4186
4629
  const seqStr = args[3];
4187
4630
  if (!seqStr) {
4188
- console.error('用法: evolclaw group ack <from> <group-id> <seq> --app <name>');
4631
+ console.error('用法: evolclaw group ack <from> <group-id> <seq> [--app <name>]');
4189
4632
  process.exit(1);
4190
4633
  }
4191
4634
  const seq = Number(seqStr);
@@ -4193,9 +4636,8 @@ Options:
4193
4636
  console.error(`❌ seq 必须是数字: ${seqStr}`);
4194
4637
  process.exit(1);
4195
4638
  }
4196
- if (!appSlot && !asDaemon) {
4197
- console.error(' group ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
4198
- process.exit(1);
4639
+ if (!appSlot) {
4640
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
4199
4641
  }
4200
4642
  const result = await groupAck({ from, groupId, seq, ...commonOpts });
4201
4643
  outputResult(result, () => {
@@ -4424,7 +4866,7 @@ export async function main(args) {
4424
4866
  }
4425
4867
  switch (cmd) {
4426
4868
  case 'init':
4427
- if (args[1] === 'help') {
4869
+ if (isHelpFlag(args[1])) {
4428
4870
  console.log(`用法: evolclaw init [渠道] [选项]
4429
4871
 
4430
4872
  仅初始化 defaults.json:
@@ -4500,7 +4942,7 @@ export async function main(args) {
4500
4942
  await cmdWatchAid();
4501
4943
  }
4502
4944
  else if (args[1] === 'msg') {
4503
- if (args[2] === '--help' || args[2] === '-h' || args[2] === 'help') {
4945
+ if (isHelpFlag(args[2])) {
4504
4946
  console.log(`用法: evolclaw watch msg
4505
4947
 
4506
4948
  三面板交互式消息监控 TUI。
@@ -4525,6 +4967,9 @@ export async function main(args) {
4525
4967
  else if (args[1] === 'log') {
4526
4968
  cmdWatch();
4527
4969
  }
4970
+ else if (args[1] === 'web' || args[1] === 'session') {
4971
+ await cmdWatchWeb();
4972
+ }
4528
4973
  else if (!args[1]) {
4529
4974
  await cmdWatchMenu();
4530
4975
  }
@@ -4588,6 +5033,11 @@ export async function main(args) {
4588
5033
  await cmdGroup(args.slice(1));
4589
5034
  break;
4590
5035
  }
5036
+ case 'model': {
5037
+ const { cmdModel } = await import('./model.js');
5038
+ await cmdModel(args.slice(1));
5039
+ break;
5040
+ }
4591
5041
  case 'bench': {
4592
5042
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
4593
5043
  suppressSdkLogs();
@@ -4638,14 +5088,25 @@ Commands:
4638
5088
  --force (覆盖已有 config.json)
4639
5089
  agent reload 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
4640
5090
  agent reload <n> 热重载指定 agent 配置
5091
+ model 模型管理(查看/切换,作用域:全局/agent/关系/会话)
5092
+ model list 列出可用模型,标注各作用域命中
5093
+ model current 显示实际生效的模型 + 来源
5094
+ model info <id> 查看单个模型详情(价格/上下文/模态等)
5095
+ model use <id> 切换模型(--self/--peer/--session 决定作用域)
5096
+ model effort <lv> 设置推理强度
5097
+ model reset 清除指定作用域设置,回落上一级
5098
+ model help 查看完整用法
4641
5099
  aid AID 身份管理
4642
5100
  aid list 列出本地所有 AID
4643
5101
  aid show <aid> 查看本地 AID 详情(证书有效期、私钥状态)
4644
5102
  aid new <aid> 创建新 AID 身份
4645
- aid delete <aid> 删除本地 AID
4646
5103
  aid lookup <aid> 远程探测 AID(是否存在 + 网关 + agent.md)
4647
5104
  aid agentmd put <aid> 签名并上传 agent.md
4648
5105
  aid agentmd get <aid> 下载并验签 agent.md
5106
+ aid delete <aid> 删除指定 AID
5107
+ aid delete --orphan 清理无私钥的外部 AID 缓存
5108
+ aid delete --unrecoverable 清理云端公钥已变更、不可恢复的 AID
5109
+ 默认 dry-run,加 --yes 执行
4649
5110
  net 网络链路诊断
4650
5111
  net check [<aid>] 10 步链路检测(DNS→Discovery→TCP→TLS→WSS→Auth→Ping→Echo)
4651
5112
  net help 查看详细帮助