evolclaw 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +1 -1
  2. package/bin/ec.js +29 -0
  3. package/dist/agents/baseagent-normalize.js +19 -0
  4. package/dist/agents/claude-runner.js +7 -9
  5. package/dist/agents/codex-runner.js +2 -0
  6. package/dist/agents/gemini-runner.js +9 -9
  7. package/dist/agents/kit-renderer.js +281 -0
  8. package/dist/aun/aid/identity.js +28 -0
  9. package/dist/aun/aid/index.js +1 -1
  10. package/dist/aun/aid/lifecycle-log.js +33 -0
  11. package/dist/aun/msg/group.js +3 -1
  12. package/dist/aun/msg/p2p.js +4 -1
  13. package/dist/channels/aun.js +353 -125
  14. package/dist/channels/dingtalk.js +2 -1
  15. package/dist/channels/feishu.js +118 -5
  16. package/dist/channels/qqbot.js +2 -1
  17. package/dist/channels/wechat.js +3 -1
  18. package/dist/channels/wecom.js +2 -1
  19. package/dist/cli/bench.js +1219 -0
  20. package/dist/cli/index.js +279 -19
  21. package/dist/cli/link-rules.js +245 -0
  22. package/dist/cli/net-check.js +640 -0
  23. package/dist/cli/watch-msg.js +589 -0
  24. package/dist/config-store.js +37 -5
  25. package/dist/core/channel-loader.js +23 -10
  26. package/dist/core/command-handler.js +46 -22
  27. package/dist/core/evolagent.js +5 -10
  28. package/dist/core/message/im-renderer.js +50 -44
  29. package/dist/core/message/items-formatter.js +11 -4
  30. package/dist/core/message/message-bridge.js +7 -2
  31. package/dist/core/message/message-log.js +2 -0
  32. package/dist/core/message/message-processor.js +150 -99
  33. package/dist/core/message/message-queue.js +10 -3
  34. package/dist/core/permission.js +95 -3
  35. package/dist/core/session/session-manager.js +98 -64
  36. package/dist/core/trigger/scheduler.js +1 -1
  37. package/dist/data/error-dict.json +118 -0
  38. package/dist/eck/baseagent-caps.js +18 -0
  39. package/dist/eck/detect.js +47 -0
  40. package/dist/eck/init.js +77 -0
  41. package/dist/eck/rules-loader.js +28 -0
  42. package/dist/index.js +137 -16
  43. package/dist/net-check.js +640 -0
  44. package/dist/paths.js +31 -40
  45. package/dist/utils/aid-lifecycle-log.js +33 -0
  46. package/dist/utils/atomic-write.js +10 -0
  47. package/dist/utils/cross-platform.js +17 -8
  48. package/dist/utils/error-utils.js +10 -2
  49. package/dist/utils/instance-registry.js +6 -5
  50. package/dist/utils/log-writer.js +2 -1
  51. package/dist/utils/logger.js +10 -0
  52. package/dist/utils/npm-ops.js +35 -3
  53. package/dist/utils/process-introspect.js +16 -38
  54. package/dist/watch-msg.js +26 -11
  55. package/evolclaw-install-aun.md +14 -2
  56. package/kits/docs/GUIDE.md +20 -0
  57. package/kits/docs/INDEX.md +52 -0
  58. package/kits/docs/aun/CHEATSHEET.md +17 -0
  59. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  60. package/kits/docs/channels/feishu.md +27 -0
  61. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  62. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  63. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  64. package/kits/docs/eck_templates/runtime.template.md +19 -0
  65. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  66. package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
  67. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  68. package/kits/docs/identity/PATH_OPS.md +16 -0
  69. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  70. package/kits/docs/path-registry.md +43 -0
  71. package/kits/eck_manifest.json +95 -0
  72. package/kits/rules/01-overview.md +120 -0
  73. package/kits/rules/02-navigation.md +75 -0
  74. package/kits/rules/03-identity.md +34 -0
  75. package/kits/rules/04-relation.md +49 -0
  76. package/kits/rules/05-venue.md +45 -0
  77. package/kits/rules/06-channel.md +43 -0
  78. package/kits/templates/system-fragments/baseagent.md +2 -0
  79. package/kits/templates/system-fragments/channel.md +10 -0
  80. package/kits/templates/system-fragments/identity.md +12 -0
  81. package/kits/templates/system-fragments/relation.md +9 -0
  82. package/kits/templates/system-fragments/runtime.md +19 -0
  83. package/kits/templates/system-fragments/venue.md +5 -0
  84. package/package.json +7 -5
  85. package/dist/agents/templates.js +0 -122
  86. package/dist/data/prompts.md +0 -137
  87. package/kits/aun/meta.md +0 -25
  88. package/kits/aun/role.md +0 -25
  89. package/kits/templates/group.md +0 -20
  90. package/kits/templates/private.md +0 -9
  91. package/kits/templates/system-fragments/personal-context.md +0 -3
  92. package/kits/templates/system-fragments/self-intro.md +0 -5
  93. package/kits/templates/system-fragments/speaker-intro.md +0 -5
  94. package/kits/templates/system-fragments/venue-intro.md +0 -5
  95. /package/kits/{channels → docs/channels}/aun.md +0 -0
  96. /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
  97. /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
  98. /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
  99. /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
