evolclaw 3.1.0 → 3.1.2

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 (45) hide show
  1. package/CHANGELOG.md +407 -0
  2. package/README.md +1 -1
  3. package/SKILLS.md +311 -0
  4. package/dist/agents/claude-runner.js +40 -3
  5. package/dist/aun/aid/agentmd.js +7 -6
  6. package/dist/aun/aid/client.js +5 -11
  7. package/dist/aun/aid/identity.js +32 -13
  8. package/dist/aun/msg/group.js +1 -0
  9. package/dist/aun/msg/p2p.js +51 -0
  10. package/dist/aun/msg/upload.js +57 -18
  11. package/dist/channels/aun.js +124 -50
  12. package/dist/channels/dingtalk.js +2 -0
  13. package/dist/channels/feishu.js +15 -6
  14. package/dist/channels/qqbot.js +2 -0
  15. package/dist/channels/wechat.js +2 -0
  16. package/dist/channels/wecom.js +2 -0
  17. package/dist/cli/agent.js +130 -35
  18. package/dist/cli/index.js +221 -48
  19. package/dist/cli/init-channel.js +4 -2
  20. package/dist/cli/init.js +44 -23
  21. package/dist/cli/watch-msg.js +109 -30
  22. package/dist/config-store.js +67 -1
  23. package/dist/core/channel-loader.js +4 -4
  24. package/dist/core/command-handler.js +95 -84
  25. package/dist/core/evolagent-registry.js +45 -9
  26. package/dist/core/evolagent.js +4 -4
  27. package/dist/core/message/im-renderer.js +47 -8
  28. package/dist/core/message/message-bridge.js +30 -1
  29. package/dist/core/message/message-log.js +6 -1
  30. package/dist/core/message/message-processor.js +29 -35
  31. package/dist/core/relation/peer-identity.js +161 -0
  32. package/dist/core/session/session-fs-store.js +23 -0
  33. package/dist/core/session/session-manager.js +11 -4
  34. package/dist/core/trigger/manager.js +16 -0
  35. package/dist/core/trigger/parser.js +110 -0
  36. package/dist/core/trigger/scheduler.js +6 -0
  37. package/dist/index.js +64 -20
  38. package/dist/paths.js +35 -0
  39. package/dist/utils/cross-platform.js +2 -1
  40. package/dist/utils/error-utils.js +17 -13
  41. package/dist/utils/stats.js +216 -2
  42. package/kits/docs/INDEX.md +6 -0
  43. package/kits/docs/evolclaw/MSG_PRIVATE.md +53 -6
  44. package/kits/rules/06-channel.md +30 -0
  45. package/package.json +6 -3
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import os from 'os';
5
5
  import { spawn, execFileSync, execFile } from 'child_process';
6
6
  import { promisify } from 'util';
7
- import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot } from '../paths.js';
7
+ import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
8
8
  import { loadDefaults, loadAllAgents, mergeForAgent } from '../config-store.js';
9
9
  import { resolveAnthropicConfig } from '../agents/resolve.js';
10
10
  import { migrateProject } from '../config-store.js';
@@ -231,11 +231,61 @@ function reportOrphans(orphans) {
231
231
  console.log(' 这些进程不属于当前 HOME 的实例登记簿,自动清理不会处理它们。');
232
232
  console.log(' 使用 evolclaw restart --clear 一并清掉,或手动 kill。');
233
233
  }
