evolclaw 3.1.4 → 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 (85) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agents/claude-runner.js +348 -156
  3. package/dist/agents/kit-renderer.js +176 -21
  4. package/dist/aun/aid/agentmd.js +68 -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/p2p.js +26 -2
  10. package/dist/aun/rpc/connection.js +23 -30
  11. package/dist/channels/aun.js +77 -88
  12. package/dist/channels/dingtalk.js +1 -0
  13. package/dist/channels/feishu.js +270 -190
  14. package/dist/channels/qqbot.js +1 -0
  15. package/dist/channels/wechat.js +1 -0
  16. package/dist/channels/wecom.js +1 -0
  17. package/dist/cli/agent.js +11 -5
  18. package/dist/cli/bench.js +40 -23
  19. package/dist/cli/index.js +170 -44
  20. package/dist/cli/init-channel.js +5 -1
  21. package/dist/cli/model.js +324 -0
  22. package/dist/cli/net-check.js +133 -50
  23. package/dist/cli/watch-msg.js +7 -7
  24. package/dist/cli/watch-web/debug-log.js +18 -0
  25. package/dist/cli/watch-web/server.js +306 -0
  26. package/dist/cli/watch-web/sources/aid.js +63 -0
  27. package/dist/cli/watch-web/sources/msg.js +70 -0
  28. package/dist/cli/watch-web/sources/session.js +638 -0
  29. package/dist/cli/watch-web/sources/types.js +10 -0
  30. package/dist/cli/watch-web/static/app.js +546 -0
  31. package/dist/cli/watch-web/static/index.html +54 -0
  32. package/dist/cli/watch-web/static/style.css +247 -0
  33. package/dist/core/channel-loader.js +7 -4
  34. package/dist/core/command-handler.js +81 -86
  35. package/dist/core/evolagent-registry.js +1 -1
  36. package/dist/core/evolagent.js +4 -4
  37. package/dist/core/interaction-router.js +59 -0
  38. package/dist/core/message/message-bridge.js +6 -6
  39. package/dist/core/message/message-log.js +2 -2
  40. package/dist/core/message/message-processor.js +86 -101
  41. package/dist/core/message/stream-idle-monitor.js +21 -0
  42. package/dist/core/model/model-catalog.js +215 -0
  43. package/dist/core/model/model-scope.js +250 -0
  44. package/dist/core/relation/peer-identity.js +40 -49
  45. package/dist/core/relation/peer-key.js +16 -0
  46. package/dist/core/session/session-fs-store.js +34 -55
  47. package/dist/core/session/session-key.js +24 -0
  48. package/dist/core/session/session-manager.js +308 -251
  49. package/dist/core/session/session-mapper.js +9 -4
  50. package/dist/core/trigger/manager.js +3 -3
  51. package/dist/core/trigger/scheduler.js +2 -1
  52. package/dist/index.js +6 -2
  53. package/dist/ipc.js +22 -0
  54. package/kits/docs/GUIDE.md +2 -2
  55. package/kits/docs/INDEX.md +11 -7
  56. package/kits/docs/channels/aun.md +56 -17
  57. package/kits/docs/channels/feishu.md +41 -12
  58. package/kits/docs/context-assembly.md +181 -0
  59. package/kits/docs/evolclaw/agent.md +49 -0
  60. package/kits/docs/evolclaw/aid.md +49 -0
  61. package/kits/docs/evolclaw/ctl.md +46 -0
  62. package/kits/docs/evolclaw/group.md +82 -0
  63. package/kits/docs/evolclaw/msg.md +86 -0
  64. package/kits/docs/evolclaw/rpc.md +35 -0
  65. package/kits/docs/evolclaw/storage.md +49 -0
  66. package/kits/docs/venues/aun-group.md +10 -0
  67. package/kits/docs/venues/aun-private.md +10 -0
  68. package/kits/docs/venues/client-desktop.md +10 -0
  69. package/kits/docs/venues/client-mobile.md +10 -0
  70. package/kits/docs/venues/feishu-group.md +13 -0
  71. package/kits/docs/venues/feishu-private.md +9 -0
  72. package/kits/docs/venues/group.md +11 -0
  73. package/kits/docs/venues/private.md +10 -0
  74. package/kits/eck_manifest.json +72 -36
  75. package/kits/rules/01-overview.md +20 -10
  76. package/kits/rules/06-channel.md +30 -27
  77. package/kits/templates/system-fragments/session.md +10 -3
  78. package/kits/templates/system-fragments/venue.md +9 -0
  79. package/package.json +11 -6
  80. package/dist/aun/aid/lifecycle-log.js +0 -33
  81. package/dist/utils/aid-lifecycle-log.js +0 -33
  82. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  83. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  84. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  85. package/kits/docs/evolclaw/tools.md +0 -25