package/dist/paths.js CHANGED
@@ -36,15 +36,13 @@ export function resolvePaths() {
36
36
  readySignal: path.join(root, 'data', 'instance', 'ready.signal'),
37
37
  selfHealLog: path.join(root, 'logs', 'self-heal.md'),
38
38
  socket: resolveInstanceSocketPath(root),
39
- // ── 新结构(evolclaw-home-directory.md)────────────────
39
+ // ── 新结构(evolclaw-directory-design.md)────────────────
40
40
  defaultsConfig: path.join(root, 'agents', 'defaults.json'),
41
- kitsDir: path.join(root, 'kits'),
42
- kitsAunDir: path.join(root, 'kits', 'aun'),
43
- kitsChannelsDir: path.join(root, 'kits', 'channels'),
44
- kitsEvolclawDir: path.join(root, 'kits', 'evolclaw'),
45
- kitsTemplatesDir: path.join(root, 'kits', 'templates'),
41
+ processConfig: path.join(root, 'config.json'),
42
+ eckDir: path.join(root, 'eck'),
46
43
  instanceReadySignal: path.join(root, 'data', 'instance', 'ready.signal'),
47
44
  instanceSocket: resolveInstanceSocketPath(root),
45
+ aidLogsDir: path.join(root, 'logs', 'aids'),
48
46
  };
49
47
  }
50
48
  // ── per-agent 路径(参数化,不进 resolvePaths() 的固定 map)──