234
- async function cmdStart() {
235
- const cmdStartedAt = Date.now();
234
+ function formatLocalTime(ms) {
235
+ const d = new Date(ms);
236
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
237
+ }
238
+ function printStartupInfo(opts = {}) {
236
239
  const pkgRoot = getPackageRoot();
237
240
  const isNpmInstall = pkgRoot.includes('node_modules');
238
- console.log(`⏱ ${new Date().toLocaleString()} [${isNpmInstall ? 'pkg' : 'dev'}] ${pkgRoot}`);
241
+ const cliRunsSource = !import.meta.url.includes('/dist/');
242
+ const daemonEntry = path.join(pkgRoot, 'dist', 'index.js');
243
+ const daemonRunsDist = fs.existsSync(daemonEntry);
244
+ const scanDir = path.join(pkgRoot, daemonRunsDist ? 'dist' : 'src');
245
+ let latestMtime = 0;
246
+ const scanRecursive = (dir) => {
247
+ try {
248
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
249
+ if (entry.name === 'node_modules')
250
+ continue;
251
+ const full = path.join(dir, entry.name);
252
+ if (entry.isDirectory()) {
253
+ scanRecursive(full);
254
+ continue;
255
+ }
256
+ if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
257
+ const mt = fs.statSync(full).mtimeMs;
258
+ if (mt > latestMtime)
259
+ latestMtime = mt;
260
+ }
261
+ }
262
+ }
263
+ catch { }
264
+ };
265
+ scanRecursive(scanDir);
266
+ let version = '?';
267
+ try {
268
+ version = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf-8')).version;
269
+ }
270
+ catch { }
271
+ let aunVer = null;
272
+ try {
273
+ aunVer = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'node_modules', '@agentunion', 'fastaun', 'package.json'), 'utf-8')).version;
274
+ }
275
+ catch { }
276
+ const pidPart = opts.pid ? ` (PID: ${opts.pid})` : '';
277
+ const aunPart = aunVer ? ` fastaun v${aunVer}` : '';
278
+ const prefix = opts.running ? '✓ EvolClaw is running , v' : ' EvolClaw v';
279
+ console.log(`${prefix}${version}${pidPart}${aunPart}`);
280
+ console.log(` 包路径: ${pkgRoot}`);
281
+ console.log(` 安装类型: ${isNpmInstall ? 'npm全局安装' : '开发仓(link)'}`);
282
+ console.log(` CLI执行: ${cliRunsSource ? '源码(tsx)' : '编译产物(dist)'}`);
283
+ console.log(` Daemon执行: ${daemonRunsDist ? '编译产物(dist)' : '未知'}`);
284
+ console.log(` 代码时间: ${latestMtime ? formatLocalTime(latestMtime) : '?'}`);
285
+ }
286
+ async function cmdStart() {
287
+ const cmdStartedAt = Date.now();
288
+ printStartupInfo();
239
289
  const p = resolvePaths();
240
290
  ensureDataDirs();
241
291
  // 旧配置自动迁移(evolclaw.json → 新结构)
@@ -263,7 +313,7 @@ async function cmdStart() {
263
313
  if (aliveMains.length > 0) {
264
314
  const first = aliveMains[0];
265
315
  console.log(`❌ EvolClaw is already running (PID: ${aliveMains.map(m => m.record.pid).join(', ')})`);
266
- console.log(` 启动于: ${first.record.startedAtIso}`);
316
+ console.log(` 启动于: ${new Date(first.record.startedAtIso).toLocaleString()}`);
267
317
  console.log(` 启动方式: ${first.record.launchedBy}`);
268
318
  // 报告 AID 状态
269
319
  if (status.aidLastActivity.size > 0) {
@@ -444,9 +494,7 @@ async function cmdStop() {
444
494
  }
445
495
  async function cmdRestart(opts = {}) {
446
496
  const cmdStartedAt = Date.now();
447
- const pkgRoot = getPackageRoot();
448
- const isNpmInstall = pkgRoot.includes('node_modules');
449
- console.log(`⏱ ${new Date().toLocaleString()} [${isNpmInstall ? 'pkg' : 'dev'}] ${pkgRoot}`);
497
+ printStartupInfo();
450
498
  console.log('🔄 Restarting EvolClaw...');
451
499
  // 版本检查与自动升级
452
500
  console.log('📦 Checking for updates...');
@@ -789,7 +837,7 @@ async function cmdStatus() {
789
837
  console.log('');
790
838
  }
791
839
  if (pid) {
792
- console.log(`✓ EvolClaw is running (PID: ${pid})`);
840
+ printStartupInfo({ pid, running: true });
793
841
  console.log('');
794
842
  console.log('📊 Process Info:');
795
843
  try {
@@ -835,8 +883,8 @@ async function cmdStatus() {
835
883
  const configChannelNames = new Set();
836
884
  for (const cfg of agents) {
837
885
  for (const inst of cfg.channels) {
838
- // effective key: <aid>#<type>#<name>
839
- configChannelNames.add(`${cfg.aid}#${inst.type}#${inst.name}`);
886
+ // effective key: <type>#<urlEncode(selfPeerId)>#<name>
887
+ configChannelNames.add(`${inst.type}#${encodeURIComponent(cfg.aid)}#${inst.name}`);
840
888
  }
841
889
  }
842
890
  for (const s of allSessions) {
@@ -965,7 +1013,7 @@ async function cmdStatus() {
965
1013
  }
966
1014
  }
967
1015
  /**
968
- * 把 channel fingerprint 列表(`<aid>#<type>#<name>`)折叠成展示用摘要。
1016
+ * 把 channel fingerprint 列表(`<type>#<selfPeerId>#<name>`)折叠成展示用摘要。
969
1017
  *
970
1018
  * 聚合规则:
971
1019
  * - 按 type 分组
@@ -987,8 +1035,8 @@ function summarizeChannelFingerprints(fingerprints) {
987
1035
  }
988
1036
  continue;
989
1037
  }
990
- const type = parts[1];
991
- const name = parts.slice(2).join('#');
1038
+ const type = parts[0];
1039
+ const name = parts[2];
992
1040
  if (!groups.has(type)) {
993
1041
  groups.set(type, []);
994
1042
  order.push(type);
@@ -1523,7 +1571,7 @@ function cmdWatch() {
1523
1571
  }
1524
1572
  const m = aliveMainEntries[0].record;
1525
1573
  const uptime = formatTimeAgo(Date.now() - m.startedAt);
1526
- console.log(`📦 Instance: PID ${m.pid} | 启动于 ${m.startedAtIso} (${uptime}) | via ${m.launchedBy}`);
1574
+ console.log(`📦 Instance: PID ${m.pid} | 启动于 ${new Date(m.startedAtIso).toLocaleString()} (${uptime}) | via ${m.launchedBy}`);
1527
1575
  if (instStatus.aidLastActivity.size > 0) {
1528
1576
  const now = Date.now();
1529
1577
  const aidLines = [];
@@ -1694,10 +1742,10 @@ async function cmdWatchAid() {
1694
1742
  const refreshedAids = new Set();
1695
1743
  function readLocalName(aid) {
1696
1744
  try {
1697
- const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aid, 'agent.md');
1698
- if (!fs.existsSync(agentMdPath))
1745
+ const mdPath = agentMdPath(aid);
1746
+ if (!fs.existsSync(mdPath))
1699
1747
  return undefined;
1700
- const content = fs.readFileSync(agentMdPath, 'utf-8');
1748
+ const content = fs.readFileSync(mdPath, 'utf-8');
1701
1749
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
1702
1750
  if (!fmMatch)
1703
1751
  return undefined;
@@ -1773,6 +1821,11 @@ async function cmdWatchAid() {
1773
1821
  const COL_LRECV = 10;
1774
1822
  const COL_LSENT = 10;
1775
1823
  const COL_PEERS = 5;
1824
+ // 表头跟随系统语言
1825
+ const isChinese = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || Intl.DateTimeFormat().resolvedOptions().locale || '').toLowerCase().includes('zh');
1826
+ const HEADERS = isChinese
1827
+ ? { aid: 'AID', status: '状态', uptime: '运行', state: '工作', reconn: '重连', recv: '收', sent: '发', sys: '系统', bin: '入流量', bout: '出流量', lrecv: '最后收', lsent: '最后发', peers: '对端' }
1828
+ : { aid: 'AID', status: 'STATUS', uptime: 'UPTIME', state: 'STATE', reconn: 'RECONN', recv: 'RECV', sent: 'SENT', sys: 'SYS R/S', bin: 'BYTES IN', bout: 'BYTES OUT', lrecv: 'LAST RECV', lsent: 'LAST SENT', peers: 'PEERS' };
1776
1829
  function formatDuration(ms) {
1777
1830
  const sec = Math.floor(ms / 1000);
1778
1831
  if (sec < 60)
@@ -1790,19 +1843,19 @@ async function cmdWatchAid() {
1790
1843
  }
1791
1844
  function renderHeader() {
1792
1845
  return ' ' +
1793
- padRight('AID', COL_AID) +
1794
- padRight('STATUS', COL_STATUS) +
1795
- padRight('UPTIME', COL_UPTIME) +
1796
- padRight('STATE', COL_STATE) +
1797
- padRight('RECONN', COL_RECONN) +
1798
- padRight('RECV', COL_RECV) +
1799
- padRight('SENT', COL_SENT) +
1800
- padRight('SYS R/S', COL_SYS) +
1801
- padRight('BYTES IN', COL_BIN) +
1802
- padRight('BYTES OUT', COL_BOUT) +
1803
- padRight('LAST RECV', COL_LRECV) +
1804
- padRight('LAST SENT', COL_LSENT) +
1805
- padRight('PEERS', COL_PEERS);
1846
+ padRight(HEADERS.aid, COL_AID) +
1847
+ padRight(HEADERS.status, COL_STATUS) +
1848
+ padRight(HEADERS.uptime, COL_UPTIME) +
1849
+ padRight(HEADERS.state, COL_STATE) +
1850
+ padRight(HEADERS.reconn, COL_RECONN) +
1851
+ padRight(HEADERS.recv, COL_RECV) +
1852
+ padRight(HEADERS.sent, COL_SENT) +
1853
+ padRight(HEADERS.sys, COL_SYS) +
1854
+ padRight(HEADERS.bin, COL_BIN) +
1855
+ padRight(HEADERS.bout, COL_BOUT) +
1856
+ padRight(HEADERS.lrecv, COL_LRECV) +
1857
+ padRight(HEADERS.lsent, COL_LSENT) +
1858
+ padRight(HEADERS.peers, COL_PEERS);
1806
1859
  }
1807
1860
  function renderRow(aid, stats, projectPath) {
1808
1861
  const aidLabel = aid.aid.length > COL_AID - 2 ? aid.aid.slice(0, COL_AID - 4) + '..' : aid.aid;
@@ -1841,6 +1894,13 @@ async function cmdWatchAid() {
1841
1894
  const nameReset = refreshedAids.has(aid.aid) ? '' : RST;
1842
1895
  const BLUE = useColor ? '\x1b[34m' : '';
1843
1896
  const ORANGE = useColor ? '\x1b[38;5;208m' : '';
1897
+ const MAGENTA = useColor ? '\x1b[35m' : '';
1898
+ // 标记生成:[明文/密文|自主/响应](紫色=工具渲染标记)
1899
+ const mkTags = (encrypt, chatmode) => {
1900
+ const enc = encrypt ? '密文' : '明文';
1901
+ const mode = chatmode === 'proactive' ? '自主' : '响应';
1902
+ return `${MAGENTA}[${enc}|${mode}]${RST}`;
1903
+ };
1844
1904
  let msgPreview = '';
1845
1905
  if (stats?.lastReceivedAt || stats?.lastSentAt) {
1846
1906
  const recvTs = stats.lastReceivedAt ?? 0;
@@ -1851,13 +1911,53 @@ async function cmdWatchAid() {
1851
1911
  }
1852
1912
  else if (stats.lastSentText) {
1853
1913
  const toShort = stats.lastSentTo ? stats.lastSentTo.split('.')[0] : '';
1854
- msgPreview = `${BLUE}↑ ${toShort ? `${ORANGE}${toShort}${RST}${BLUE}: ` : ''}${stats.lastSentText.replace(/\n/g, ' ').slice(0, 60)}${RST}`;
1914
+ const tags = mkTags(stats.lastSentEncrypt, stats.lastSentChatmode);
1915
+ // task 进行中时也显示计数(processing > 0 说明还在跑)
1916
+ const isWorking = (stats.processing ?? 0) > 0;
1917
+ const taskEnd = stats?.lastTaskEnd;
1918
+ const counts = isWorking && taskEnd
1919
+ ? `${MAGENTA}[大模型${taskEnd.numTurns}|调用${taskEnd.toolUseCount}|thought${taskEnd.thoughtPutCount}|msg${taskEnd.replyCount}]${RST}`
1920
+ : '';
1921
+ msgPreview = `${BLUE}↑${tags}${counts} ${toShort ? `${ORANGE}${toShort}${RST}${BLUE}: ` : ''}${stats.lastSentText.replace(/\n/g, ' ').slice(0, 60)}${RST}`;
1855
1922
  }
1856
1923
  else if (stats.lastReceivedText) {
1857
1924
  const fromShort = stats.lastReceivedFrom ? stats.lastReceivedFrom.split('.')[0] : '';
1858
1925
  msgPreview = `${GREEN}↓ ${fromShort ? `${ORANGE}${fromShort}${RST}${GREEN}: ` : ''}${stats.lastReceivedText.replace(/\n/g, ' ').slice(0, 60)}${RST}`;
1859
1926
  }
1860
1927
  }
1928
+ // 任务结束状态覆盖:仅当 taskEnd 比最后收发都新时才覆盖
1929
+ const taskEnd = stats?.lastTaskEnd;
1930
+ if (taskEnd && taskEnd.ts >= (stats?.lastSentAt ?? 0) && taskEnd.ts >= (stats?.lastReceivedAt ?? 0)) {
1931
+ const tags = mkTags(taskEnd.encrypt, taskEnd.chatmode);
1932
+ // 计数标记: [大模型N|调用N|thoughtN(streamN)|msgN]
1933
+ const thoughtLabel = taskEnd.thoughtPutCount > 0
1934
+ ? `thought${taskEnd.numTurns}(stream${taskEnd.thoughtPutCount})`
1935
+ : `thought${taskEnd.numTurns}`;
1936
+ const counts = `${MAGENTA}[大模型${taskEnd.numTurns}|调用${taskEnd.toolUseCount}|${thoughtLabel}|msg${taskEnd.replyCount}]${RST}`;
1937
+ if (taskEnd.status === 'error') {
1938
+ msgPreview = `${RED}${tags}${counts} 错误: ${taskEnd.errorType ?? '未知错误'}${RST}`;
1939
+ }
1940
+ else if (taskEnd.sentDuringTask) {
1941
+ // 有 message.send:蓝色加粗 + 内容
1942
+ const toShort = stats?.lastSentTo ? stats.lastSentTo.split('.')[0] : '';
1943
+ const textPreview = stats?.lastSentText ? stats.lastSentText.replace(/\n/g, ' ').slice(0, 60) : '';
1944
+ msgPreview = `${BOLD}${BLUE}↑${tags}${counts} ${toShort ? `${ORANGE}${toShort}${RST}${BOLD}${BLUE}: ` : ''}${textPreview}${RST}`;
1945
+ }
1946
+ else if (taskEnd.thoughtDuringTask) {
1947
+ // 只有 thought:普通蓝色 + thought 内容
1948
+ const textPreview = taskEnd.lastThoughtText
1949
+ ? taskEnd.lastThoughtText.replace(/\n/g, ' ').slice(0, 60)
1950
+ : (taskEnd.finalText ? taskEnd.finalText.replace(/\n/g, ' ').slice(0, 60) : '');
1951
+ msgPreview = `${BLUE}↑${tags}${counts} ${textPreview}${RST}`;
1952
+ }
1953
+ else {
1954
+ // 既没 send 也没 thought
1955
+ const textPreview = taskEnd.finalText
1956
+ ? taskEnd.finalText.replace(/\n/g, ' ').slice(0, 60)
1957
+ : '(无输出)';
1958
+ msgPreview = `${ORANGE}${tags}${counts} ${textPreview}${RST}`;
1959
+ }
1960
+ }
1861
1961
  const subLine1 = ` ${nameColor}${namePart}${nameReset}${msgPreview ? ' ' + msgPreview : ''}`;
1862
1962
  const dirLabel = projectPath || '—';
1863
1963
  const subLine2 = `${DIM} ${dirLabel}${RST}`;
@@ -2371,12 +2471,14 @@ function archiveSelfHealLog(p, log) {
2371
2471
  * Searches across all channel types (feishu, wechat, aun) for a matching instance.
2372
2472
  */
2373
2473
  function resolveInstanceConfig(instanceName) {
2374
- // 新结构:channel key 是 <aid>#<type>#<name>,解析后从对应 agent 的 channels[] 找
2474
+ // 新结构:channel key 是 <type>#<selfPeerId>#<name>,解析后从对应 agent 的 channels[] 找
2375
2475
  const parts = instanceName.split('#');
2376
2476
  if (parts.length === 3) {
2377
- const [aid, type, name] = parts;
2477
+ const [type, encodedSelfPeerId, name] = parts;
2478
+ const selfPeerId = decodeURIComponent(encodedSelfPeerId);
2378
2479
  const { agents } = loadAllAgents();
2379
- const agent = agents.find(a => a.aid === aid);
2480
+ // AUN channel selfPeerId 就是 agent.aid
2481
+ const agent = agents.find(a => a.aid === selfPeerId);
2380
2482
  if (!agent)
2381
2483
  return null;
2382
2484
  const inst = agent.channels.find((c) => c.type === type && c.name === name);
@@ -2698,7 +2800,7 @@ async function cmdCtl(args) {
2698
2800
  async function cmdAgent(args) {
2699
2801
  const sub = args[0];
2700
2802
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
2701
- if (sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
2803
+ if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
2702
2804
  console.log(`用法: evolclaw agent <command>
2703
2805
 
2704
2806
  Commands:
@@ -2750,17 +2852,43 @@ Options:
2750
2852
  console.log('No agents configured.');
2751
2853
  return;
2752
2854
  }
2753
- console.log('NAME'.padEnd(14) + 'STATUS'.padEnd(10) + 'CHANNELS'.padEnd(24) +
2754
- 'PROJECT'.padEnd(22) + 'BASEAGENT'.padEnd(11) + 'LAST ACTIVE');
2855
+ // 表头跟随系统语言
2856
+ const isChinese = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || Intl.DateTimeFormat().resolvedOptions().locale || '').toLowerCase().includes('zh');
2857
+ const headers = isChinese
2858
+ ? { name: '名称', status: '状态', channels: '渠道', project: '项目', baseagent: '基座', lastActive: '最后活跃' }
2859
+ : { name: 'NAME', status: 'STATUS', channels: 'CHANNELS', project: 'PROJECT', baseagent: 'BASEAGENT', lastActive: 'LAST ACTIVE' };
2860
+ // 计算各列实际需要的宽度
2861
+ let maxNameLen = headers.name.length;
2862
+ let maxStatusLen = headers.status.length;
2863
+ let maxChannelsLen = headers.channels.length;
2864
+ let maxProjectLen = headers.project.length;
2865
+ let maxBaseagentLen = headers.baseagent.length;
2866
+ for (const info of result.agents) {
2867
+ maxNameLen = Math.max(maxNameLen, info.name.length);
2868
+ maxStatusLen = Math.max(maxStatusLen, (info.status || 'stopped').length);
2869
+ const channelsStr = info.channels?.length > 0 ? info.channels.join(', ') : '—';
2870
+ maxChannelsLen = Math.max(maxChannelsLen, channelsStr.length);
2871
+ const projectStr = info.projectPath ? path.basename(info.projectPath) : '—';
2872
+ maxProjectLen = Math.max(maxProjectLen, projectStr.length);
2873
+ maxBaseagentLen = Math.max(maxBaseagentLen, (info.baseagent || '—').length);
2874
+ }
2875
+ // 加 2 作为列间距
2876
+ const colName = maxNameLen + 2;
2877
+ const colStatus = maxStatusLen + 2;
2878
+ const colChannels = maxChannelsLen + 1;
2879
+ const colProject = maxProjectLen + 2;
2880
+ const colBaseagent = maxBaseagentLen + 2;
2881
+ console.log(headers.name.padEnd(colName) + headers.status.padEnd(colStatus) + headers.channels.padEnd(colChannels) +
2882
+ headers.project.padEnd(colProject) + headers.baseagent.padEnd(colBaseagent) + headers.lastActive);
2755
2883
  for (const info of result.agents) {
2756
2884
  const name = info.name;
2757
2885
  const status = info.status || 'stopped';
2758
- const channels = info.channels?.length > 0 ? info.channels.join(', ').slice(0, 22) : '—';
2886
+ const channels = info.channels?.length > 0 ? info.channels.join(', ') : '—';
2759
2887
  const project = info.projectPath ? path.basename(info.projectPath) : '—';
2760
2888
  const baseagent = info.baseagent || '—';
2761
2889
  const lastActive = info.lastActivity ? formatTimeAgo(Date.now() - info.lastActivity) : '—';
2762
- console.log(name.padEnd(14) + status.padEnd(10) + channels.padEnd(24) +
2763
- project.padEnd(22) + baseagent.padEnd(11) + lastActive);
2890
+ console.log(name.padEnd(colName) + status.padEnd(colStatus) + channels.padEnd(colChannels) +
2891
+ project.padEnd(colProject) + baseagent.padEnd(colBaseagent) + lastActive);
2764
2892
  }
2765
2893
  return;
2766
2894
  }
@@ -2803,7 +2931,11 @@ Options:
2803
2931
  console.log(result.agentmdUploaded
2804
2932
  ? ' ✓ agent.md 已发布'
2805
2933
  : ' ⚠ agent.md 上传失败(可用 evolclaw aid agentmd put 重试)');
2806
- console.log(' Run `evolclaw restart` to activate.');
2934
+ console.log(result.hotLoaded
2935
+ ? ' ✓ 已热重载,agent 已上线'
2936
+ : result.hotLoadError
2937
+ ? ` ✗ 热重载失败:${result.hotLoadError}`
2938
+ : ' ⚠ 服务未运行,下次 evolclaw start 时生效');
2807
2939
  }
2808
2940
  }
2809
2941
  else {
@@ -2825,7 +2957,11 @@ Options:
2825
2957
  console.log(result.agentmdUploaded
2826
2958
  ? ' ✓ agent.md 已发布'
2827
2959
  : ' ⚠ agent.md 上传失败(可用 evolclaw aid agentmd put 重试)');
2828
- console.log(' Run `evolclaw restart` to activate.');
2960
+ console.log(result.hotLoaded
2961
+ ? ' ✓ 已热重载,agent 已上线'
2962
+ : result.hotLoadError
2963
+ ? ` ✗ 热重载失败:${result.hotLoadError}`
2964
+ : ' ⚠ 服务未运行,下次 evolclaw start 时生效');
2829
2965
  }
2830
2966
  }
2831
2967
  return;
@@ -3161,7 +3297,7 @@ async function cmdAid(args) {
3161
3297
  const sub = args[0] || 'list';
3162
3298
  const formatJson = args.includes('--format') && args[args.indexOf('--format') + 1] === 'json';
3163
3299
  const aunPath = resolveAunPath(args);
3164
- if (sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
3300
+ if (!sub || sub === 'help' || sub === '--help' || sub === '-h' || args.includes('--help') || args.includes('-h')) {
3165
3301
  console.log(`用法: evolclaw aid <command>
3166
3302
 
3167
3303
  Commands:
@@ -3320,8 +3456,7 @@ Options:
3320
3456
  console.error(`❌ 无效 AID 格式: ${aid}`);
3321
3457
  process.exit(1);
3322
3458
  }
3323
- const aunBase = aunPath ?? path.join(os.homedir(), '.aun');
3324
- const localPath = path.join(aunBase, 'AIDs', aid, 'agent.md');
3459
+ const localPath = agentMdPath(aid);
3325
3460
  if (!fs.existsSync(localPath)) {
3326
3461
  console.error(`❌ 本地无 agent.md: ${aid}`);
3327
3462
  process.exit(1);
@@ -3629,12 +3764,15 @@ Options:
3629
3764
  --app <name> 指定应用 slot(隔离 ack 游标)
3630
3765
  --as-daemon ack 时显式以 daemon 身份(高危,会污染 daemon 游标)
3631
3766
  --format json 输出 JSON 格式
3767
+ --encrypt 启用端到端加密
3768
+ --thread <id> 指定话题 ID(用于多话题路由)
3632
3769
  --content-type <mime> 显式覆盖 MIME(仅 --file 模式)
3633
3770
  --text <说明> 附件说明文字(仅 --file 模式)
3634
3771
  --transcript <text> 语音转写(仅 --as voice)
3635
3772
 
3636
3773
  示例:
3637
3774
  evolclaw msg send alice.agentid.pub bob.agentid.pub "hello"
3775
+ evolclaw msg send alice.agentid.pub bob.agentid.pub "讨论项目A" --thread "project-A"
3638
3776
  evolclaw msg send alice.agentid.pub bob.agentid.pub --file ./pic.png
3639
3777
  evolclaw msg send alice.agentid.pub bob.agentid.pub --file ./demo.mp4 --as video
3640
3778
  evolclaw msg send alice.agentid.pub bob.agentid.pub --link https://example.com --title "AUN"
@@ -3708,7 +3846,29 @@ Options:
3708
3846
  body = { mode: 'text', text };
3709
3847
  }
3710
3848
  const encrypt = args.includes('--encrypt');
3711
- const result = await msgSend({ from, to, body, encrypt, ...commonOpts });
3849
+ const thread = getArgValue(args, '--thread');
3850
+ // 文件上传进度展示(非 JSON 输出时)。仅在大文件降级到 HTTP PUT 阶段会逐块更新。
3851
+ let lastPctShown = -1;
3852
+ const onUploadProgress = formatJson ? undefined : (info) => {
3853
+ if (info.phase === 'inline')
3854
+ return; // 内联阶段不分块,跳过
3855
+ if (info.phase === 'http-put') {
3856
+ const pct = info.total > 0 ? Math.floor((info.bytes / info.total) * 100) : 0;
3857
+ if (pct === lastPctShown && info.bytes < info.total)
3858
+ return;
3859
+ lastPctShown = pct;
3860
+ const mb = (n) => (n / 1024 / 1024).toFixed(2);
3861
+ const eol = info.bytes >= info.total ? '\n' : '\r';
3862
+ process.stderr.write(` ⏫ uploading: ${pct}% (${mb(info.bytes)}/${mb(info.total)} MB)${eol}`);
3863
+ }
3864
+ else if (info.phase === 'session-create') {
3865
+ process.stderr.write(' ⏫ requesting upload session...\n');
3866
+ }
3867
+ else if (info.phase === 'session-complete') {
3868
+ process.stderr.write(' ⏫ finalizing upload...\n');
3869
+ }
3870
+ };
3871
+ const result = await msgSend({ from, to, body, encrypt, thread, onUploadProgress, ...commonOpts });
3712
3872
  if (!result.ok) {
3713
3873
  if (formatJson) {
3714
3874
  console.log(JSON.stringify(result));
@@ -4290,15 +4450,23 @@ export async function main(args) {
4290
4450
  evolclaw init wecom 企业微信 AI Bot 配置(手动输入)`);
4291
4451
  }
4292
4452
  else if (args[1] === 'wechat') {
4453
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4454
+ suppressSdkLogs();
4293
4455
  await cmdInitWechat();
4294
4456
  }
4295
4457
  else if (args[1] === 'feishu') {
4458
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4459
+ suppressSdkLogs();
4296
4460
  await cmdInitFeishu();
4297
4461
  }
4298
4462
  else if (args[1] === 'dingtalk') {
4463
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4464
+ suppressSdkLogs();
4299
4465
  await cmdInitDingtalk();
4300
4466
  }
4301
4467
  else if (args[1] === 'qqbot') {
4468
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4469
+ suppressSdkLogs();
4302
4470
  await cmdInitQQBot();
4303
4471
  }
4304
4472
  else if (args[1] === 'wecom') {
@@ -4311,6 +4479,8 @@ export async function main(args) {
4311
4479
  process.exit(1);
4312
4480
  }
4313
4481
  else {
4482
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4483
+ suppressSdkLogs();
4314
4484
  const nonInteractive = args.includes('--non-interactive');
4315
4485
  await cmdInit({
4316
4486
  nonInteractive,
@@ -4391,9 +4561,12 @@ export async function main(args) {
4391
4561
  case 'ctl':
4392
4562
  await cmdCtl(args.slice(1));
4393
4563
  break;
4394
- case 'agent':
4564
+ case 'agent': {
4565
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
4566
+ suppressSdkLogs();
4395
4567
  await cmdAgent(args.slice(1));
4396
4568
  break;
4569
+ }
4397
4570
  case 'aid': {
4398
4571
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
4399
4572
  suppressSdkLogs();
@@ -10,6 +10,7 @@ import readline from 'readline';
10
10
  import path from 'path';
11
11
  import os from 'os';
12
12
  import crypto from 'crypto';
13
+ import { aidLocalDir } from '../paths.js';
13
14
  import { selectInstance } from './init.js';
14
15
  import { npmInstallGlobal } from '../utils/npm-ops.js';
15
16
  import { loadAllAgents, loadAgent } from '../config-store.js';
@@ -470,8 +471,9 @@ export async function setupAunAid(rl, _config) {
470
471
  console.log(` ⚠ agent.md 发布失败(首次连接将自动重试): ${String(e.message || e).slice(0, 100)}`);
471
472
  // Still write local copy as fallback
472
473
  try {
473
- fs.mkdirSync(aidDir, { recursive: true });
474
- fs.writeFileSync(path.join(aidDir, 'agent.md'), content, 'utf-8');
474
+ const localDir = aidLocalDir(aid);
475
+ fs.mkdirSync(localDir, { recursive: true });
476
+ fs.writeFileSync(path.join(localDir, 'agent.md'), content, 'utf-8');
475
477
  console.log(' ✓ agent.md 已写入本地');
476
478
  }
477
479
  catch (we) {
package/dist/cli/init.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
3
2
  import readline from 'readline';
4
3
  import { resolvePaths, ensureDataDirs } from '../paths.js';
5
4
  import { commandExists } from '../utils/cross-platform.js';
6
5
  import { scanInstances } from '../utils/instance-registry.js';
6
+ import { saveDefaultsSafe, loadAllAgents } from '../config-store.js';
7
7
  // ==================== Helpers ====================
8
8
  function ask(rl, question) {
9
9
  return new Promise(resolve => rl.question(question, resolve));
@@ -28,9 +28,8 @@ function buildDefaults(chosen) {
28
28
  baseagents: { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} },
29
29
  };
30
30
  }
31
- function writeDefaults(defaultsPath, chosen) {
32
- fs.mkdirSync(path.dirname(defaultsPath), { recursive: true });
33
- fs.writeFileSync(defaultsPath, JSON.stringify(buildDefaults(chosen), null, 2) + '\n');
31
+ function writeDefaults(_defaultsPath, chosen) {
32
+ saveDefaultsSafe(buildDefaults(chosen));
34
33
  }
35
34
  // ==================== Main ====================
36
35
  export async function cmdInit(options) {
@@ -82,19 +81,21 @@ export async function cmdInit(options) {
82
81
  writeDefaults(defaultsPath, chosen);
83
82
  console.log(`✓ 已${exists ? '覆盖' : '创建'}: ${defaultsPath}`);
84
83
  console.log(` active_baseagent: ${chosen}`);
84
+ const { agents } = loadAllAgents();
85
+ if (agents.length === 0) {
86
+ console.log('\n提示:尚无 agent,运行以下命令创建:');
87
+ console.log(' evolclaw agent new <aid>.agentid.pub');
88
+ }
85
89
  return;
86
90
  }
87
91
  // ── 4. 交互式分支 ──
88
92
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
89
- try {
90
- if (exists) {
91
- const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
92
- if (ans !== 'y' && ans !== 'yes') {
93
- console.log(' 已取消');
94
- return;
95
- }
96
- }
93
+ async function askBaseagent() {
97
94
  const defaultBa = pickDefault(available);
95
+ if (available.length === 1) {
96
+ console.log(` baseagent: ${defaultBa}`);
97
+ return defaultBa;
98
+ }
98
99
  let chosen = null;
99
100
  while (chosen === null) {
100
101
  const input = (await ask(rl, `默认 baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
@@ -108,17 +109,37 @@ export async function cmdInit(options) {
108
109
  }
109
110
  chosen = input;
110
111
  }
111
- writeDefaults(defaultsPath, chosen);
112
- console.log(`\n✓ 已${exists ? '覆盖' : '创建'}: ${defaultsPath}`);
113
- console.log(` active_baseagent: ${chosen}\n`);
114
- rl.close();
115
- // ── 5. 嵌套 agent new ──
116
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
117
- console.log('下一步:创建 agent\n');
118
- const { agentCreateInteractive } = await import('./agent.js');
119
- const result = await agentCreateInteractive();
120
- if (!result.ok) {
121
- console.error(`❌ ${result.error}`);
112
+ return chosen;
113
+ }
114
+ try {
115
+ if (exists) {
116
+ const ans = (await ask(rl, `配置文件已存在: ${defaultsPath}\n 是否覆盖?[y/N] `)).trim().toLowerCase();
117
+ if (ans === 'y' || ans === 'yes') {
118
+ const chosen = await askBaseagent();
119
+ writeDefaults(defaultsPath, chosen);
120
+ console.log(`\n✓ 已覆盖: ${defaultsPath}`);
121
+ console.log(` active_baseagent: ${chosen}\n`);
122
+ }
123
+ else {
124
+ console.log(' 已跳过(保留现有配置)\n');
125
+ }
126
+ }
127
+ else {
128
+ const chosen = await askBaseagent();
129
+ writeDefaults(defaultsPath, chosen);
130
+ console.log(`\n✓ 已创建: ${defaultsPath}`);
131
+ console.log(` active_baseagent: ${chosen}\n`);
132
+ }
133
+ // ── 5. 无 agent 时自动进入 agent new ──
134
+ const { agents } = loadAllAgents();
135
+ if (agents.length === 0) {
136
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
137
+ console.log('下一步:创建 agent\n');
138
+ const { agentCreateInteractive } = await import('./agent.js');
139
+ const result = await agentCreateInteractive({ rl });
140
+ if (!result.ok) {
141
+ console.error(`❌ ${result.error}`);
142
+ }
122
143
  }
123
144
  }
124
145
  finally {