package/dist/cli/agent.js CHANGED
@@ -419,11 +419,14 @@ export async function agentCreateInteractive(opts = {}) {
419
419
  catch (e) {
420
420
  console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
421
421
  }
422
- // 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.
423
426
  let hotLoaded = false;
424
427
  let hotLoadError;
425
428
  try {
426
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid });
429
+ const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
427
430
  if (ipcResult?.ok) {
428
431
  hotLoaded = true;
429
432
  }
@@ -569,11 +572,14 @@ export async function agentCreateNonInteractive(opts) {
569
572
  catch (e) {
570
573
  console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
571
574
  }
572
- // 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.
573
579
  let hotLoaded = false;
574
580
  let hotLoadError;
575
581
  try {
576
- 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);
577
583
  if (ipcResult?.ok) {
578
584
  hotLoaded = true;
579
585
  }
@@ -829,7 +835,7 @@ export async function agentChannelUpsert(opts) {
829
835
  return {
830
836
  ok: true,
831
837
  aid: opts.aid,
832
- channelKey: `${opts.channel.type}#${encodeURIComponent(opts.aid)}#${opts.channel.name}`,
838
+ channelKey: `${opts.channel.type}#${opts.aid}#${opts.channel.name}`,
833
839
  reloaded,
834
840
  };
835
841
  }
package/dist/cli/bench.js CHANGED
@@ -7,7 +7,7 @@ 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
9
  import { getPackageRoot, aunPath as defaultAunPath } from '../paths.js';
10
- import { createAunClient } from '../aun/aid/client.js';
10
+ import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
11
11
  import { isHelpFlag } from './help.js';
12
12
  const execFileAsync = promisify(execFile);
13
13
  // ==================== ANSI ====================
@@ -248,34 +248,48 @@ function computeMetrics(results, received, durationSec) {
248
248
  function filterBySize(results, received, cls, durationSec) {
249
249
  return computeMetrics(results.filter(r => r.sizeClass === cls), received.filter(r => r.sizeClass === cls), durationSec);
250
250
  }
251
- async function benchAuth(aids, concurrency, aunPath, slotId) {
252
- const resolvedAunPath = aunPath ?? defaultAunPath();
253
- const tasks = aids.map(aid => async () => {
251
+ async function benchAuth(aids, concurrency, aunPath) {
252
+ const tasks = aids.map((aid, index) => async () => {
254
253
  const start = Date.now();
254
+ const slotId = `${SLOT.bench}-${index}`;
255
+ let store = null;
256
+ let client = null;
255
257
  try {
256
- const client = await createAunClient({ aunPath: resolvedAunPath });
257
- await client.auth.createAid({ aid });
258
- const authResult = await client.auth.authenticate({ aid });
259
- const accessToken = authResult?.access_token ?? client._access_token;
260
- const gateway = client._gatewayUrl ?? authResult?.gateway ?? '';
261
- 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;
262
263
  try {
263
264
  await client.close();
264
265
  }
265
266
  catch { }
266
- return { aid, ok: true, authMs: Date.now() - start, gateway };
267
+ try {
268
+ store.close();
269
+ }
270
+ catch { }
271
+ return { aid, ok: true, authMs, gateway };
267
272
  }
268
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 { }
269
284
  return { aid, ok: false, authMs: Date.now() - start, error: e.message };
270
285
  }
271
286
  });
272
287
  return runPool(tasks, concurrency);
273
288
  }
274
- async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
289
+ async function benchSwitch(aids, rounds, aunPath, useCli, encrypt) {
275
290
  const results = [];
276
291
  const start = Date.now();
277
292
  let seq = 0;
278
- const slot = slotId ?? 'bench';
279
293
  for (let round = 0; round < rounds; round++) {
280
294
  for (let i = 0; i < aids.length; i++) {
281
295
  const from = aids[i];
@@ -286,8 +300,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
286
300
  let ok = false;
287
301
  let serverTimestamp;
288
302
  if (useCli) {
303
+ const slotId = `${SLOT.bench}-${i}`;
289
304
  try {
290
- 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]}`);
291
306
  ok = res.ok;
292
307
  serverTimestamp = res.timestamp;
293
308
  }
@@ -296,8 +311,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
296
311
  }
297
312
  }
298
313
  else {
314
+ const slotId = `${SLOT.bench}-${i}`;
299
315
  try {
300
- 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]}`);
301
317
  ok = res.ok;
302
318
  serverTimestamp = res.ok ? res.timestamp : undefined;
303
319
  }
@@ -392,7 +408,6 @@ Options:
392
408
  const defaultWait = encrypt ? '10' : '3';
393
409
  const waitSec = Math.max(1, parseInt(getArgValue(args, '--wait') || defaultWait, 10));
394
410
  const sessionId = crypto.randomBytes(4).toString('hex');
395
- const benchSlot = `bench-${sessionId}`;
396
411
  if (!formatJson) {
397
412
  console.log(`\n${BOLD} evolclaw bench${RST} — AUN 消息性能基准测试`);
398
413
  console.log(` ${'━'.repeat(50)}`);
@@ -495,14 +510,15 @@ Options:
495
510
  authTasks.push(...aids);
496
511
  let authResults;
497
512
  if (cliMode) {
498
- const cliAuthTasks = authTasks.map(aid => async () => {
499
- 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);
500
516
  return { aid, ok: r.ok, authMs: r.authMs };
501
517
  });
502
518
  authResults = await runPool(cliAuthTasks, concurrency);
503
519
  }
504
520
  else {
505
- authResults = await benchAuth(authTasks, concurrency, aunPath, benchSlot);
521
+ authResults = await benchAuth(authTasks, concurrency, aunPath);
506
522
  }
507
523
  const authOk = authResults.filter(r => r.ok);
508
524
  const authFail = authResults.filter(r => !r.ok);
@@ -592,7 +608,7 @@ Options:
592
608
  // Suppress SDK error logs during send phase (we track errors ourselves)
593
609
  const origError2 = console.error;
594
610
  console.error = () => { };
595
- const sendFns = tasks.map(t => async () => {
611
+ const sendFns = tasks.map((t, taskIndex) => async () => {
596
612
  let lastError;
597
613
  let retries = 0;
598
614
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -605,10 +621,11 @@ Options:
605
621
  ? buildFileChunkText(t.seq, fileChunks.length, sendTs, sessionId, fileChunks[t.seq]?.toString('base64') ?? '')
606
622
  : buildMessageText(t.seq, t.sizeClass, sendTs, sessionId);
607
623
  const t0 = Date.now();
624
+ const slotId = `${SLOT.bench}-${taskIndex % concurrency}`;
608
625
  try {
609
626
  const sendPromise = cliMode
610
- ? cliSend(t.from, t.to, text, benchSlot, encrypt)
611
- : 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 });
612
629
  const res = await withTimeout(sendPromise, SEND_TIMEOUT_MS, `${t.from.split('.')[0]}→${t.to.split('.')[0]}`);
613
630
  if (cliMode) {
614
631
  const cliRes = res;
@@ -922,7 +939,7 @@ Options:
922
939
  if (!formatJson)
923
940
  console.log(`${DIM} Phase 5: 频繁切换账号收发测试${RST}`);
924
941
  const switchRounds = Math.max(2, Math.min(rounds, 5));
925
- const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, benchSlot, encrypt);
942
+ const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, encrypt);
926
943
  const switchOkResults = switchOut.results.filter(r => r.ok);
927
944
  const switchLatencies = switchOkResults
928
945
  .filter(r => r.serverTimestamp !== undefined)
package/dist/cli/index.js CHANGED
@@ -884,8 +884,8 @@ async function cmdStatus() {
884
884
  const configChannelNames = new Set();
885
885
  for (const cfg of agents) {
886
886
  for (const inst of cfg.channels) {
887
- // effective key: <type>#<urlEncode(selfPeerId)>#<name>
888
- configChannelNames.add(`${inst.type}#${encodeURIComponent(cfg.aid)}#${inst.name}`);
887
+ // effective key: <type>#<selfAID>#<name>
888
+ configChannelNames.add(`${inst.type}#${cfg.aid}#${inst.name}`);
889
889
  }
890
890
  }
891
891
  for (const s of allSessions) {
@@ -1014,7 +1014,7 @@ async function cmdStatus() {
1014
1014
  }
1015
1015
  }
1016
1016
  /**
1017
- * 把 channel fingerprint 列表(`<type>#<selfPeerId>#<name>`)折叠成展示用摘要。
1017
+ * 把 channel fingerprint 列表(`<type>#<selfAID>#<name>`)折叠成展示用摘要。
1018
1018
  *
1019
1019
  * 聚合规则:
1020
1020
  * - 按 type 分组
@@ -1414,6 +1414,7 @@ async function cmdWatchMenu() {
1414
1414
  { key: 'log', label: 'log', desc: 'real-time log tail' },
1415
1415
  { key: 'aid', label: 'aid', desc: 'AID connection stats' },
1416
1416
  { key: 'msg', label: 'msg', desc: 'message inspector' },
1417
+ { key: 'web', label: 'web', desc: 'browser dashboard (aid/msg/session)' },
1417
1418
  ];
1418
1419
  let index = 0;
1419
1420
  const useColor = !!process.stdout.isTTY;
@@ -1477,6 +1478,9 @@ async function cmdWatchMenu() {
1477
1478
  const { cmdWatchMsg } = await import('./watch-msg.js');
1478
1479
  await cmdWatchMsg();
1479
1480
  }
1481
+ else if (chosen === 'web') {
1482
+ await cmdWatchWeb();
1483
+ }
1480
1484
  resolve();
1481
1485
  }
1482
1486
  };
@@ -2098,6 +2102,97 @@ async function cmdWatchAid() {
2098
2102
  }
2099
2103
  platform.onShutdown(cleanup);
2100
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
+ }
2101
2196
  async function cmdRestartMonitor() {
2102
2197
  const p = resolvePaths();
2103
2198
  const restartLog = path.join(p.logs, 'restart.log');
@@ -2472,14 +2567,13 @@ function archiveSelfHealLog(p, log) {
2472
2567
  * Searches across all channel types (feishu, wechat, aun) for a matching instance.
2473
2568
  */
2474
2569
  function resolveInstanceConfig(instanceName) {
2475
- // 新结构:channel key 是 <type>#<selfPeerId>#<name>,解析后从对应 agent 的 channels[] 找
2570
+ // 新结构:channel key 是 <type>#<selfAID>#<name>,解析后从对应 agent 的 channels[] 找
2476
2571
  const parts = instanceName.split('#');
2477
2572
  if (parts.length === 3) {
2478
- const [type, encodedSelfPeerId, name] = parts;
2479
- const selfPeerId = decodeURIComponent(encodedSelfPeerId);
2573
+ const [type, selfAID, name] = parts;
2480
2574
  const { agents } = loadAllAgents();
2481
- // AUN channel 的 selfPeerId 就是 agent.aid
2482
- const agent = agents.find(a => a.aid === selfPeerId);
2575
+ // AUN channel 的 selfAID 就是 agent.aid
2576
+ const agent = agents.find(a => a.aid === selfAID);
2483
2577
  if (!agent)
2484
2578
  return null;
2485
2579
  const inst = agent.channels.find((c) => c.type === type && c.name === name);
@@ -3467,7 +3561,7 @@ Options:
3467
3561
  console.log('无匹配 AID');
3468
3562
  return;
3469
3563
  }
3470
- console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}:`);
3564
+ console.log(`本地 AID${noVerify ? '(静态扫描,未实测)' : ''}(${aunPath ?? resolveRoot()}):`);
3471
3565
  for (const a of aids) {
3472
3566
  const keyIcon = a.hasPrivateKey ? '🔑' : ' ';
3473
3567
  let signIcon = ' ';
@@ -3533,40 +3627,63 @@ Options:
3533
3627
  }
3534
3628
  if (sub === 'new') {
3535
3629
  if (wantsHelp(args)) {
3536
- console.log(`用法: evolclaw aid new <完整AID>
3630
+ console.log(`用法: evolclaw aid new <完整AID> [--force]
3537
3631
 
3538
3632
  创建新 AID 身份:生成 ECDSA 密钥对、向 Issuer 申请证书、构建并上传初始 agent.md。
3539
3633
 
3540
- 例: evolclaw aid new reviewer.agentid.pub`);
3634
+ 选项:
3635
+ --force 强制重新注册,覆盖已存在的身份(即使签名验证失败)
3636
+
3637
+ 例: evolclaw aid new reviewer.agentid.pub
3638
+ evolclaw aid new reviewer.agentid.pub --force`);
3541
3639
  return;
3542
3640
  }
3543
3641
  const aid = args[1];
3642
+ const force = args.includes('--force');
3544
3643
  if (!aid) {
3545
- 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');
3546
3645
  process.exit(1);
3547
3646
  }
3548
3647
  if (!isValidAid(aid)) {
3549
3648
  console.error(`❌ 无效 AID 格式: ${aid}`);
3550
3649
  process.exit(1);
3551
3650
  }
3552
- const result = await aidCreate(aid, { aunPath });
3553
- if (!result.alreadyExisted) {
3554
- 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
+ }
3555
3663
  try {
3556
- await agentmdPut(content, { aid, client: result.client, aunPath });
3557
- console.log('✓ agent.md 已发布');
3664
+ await result.client.close();
3558
3665
  }
3559
- catch (e) {
3560
- console.warn(`⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
3666
+ catch { }
3667
+ try {
3668
+ result.store?.close();
3561
3669
  }
3670
+ catch { }
3671
+ const verb = result.alreadyExisted ? '已存在且有效' : (force ? '已重新创建' : '已创建');
3672
+ console.log(`✓ ${aid} ${verb}`);
3673
+ console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3562
3674
  }
3563
- try {
3564
- 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;
3565
3686
  }
3566
- catch { }
3567
- const verb = result.alreadyExisted ? '已存在' : '已创建';
3568
- console.log(`✓ ${aid} ${verb}`);
3569
- console.log(' 如需上线 AUN 通道,运行 evolclaw agent new ' + aid);
3570
3687
  return;
3571
3688
  }
3572
3689
  if (sub === 'delete') {
@@ -4069,7 +4186,6 @@ async function cmdMsg(args) {
4069
4186
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4070
4187
  const appIdx = args.indexOf('--app');
4071
4188
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4072
- const asDaemon = args.includes('--as-daemon');
4073
4189
  if (!sub || isHelpFlag(sub)) {
4074
4190
  console.log(`用法: evolclaw msg <command> <from-aid> [args...] [options]
4075
4191
 
@@ -4079,13 +4195,12 @@ Commands:
4079
4195
  send <from> <to> --link <url> [--title T] 发送链接卡片
4080
4196
  send <from> <to> --payload <json> 发送自定义 payload
4081
4197
  pull <from> [--after-seq N] [--limit N] 拉取收件箱
4082
- ack <from> <seq> --app <name> 确认已读(必须传 --app)
4198
+ ack <from> <seq> [--app <name>] 确认已读
4083
4199
  recall <from> <message-id> [<message-id>...] 撤回消息
4084
4200
  online <from> <target-aid> [<target-aid>...] 查询在线状态
4085
4201
 
4086
4202
  Options:
4087
- --app <name> 指定应用 slot(隔离 ack 游标)
4088
- --as-daemon ack 时显式以 daemon 身份(高危,会污染 daemon 游标)
4203
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4089
4204
  --format json 输出 JSON 格式
4090
4205
  --encrypt 启用端到端加密
4091
4206
  --thread <id> 指定话题 ID(用于多话题路由)
@@ -4211,7 +4326,7 @@ Options:
4211
4326
  }
4212
4327
  if (sub === 'pull') {
4213
4328
  if (!appSlot) {
4214
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot(可能与 daemon 看到同一批消息)');
4329
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
4215
4330
  }
4216
4331
  const afterSeqStr = getArgValue(args, '--after-seq');
4217
4332
  const limitStr = getArgValue(args, '--limit');
@@ -4253,7 +4368,7 @@ Options:
4253
4368
  if (sub === 'ack') {
4254
4369
  const seqStr = args[2];
4255
4370
  if (!seqStr) {
4256
- console.error('用法: evolclaw msg ack <from> <seq> --app <name>');
4371
+ console.error('用法: evolclaw msg ack <from> <seq> [--app <name>]');
4257
4372
  process.exit(1);
4258
4373
  }
4259
4374
  const seq = Number(seqStr);
@@ -4261,10 +4376,8 @@ Options:
4261
4376
  console.error(`❌ seq 必须是数字: ${seqStr}`);
4262
4377
  process.exit(1);
4263
4378
  }
4264
- if (!appSlot && !asDaemon) {
4265
- console.error(' ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
4266
- console.error(' 理由: 不传 --app 会推进 daemon 共享的 ack 游标,导致 daemon 丢消息');
4267
- process.exit(1);
4379
+ if (!appSlot) {
4380
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
4268
4381
  }
4269
4382
  const result = await msgAck({ from, seq, ...commonOpts });
4270
4383
  if (!result.ok) {
@@ -4355,7 +4468,6 @@ async function cmdGroup(args) {
4355
4468
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
4356
4469
  const appIdx = args.indexOf('--app');
4357
4470
  const appSlot = appIdx >= 0 ? args[appIdx + 1] : undefined;
4358
- const asDaemon = args.includes('--as-daemon');
4359
4471
  if (!sub || isHelpFlag(sub)) {
4360
4472
  console.log(`用法: evolclaw group <command> <from-aid> [args...] [options]
4361
4473
 
@@ -4364,7 +4476,7 @@ async function cmdGroup(args) {
4364
4476
  send <from> <group-id> --file <path> [--as <type>] 发送群文件
4365
4477
  send <from> <group-id> --payload <json> 发送自定义 payload
4366
4478
  pull <from> <group-id> [--after-seq N] [--limit N] 拉取群消息
4367
- ack <from> <group-id> <seq> --app <name> 确认已读(必须传 --app)
4479
+ ack <from> <group-id> <seq> [--app <name>] 确认已读
4368
4480
 
4369
4481
  群管理:
4370
4482
  create <from> <name> [--visibility public|private] [--description D] [--join-mode M] 创建群
@@ -4382,8 +4494,7 @@ async function cmdGroup(args) {
4382
4494
  online <from> <group-id> 查看在线成员
4383
4495
 
4384
4496
  Options:
4385
- --app <name> 指定应用 slot(隔离 ack 游标)
4386
- --as-daemon ack 时显式以 daemon 身份(高危)
4497
+ --app <name> 指定应用 slot(独立消费通道,不影响 daemon)
4387
4498
  --format json 输出 JSON 格式
4388
4499
  --mention <aid> 发送时 @ 某个成员(可多次)
4389
4500
  --mention-all 发送时 @ 所有人
@@ -4496,7 +4607,7 @@ Options:
4496
4607
  if (sub === 'pull') {
4497
4608
  const groupId = requireGroupId();
4498
4609
  if (!appSlot) {
4499
- console.error('⚠ 警告: 未传 --app,将使用 daemon 共享 slot');
4610
+ console.warn('⚠ 警告: 未传 --app,当前与 daemon 共享 evolclaw 消费通道。pull 会看到/影响 daemon 的消息消费;如需独立消费请用 --app <name>');
4500
4611
  }
4501
4612
  const afterSeqStr = getArgValue(args, '--after-seq');
4502
4613
  const limitStr = getArgValue(args, '--limit');
@@ -4517,7 +4628,7 @@ Options:
4517
4628
  const groupId = requireGroupId();
4518
4629
  const seqStr = args[3];
4519
4630
  if (!seqStr) {
4520
- console.error('用法: evolclaw group ack <from> <group-id> <seq> --app <name>');
4631
+ console.error('用法: evolclaw group ack <from> <group-id> <seq> [--app <name>]');
4521
4632
  process.exit(1);
4522
4633
  }
4523
4634
  const seq = Number(seqStr);
@@ -4525,9 +4636,8 @@ Options:
4525
4636
  console.error(`❌ seq 必须是数字: ${seqStr}`);
4526
4637
  process.exit(1);
4527
4638
  }
4528
- if (!appSlot && !asDaemon) {
4529
- console.error(' group ack 必须传 --app <name>(或 --as-daemon 显式以 daemon 身份,高危)');
4530
- process.exit(1);
4639
+ if (!appSlot) {
4640
+ console.warn(' 警告: 未传 --app,ack 将推进与 daemon 共享的 evolclaw 消费游标,可能影响 daemon 收消息;如需独立请用 --app <name>');
4531
4641
  }
4532
4642
  const result = await groupAck({ from, groupId, seq, ...commonOpts });
4533
4643
  outputResult(result, () => {
@@ -4857,6 +4967,9 @@ export async function main(args) {
4857
4967
  else if (args[1] === 'log') {
4858
4968
  cmdWatch();
4859
4969
  }
4970
+ else if (args[1] === 'web' || args[1] === 'session') {
4971
+ await cmdWatchWeb();
4972
+ }
4860
4973
  else if (!args[1]) {
4861
4974
  await cmdWatchMenu();
4862
4975
  }
@@ -4920,6 +5033,11 @@ export async function main(args) {
4920
5033
  await cmdGroup(args.slice(1));
4921
5034
  break;
4922
5035
  }
5036
+ case 'model': {
5037
+ const { cmdModel } = await import('./model.js');
5038
+ await cmdModel(args.slice(1));
5039
+ break;
5040
+ }
4923
5041
  case 'bench': {
4924
5042
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
4925
5043
  suppressSdkLogs();
@@ -4970,6 +5088,14 @@ Commands:
4970
5088
  --force (覆盖已有 config.json)
4971
5089
  agent reload 全量 resync(扫磁盘,新增上线、删除下线、修改热更新)
4972
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 查看完整用法
4973
5099
  aid AID 身份管理
4974
5100
  aid list 列出本地所有 AID
4975
5101
  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);