@@ -57,8 +55,15 @@ export function agentConfig(aid) {
57
55
  export function agentPersonalDir(aid) {
58
56
  return path.join(agentDir(aid), 'personal');
59
57
  }
58
+ export function agentRelationsDir(aid) {
59
+ return path.join(agentDir(aid), 'relations');
60
+ }
61
+ /** @deprecated Use agentRelationsDir instead */
60
62
  export function agentIdentitiesDir(aid) {
61
- return path.join(agentDir(aid), 'identities');
63
+ return agentRelationsDir(aid);
64
+ }
65
+ export function agentIndexDir(aid) {
66
+ return path.join(agentDir(aid), 'index');
62
67
  }
63
68
  export function agentVenuesDir(aid) {
64
69
  return path.join(agentDir(aid), 'venues');
@@ -86,44 +91,30 @@ export function ensureDataDirs() {
86
91
  const p = resolvePaths();
87
92
  fs.mkdirSync(p.dataDir, { recursive: true });
88
93
  fs.mkdirSync(p.logs, { recursive: true });
94
+ fs.mkdirSync(p.aidLogsDir, { recursive: true });
89
95
  fs.mkdirSync(p.agentsDir, { recursive: true });
90
96
  fs.mkdirSync(p.sessionsDir, { recursive: true });
91
97
  fs.mkdirSync(p.instanceDir, { recursive: true });
92
98
  fs.mkdirSync(p.outboxDir, { recursive: true });
93
- fs.mkdirSync(p.kitsDir, { recursive: true });
99
+ fs.mkdirSync(p.eckDir, { recursive: true });
100
+ fs.mkdirSync(eckDebugDir(), { recursive: true });
94
101
  }
95
- /**
96
- * 首次启动或升级时,把包内 kits/ 复制到 EVOLCLAW_HOME/kits/。
97
- * 策略:如果目标 kits/ 为空或包版本更新,整体覆盖。
98
- */
99
- export function syncKitsFromPackage() {
100
- const p = resolvePaths();
101
- const srcKits = path.join(getPackageRoot(), 'kits');
102
- if (!fs.existsSync(srcKits))
103
- return;
104
- const destKits = p.kitsDir;
105
- // 包内自用场景:EVOLCLAW_HOME 等于包根(开发仓 / 用户家目录恰好是安装目录),
106
- // src === dest 会让 cpSync 抛 ERR_FS_CP_EINVAL。直接跳过同步。
107
- if (path.resolve(srcKits) === path.resolve(destKits))
108
- return;
109
- // .kits-version 文件跟踪已安装的版本
110
- const versionFile = path.join(destKits, '.kits-version');
111
- const pkgJsonPath = path.join(getPackageRoot(), 'package.json');
112
- let pkgVersion = '0.0.0';
113
- try {
114
- pkgVersion = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8')).version || '0.0.0';
115
- }
116
- catch { }
117
- let installedVersion = '';
118
- try {
119
- installedVersion = fs.readFileSync(versionFile, 'utf-8').trim();
120
- }
121
- catch { }
122
- if (installedVersion === pkgVersion)
123
- return;
124
- // 递归复制(覆盖)
125
- fs.cpSync(srcKits, destKits, { recursive: true, force: true });
126
- fs.writeFileSync(versionFile, pkgVersion, 'utf-8');
102
+ // ── kits 路径(始终从包内读取,不复制到 EVOLCLAW_HOME)──
103
+ export function kitsDir() {
104
+ return path.join(getPackageRoot(), 'kits');
105
+ }
106
+ export function kitsRulesDir() {
107
+ return path.join(getPackageRoot(), 'kits', 'rules');
108
+ }
109
+ export function kitsDocsDir() {
110
+ return path.join(getPackageRoot(), 'kits', 'docs');
111
+ }
112
+ export function kitsTemplatesDir() {
113
+ return path.join(getPackageRoot(), 'kits', 'templates');
114
+ }
115
+ // ── 调试输出 ──
116
+ export function eckDebugDir() {
117
+ return path.join(resolveRoot(), 'data', 'eck-debug');
127
118
  }
128
119
  export function getPackageRoot() {
129
120
  // import.meta.dirname is available in Node.js 21.2+ and always returns
@@ -0,0 +1,33 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { resolvePaths } from '../paths.js';
4
+ function ensureDir(dir) {
5
+ fs.mkdirSync(dir, { recursive: true });
6
+ }
7
+ function logPath(aid) {
8
+ const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
9
+ return path.join(resolvePaths().aidLogsDir, `${aidName}.jsonl`);
10
+ }
11
+ export function appendAidLifecycle(event) {
12
+ const filePath = logPath(event.aid);
13
+ ensureDir(path.dirname(filePath));
14
+ fs.appendFileSync(filePath, JSON.stringify(event) + '\n');
15
+ }
16
+ export function readAidLifecycle(aid, lastN = 50) {
17
+ const filePath = logPath(aid);
18
+ try {
19
+ const content = fs.readFileSync(filePath, 'utf-8');
20
+ const lines = content.trim().split('\n').filter(Boolean);
21
+ const events = [];
22
+ for (const line of lines.slice(-lastN)) {
23
+ try {
24
+ events.push(JSON.parse(line));
25
+ }
26
+ catch { }
27
+ }
28
+ return events;
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
@@ -77,3 +77,13 @@ export function ensureDir(dirPath) {
77
77
  fs.mkdirSync(dirPath, { recursive: true });
78
78
  }
79
79
  }
80
+ /**
81
+ * Simple atomic write for plain-text files (ECK runtime files).
82
+ * Uses write-tmp-then-rename (no hot-backup needed for these files).
83
+ */
84
+ export function atomicWriteText(filePath, content) {
85
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
86
+ const tmp = filePath + '.tmp.' + process.pid;
87
+ fs.writeFileSync(tmp, content, 'utf-8');
88
+ fs.renameSync(tmp, filePath);
89
+ }
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- import { execFileSync, execFile, spawn } from 'child_process';
3
+ import { execFileSync, execFile, spawn, spawnSync } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
6
  const execFileAsync = promisify(execFile);
@@ -33,7 +33,7 @@ export function isProcessRunning(pid) {
33
33
  export function killProcess(pid, force = false) {
34
34
  if (isWindows && force) {
35
35
  try {
36
- execFileSync('taskkill', ['/PID', String(pid), '/F']);
36
+ spawnSync('taskkill', ['/PID', String(pid), '/F'], { windowsHide: true });
37
37
  }
38
38
  catch { }
39
39
  }
@@ -51,7 +51,8 @@ export function killProcess(pid, force = false) {
51
51
  export function findProcesses(pattern) {
52
52
  try {
53
53
  if (isWindows) {
54
- const output = execFileSync('wmic', ['process', 'where', `CommandLine like '%${pattern}%'`, 'get', 'ProcessId'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
54
+ const result = spawnSync('wmic', ['process', 'where', `CommandLine like '%${pattern}%'`, 'get', 'ProcessId'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
55
+ const output = result.stdout || '';
55
56
  return output.split('\n')
56
57
  .map(line => parseInt(line.trim(), 10))
57
58
  .filter(pid => !isNaN(pid) && pid !== process.pid);
@@ -68,8 +69,8 @@ export function findProcesses(pattern) {
68
69
  export function getProcessInfo(pid) {
69
70
  try {
70
71
  if (isWindows) {
71
- // Use wmic on Windows
72
- const output = execFileSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'WorkingSetSize,CreationDate'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
72
+ const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'WorkingSetSize,CreationDate'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
73
+ const output = result.stdout || '';
73
74
  const lines = output.trim().split('\n').filter(l => l.trim());
74
75
  if (lines.length >= 2) {
75
76
  const parts = lines[1].trim().split(/\s+/);
@@ -109,19 +110,27 @@ function formatUptime(totalSeconds) {
109
110
  /**
110
111
  * Cross-platform command existence check.
111
112
  */
113
+ const _commandExistsCache = new Map();
112
114
  export function commandExists(cmd) {
115
+ const cached = _commandExistsCache.get(cmd);
116
+ if (cached !== undefined)
117
+ return cached;
118
+ let exists = false;
113
119
  try {
114
120
  if (isWindows) {
115
- execFileSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe' });
121
+ const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
122
+ exists = r.status === 0;
116
123
  }
117
124
  else {
118
125
  execFileSync('which', [cmd], { encoding: 'utf-8', stdio: 'pipe' });
126
+ exists = true;
119
127
  }
120
- return true;
121
128
  }
122
129
  catch {
123
- return false;
130
+ exists = false;
124
131
  }
132
+ _commandExistsCache.set(cmd, exists);
133
+ return exists;
125
134
  }
126
135
  /**
127
136
  * Cross-platform live log tailing (replaces tail -f).
@@ -73,7 +73,14 @@ let _rules = [];
73
73
  let _lastMtime = 0;
74
74
  function getDictPath() {
75
75
  if (!_dictPath) {
76
- _dictPath = path.join(resolvePaths().dataDir, 'error-dict.json');
76
+ const userDict = path.join(resolvePaths().dataDir, 'error-dict.json');
77
+ if (fs.existsSync(userDict)) {
78
+ _dictPath = userDict;
79
+ }
80
+ else {
81
+ // Bundled default: src/utils/ → src/data/ (dev) or dist/utils/ → dist/data/ (prod)
82
+ _dictPath = path.resolve(import.meta.dirname, '..', 'data', 'error-dict.json');
83
+ }
77
84
  }
78
85
  return _dictPath;
79
86
  }
@@ -199,7 +206,8 @@ export function classifyError(error) {
199
206
  }
200
207
  // 内置兜底规则(结构性、稳定的错误模式)
201
208
  if (msg.includes('context_length_exceeded') || msg.includes('context_compact_failed')
202
- || msg.includes('context limit')) {
209
+ || msg.includes('context limit') || msg.includes('input is too long')
210
+ || msg.includes('上下文过长')) {
203
211
  return ErrorType.CONTEXT_TOO_LONG;
204
212
  }
205
213
  if (msg.includes('401') || msg.includes('authentication_error')) {
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import fs from 'fs';
12
12
  import path from 'path';
13
- import { execFileSync } from 'child_process';
13
+ import { spawnSync } from 'child_process';
14
14
  import { resolvePaths } from '../paths.js';
15
15
  import { isProcessRunning, killProcess, isWindows, findProcesses } from './cross-platform.js';
16
16
  import { getProcessStartTime, startTimeMatches } from './process-introspect.js';
@@ -389,7 +389,8 @@ export function killOrphans(orphans) {
389
389
  function readCmdline(pid) {
390
390
  if (isWindows) {
391
391
  try {
392
- const out = execFileSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/value'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
392
+ const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/value'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
393
+ const out = result.stdout || '';
393
394
  const m = out.match(/CommandLine=([^\r\n]+)/);
394
395
  return m ? m[1].trim() : '';
395
396
  }
@@ -401,13 +402,13 @@ function readCmdline(pid) {
401
402
  return fs.readFileSync(`/proc/${pid}/cmdline`, 'utf-8').replace(/\0/g, ' ').trim();
402
403
  }
403
404
  catch {
404
- // macOS / 权限不足
405
405
  try {
406
- return execFileSync('ps', ['-p', String(pid), '-o', 'args='], {
406
+ const result = spawnSync('ps', ['-p', String(pid), '-o', 'args='], {
407
407
  encoding: 'utf-8',
408
408
  timeout: 3000,
409
409
  stdio: ['pipe', 'pipe', 'pipe'],
410
- }).trim();
410
+ });
411
+ return result.stdout?.trim() || '';
411
412
  }
412
413
  catch {
413
414
  return '';
@@ -207,7 +207,8 @@ export class LogWriter {
207
207
  continue;
208
208
  const full = path.join(logDir, name);
209
209
  try {
210
- if (fs.statSync(full).mtimeMs < cutoff)
210
+ const st = fs.lstatSync(full);
211
+ if (st.isSymbolicLink() || st.mtimeMs < cutoff)
211
212
  fs.unlinkSync(full);
212
213
  }
213
214
  catch { }
@@ -22,6 +22,10 @@ const messageWriter = config.messageLog
22
22
  const eventWriter = config.eventLog
23
23
  ? new LogWriter({ baseName: 'events', logDir: LOG_DIR, rotation: 'hourly', retention: { hours: 12 } })
24
24
  : null;
25
+ // 渠道入站日志:记录从渠道收到的原始消息
26
+ const channelInWriter = new LogWriter({ baseName: 'channel-in', logDir: LOG_DIR, rotation: 'hourly', retention: { hours: 12 } });
27
+ // 渠道出站日志:记录发往渠道的所有消息
28
+ const channelOutWriter = new LogWriter({ baseName: 'channel-out', logDir: LOG_DIR, rotation: 'hourly', retention: { hours: 12 } });
25
29
  function shouldLog(level) {
26
30
  return (LEVELS[level] ?? 1) >= (LEVELS[currentLevel] ?? 1);
27
31
  }
@@ -63,5 +67,11 @@ export const logger = {
63
67
  if (!eventWriter)
64
68
  return;
65
69
  eventWriter.write(JSON.stringify({ ts: localTimestamp(), ...data }));
70
+ },
71
+ channelIn: (data) => {
72
+ channelInWriter.write(JSON.stringify({ ts: localTimestamp(), ...data }));
73
+ },
74
+ channelOut: (data) => {
75
+ channelOutWriter.write(JSON.stringify({ ts: localTimestamp(), ...data }));
66
76
  }
67
77
  };
@@ -83,12 +83,12 @@ export function getLocalVersion() {
83
83
  return pkg.version;
84
84
  }
85
85
  /**
86
- * 查询 npm registry 上 evolclaw 的最新版本。
86
+ * 查询 npm registry 上指定包的最新版本。
87
87
  * 超时 15 秒,失败返回 null。
88
88
  */
89
- export function checkLatestVersion() {
89
+ export function checkLatestVersion(pkg = 'evolclaw') {
90
90
  return new Promise((resolve) => {
91
- execFile('npm', ['view', 'evolclaw', 'version'], { timeout: 15000 }, (err, stdout) => {
91
+ execFile('npm', ['view', pkg, 'version'], { timeout: 15000 }, (err, stdout) => {
92
92
  if (err) {
93
93
  resolve(null);
94
94
  return;
@@ -129,3 +129,35 @@ export async function tryUpgrade() {
129
129
  }
130
130
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
131
131
  }
132
+ /**
133
+ * AUN SDK 升级流程:检查 → 比较 → 安装
134
+ * 仅在 SDK 已安装时检查升级,未安装则跳过。
135
+ */
136
+ export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
137
+ if (isLinkedInstall()) {
138
+ return { status: 'skipped' };
139
+ }
140
+ const installed = resolveAunCoreSdkPkg();
141
+ if (!installed) {
142
+ return { status: 'skipped' }; // SDK not installed, skip
143
+ }
144
+ const localVer = installed.version;
145
+ const remoteVer = await checkLatestVersion(AUN_CORE_SDK_PKG);
146
+ if (!remoteVer) {
147
+ return { status: 'skipped', error: 'Failed to check remote version' };
148
+ }
149
+ if (compareVersions(localVer, remoteVer) >= 0) {
150
+ return { status: 'no-update', from: localVer };
151
+ }
152
+ let lastError;
153
+ for (let attempt = 0; attempt < 2; attempt++) {
154
+ try {
155
+ await npmInstallGlobal(`${AUN_CORE_SDK_PKG}@latest`);
156
+ return { status: 'upgraded', from: localVer, to: remoteVer };
157
+ }
158
+ catch (e) {
159
+ lastError = e.stderr || e.message || String(e);
160
+ }
161
+ }
162
+ return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
163
+ }
@@ -7,7 +7,7 @@
7
7
  * killing the wrong process).
8
8
  */
9
9
  import fs from 'fs';
10
- import { execFileSync } from 'child_process';
10
+ import { spawnSync } from 'child_process';
11
11
  import { isWindows } from './cross-platform.js';
12
12
  const isMacOS = process.platform === 'darwin';
13
13
  /** 容差:2 秒(覆盖 macOS 秒级精度 + 时钟漂移) */
@@ -39,8 +39,6 @@ export function startTimeMatches(recorded, actual) {
39
39
  }
40
40
  // ── Linux ──
41
41
  function getStartTimeLinux(pid) {
42
- // /proc/<pid>/stat 第 22 字段:starttime(jiffies since boot)
43
- // 注意 comm 字段(第 2 字段)含括号,可能包含空格,从最后一个 ')' 之后切
44
42
  let stat;
45
43
  try {
46
44
  stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
@@ -50,7 +48,6 @@ function getStartTimeLinux(pid) {
50
48
  }
51
49
  const tail = stat.slice(stat.lastIndexOf(')') + 2);
52
50
  const fields = tail.split(' ');
53
- // tail 从第 3 字段(state)开始,第 22 字段索引为 22 - 3 = 19
54
51
  const starttimeJiffies = parseInt(fields[19], 10);
55
52
  if (isNaN(starttimeJiffies))
56
53
  return null;
@@ -63,62 +60,44 @@ function getStartTimeLinux(pid) {
63
60
  }
64
61
  if (isNaN(uptimeSec))
65
62
  return null;
66
- // CLK_TCK 在绝大多数 Linux 系统是 100
67
63
  const clkTck = 100;
68
64
  const bootTimeMs = Date.now() - uptimeSec * 1000;
69
65
  return bootTimeMs + (starttimeJiffies / clkTck) * 1000;
70
66
  }
71
67
  // ── macOS ──
72
68
  function getStartTimeMacOS(pid) {
73
- let out;
74
- try {
75
- out = execFileSync('ps', ['-p', String(pid), '-o', 'lstart='], {
76
- encoding: 'utf-8',
77
- timeout: 5000,
78
- stdio: ['pipe', 'pipe', 'pipe'],
79
- }).trim();
80
- }
81
- catch {
82
- return null;
83
- }
69
+ const result = spawnSync('ps', ['-p', String(pid), '-o', 'lstart='], {
70
+ encoding: 'utf-8',
71
+ timeout: 5000,
72
+ stdio: ['pipe', 'pipe', 'pipe'],
73
+ });
74
+ const out = result.stdout?.trim();
84
75
  if (!out)
85
76
  return null;
86
- // 输出格式:"Fri May 16 08:00:00 2026"
87
77
  const t = Date.parse(out);
88
78
  return isNaN(t) ? null : t;
89
79
  }
90
80
  // ── Windows ──
91
81
  function getStartTimeWindows(pid) {
92
- // 优先 PowerShell Get-CimInstance(现代)
93
82
  const fromPwsh = winPowerShellCreationDate(pid);
94
83
  if (fromPwsh !== null)
95
84
  return fromPwsh;
96
- // 降级 wmic
97
85
  return winWmicCreationDate(pid);
98
86
  }
99
87
  function winPowerShellCreationDate(pid) {
100
- let out;
101
- try {
102
- out = execFileSync('powershell', [
103
- '-NoProfile',
104
- '-Command',
105
- `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate`,
106
- ], { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
107
- }
108
- catch {
88
+ const result = spawnSync('powershell', [
89
+ '-NoProfile',
90
+ '-Command',
91
+ `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate`,
92
+ ], { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
93
+ const out = result.stdout?.trim();
94
+ if (!out)
109
95
  return null;
110
- }
111
96
  return parseCimDate(out);
112
97
  }
113
98
  function winWmicCreationDate(pid) {
114
- let out;
115
- try {
116
- out = execFileSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CreationDate', '/value'], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
117
- }
118
- catch {
119
- return null;
120
- }
121
- // wmic 输出 "CreationDate=20260516080000.000000+480"
99
+ const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CreationDate', '/value'], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
100
+ const out = result.stdout || '';
122
101
  const m = out.match(/CreationDate=([^\r\n]+)/);
123
102
  if (!m)
124
103
  return null;
@@ -138,7 +117,6 @@ export function parseCimDate(s) {
138
117
  const tzMin = parseInt(tz, 10);
139
118
  if (isNaN(tzMin))
140
119
  return null;
141
- // CIM 的时间是"本地时区时间",要换成 UTC:UTC = local - offset
142
120
  const localUtcMs = Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec, Math.floor(+us / 1000));
143
121
  return localUtcMs - tzMin * 60 * 1000;
144
122
  }
package/dist/watch-msg.js CHANGED
@@ -11,6 +11,7 @@ const CYAN = isTTY ? '\x1b[36m' : '';
11
11
  const GREEN = isTTY ? '\x1b[32m' : '';
12
12
  const BLUE = isTTY ? '\x1b[34m' : '';
13
13
  const ORANGE = isTTY ? '\x1b[38;5;208m' : '';
14
+ const BG_SEL = isTTY ? '\x1b[48;5;236m' : ''; // dark gray background for selected row
14
15
  // ==================== Helpers ====================
15
16
  function visualWidth(s) {
16
17
  const stripped = s.replace(/\x1b\[[0-9;]*m/g, '');
@@ -56,6 +57,14 @@ function formatTime(ts) {
56
57
  const d = new Date(ts);
57
58
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
58
59
  }
60
+ function formatDateTime(ts) {
61
+ const d = new Date(ts);
62
+ const mo = String(d.getMonth() + 1).padStart(2, '0');
63
+ const dd = String(d.getDate()).padStart(2, '0');
64
+ const hh = String(d.getHours()).padStart(2, '0');
65
+ const mm = String(d.getMinutes()).padStart(2, '0');
66
+ return `${mo}-${dd} ${hh}:${mm}`;
67
+ }
59
68
  function shortAid(aid) {
60
69
  return aid.split('.')[0];
61
70
  }
@@ -169,10 +178,12 @@ function renderScopePanel(state, width, height) {
169
178
  const a = state.localAids[i];
170
179
  const sel = isActive && i === state.scopeIndex;
171
180
  const chosen = state.selectedLocalAid === a.aid;
172
- const marker = sel ? `${CYAN}${BOLD}▸ ` : (chosen ? `${CYAN} ` : ' ');
181
+ const bg = sel ? BG_SEL : '';
182
+ const marker = sel ? `${bg}${CYAN}${BOLD}▸ ` : (chosen ? `${CYAN} ` : ' ');
173
183
  const name = truncate(shortAid(a.aid), width - 4);
174
184
  lines.push(padRight(`${marker}${name}${RST}`, width));
175
- const stats = ` ${DIM}↓${a.totalIn} ↑${a.totalOut} peers:${a.peerCount}${RST}`;
185
+ const statsBg = sel ? BG_SEL : '';
186
+ const stats = `${statsBg} ${DIM}↓${a.totalIn} ↑${a.totalOut} peers:${a.peerCount}${RST}`;
176
187
  lines.push(padRight(stats, width));
177
188
  if (lines.length < height)
178
189
  lines.push(padRight('', width));
@@ -195,19 +206,22 @@ function renderStatsPanel(state, width, height) {
195
206
  const now = Date.now();
196
207
  // "All" item at index 0
197
208
  const allSel = isActive && state.statsIndex === 0;
198
- const allMarker = allSel ? `${CYAN}${BOLD}▸ ` : ' ';
209
+ const allBg = allSel ? BG_SEL : '';
210
+ const allMarker = allSel ? `${allBg}${CYAN}${BOLD}▸ ` : ' ';
199
211
  lines.push(padRight(`${allMarker}All (${state.peers.length} peers)${RST}`, width));
200
212
  if (lines.length < height)
201
213
  lines.push(padRight('', width));
202
214
  for (let i = 0; i < state.peers.length && lines.length < height; i++) {
203
215
  const p = state.peers[i];
204
216
  const sel = isActive && state.statsIndex === i + 1;
205
- const marker = sel ? `${CYAN}${BOLD}▸ ` : ' ';
217
+ const bg = sel ? BG_SEL : '';
218
+ const marker = sel ? `${bg}${CYAN}${BOLD}▸ ` : ' ';
206
219
  const displayName = p.peerName || shortAid(p.peerId);
207
220
  const name = truncate(displayName, width - 4);
208
221
  lines.push(padRight(`${marker}${name}${RST}`, width));
222
+ const detailBg = sel ? BG_SEL : '';
209
223
  const ago = p.lastAt ? formatTimeAgo(now - p.lastAt) : '-';
210
- const detail = ` ${DIM}↓${p.inbound} ↑${p.outbound} ${ago}${RST}`;
224
+ const detail = `${detailBg} ${DIM}↓${p.inbound} ↑${p.outbound} ${ago}${RST}`;
211
225
  lines.push(padRight(detail, width));
212
226
  if (lines.length < height)
213
227
  lines.push(padRight('', width));
@@ -231,10 +245,11 @@ function renderMessagesPanel(state, width, height) {
231
245
  const msgWidth = width - 3;
232
246
  for (let i = startIdx; i < endIdx; i++) {
233
247
  const m = msgs[i];
234
- const time = formatTime(m.ts);
248
+ const time = formatDateTime(m.ts);
235
249
  const dir = m.dir === 'in' ? `${GREEN}↓${RST}` : `${BLUE}↑${RST}`;
236
- const peer = m.dir === 'in' ? shortAid(m.from) : shortAid(m.to);
237
- const header = `${DIM}${time}${RST} ${dir} ${ORANGE}${peer}${RST}`;
250
+ const from = shortAid(m.from);
251
+ const to = shortAid(m.to);
252
+ const header = `${DIM}${time}${RST} ${dir} ${ORANGE}${from}${RST}${DIM}→${RST}${GREEN}${to}${RST}`;
238
253
  const headerLine = padRight(header, msgWidth);
239
254
  const sbIdx = lines.length - 1;
240
255
  lines.push(`${headerLine} ${scrollbar[sbIdx] || ' '}`);
@@ -255,9 +270,9 @@ function renderMessagesPanel(state, width, height) {
255
270
  function renderFrame(state) {
256
271
  const cols = process.stdout.columns || 120;
257
272
  const rows = (process.stdout.rows || 40) - 3;
258
- const leftW = Math.max(24, Math.floor(cols * 0.28));
259
- const midW = Math.max(28, Math.floor(cols * 0.30));
260
- const rightW = Math.max(30, cols - leftW - midW - 4);
273
+ const leftW = Math.max(20, Math.floor(cols * 0.20));
274
+ const midW = Math.max(24, Math.floor(cols * 0.22));
275
+ const rightW = Math.max(40, cols - leftW - midW - 4);
261
276
  const bodyHeight = rows - 2;
262
277
  const leftLines = renderScopePanel(state, leftW, bodyHeight);
263
278
  const midLines = renderStatsPanel(state, midW, bodyHeight);
@@ -19,6 +19,8 @@ EvolClaw 是一个开源的 AI Agent 网关,将 Claude/Codex 等 AI 后端连
19
19
 
20
20
  ## 安装步骤
21
21
 
22
+ > **⚠️ 给执行安装的 AI Agent**:本文档中所有破坏性命令(`evolclaw stop` / `evolclaw restart` / `rm -rf` / `evolclaw aid delete` 等)**必须先 `AskUserQuestion` 等用户明确回复后才能执行**,不要因为已经发出确认提示就默认 yes 自动跑。用户在打字的几秒内决定 "等等先别停",比事后回滚要省事得多。
23
+
22
24
  ### 步骤 1:检查环境
23
25
 
24
26
  ```bash
@@ -169,23 +171,33 @@ AUN 通道由 `agent.aid` 隐式驱动,不需要单独配 channel——`evolcl
169
171
  cat $EVOLCLAW_HOME/agents/<aid>/config.json
170
172
  ```
171
173
 
172
- 检查:
174
+ 检查关键字段:
173
175
 
174
176
  - `aid` — 你的 AID
175
177
  - `owners` — Owner AID 数组(可为空,留给自动绑定)
176
178
  - `active_baseagent` — `claude` / `codex` / `gemini`
177
179
  - `projects.defaultPath` — 项目路径(目录需存在)
178
- - `channels` — 数组,初始可为空(AUN 隐式上线)
180
+ - `channels` — 数组,初始为空(AUN 隐式上线,无需在此配置)
179
181
  - `initialized` — 应为 `false`(首次连接成功后由系统更新为 `true`)
180
182
 
183
+ > 注:config.json 还包含 `$schema_version`、`enabled`、`baseagents`、`chatmode`、`dispatch` 等字段,由 `agent new` 自动填入,无需手动检查。
184
+
181
185
  如发现缺失或异常,向用户说明并提供修复方案。
182
186
 
183
187
  ### 步骤 9:启动服务
184
188
 
189
+ 首次安装(服务未运行):
190
+
185
191
  ```bash
186
192
  evolclaw start
187
193
  ```
188
194
 
195
+ 若服务已在运行(升级/重装场景,步骤 3 已停止旧进程):
196
+
197
+ ```bash
198
+ evolclaw restart
199
+ ```
200
+
189
201
  ### 步骤 10:验证运行状态
190
202
 
191
203
  ```bash
@@ -0,0 +1,20 @@
1
+ # 文档查阅指南
2
+
3
+ ## 查阅流程
4
+
5
+ 1. 先看 `$KITS_RULES`(自动加载的 8 个规则文件)了解机制骨架
6
+ 2. 需要详细信息时,按 `INDEX.md` 找到对应文档路径
7
+ 3. Read 对应文档
8
+
9
+ ## 路径解析
10
+
11
+ 文档中用 `$名称` 引用路径。解析步骤:
12
+ 1. 查 `$KITS_RULES/01-entry.md` 的路径体系速查表
13
+ 2. 如需完整定义,Read `$KITS_DOCS/path-registry.md`
14
+ 3. 如需运行时实际值,Read `$ECK/path-registry.md`
15
+
16
+ ## 不要做的事
17
+
18
+ - 不要一次性加载所有文档——按需逐个 Read
19
+ - 不要猜测路径——查注册表
20
+ - 不要在当前会话输出对外消息——用 CLI