evolclaw 3.0.0 → 3.1.1

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 (104) 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 +47 -12
  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 +42 -1
  13. package/dist/channels/aun.js +427 -146
  14. package/dist/channels/dingtalk.js +3 -1
  15. package/dist/channels/feishu.js +128 -7
  16. package/dist/channels/qqbot.js +3 -1
  17. package/dist/channels/wechat.js +4 -1
  18. package/dist/channels/wecom.js +3 -1
  19. package/dist/cli/bench.js +1219 -0
  20. package/dist/cli/index.js +418 -40
  21. package/dist/cli/init.js +3 -4
  22. package/dist/cli/link-rules.js +245 -0
  23. package/dist/cli/net-check.js +640 -0
  24. package/dist/cli/watch-msg.js +666 -0
  25. package/dist/config-store.js +82 -5
  26. package/dist/core/channel-loader.js +23 -10
  27. package/dist/core/command-handler.js +127 -99
  28. package/dist/core/evolagent.js +5 -10
  29. package/dist/core/message/im-renderer.js +93 -48
  30. package/dist/core/message/items-formatter.js +11 -4
  31. package/dist/core/message/message-bridge.js +11 -2
  32. package/dist/core/message/message-log.js +8 -1
  33. package/dist/core/message/message-processor.js +194 -127
  34. package/dist/core/message/message-queue.js +10 -3
  35. package/dist/core/permission.js +95 -3
  36. package/dist/core/relation/peer-identity.js +161 -0
  37. package/dist/core/session/session-manager.js +103 -65
  38. package/dist/core/trigger/manager.js +16 -0
  39. package/dist/core/trigger/parser.js +110 -0
  40. package/dist/core/trigger/scheduler.js +7 -1
  41. package/dist/data/error-dict.json +118 -0
  42. package/dist/eck/baseagent-caps.js +18 -0
  43. package/dist/eck/detect.js +47 -0
  44. package/dist/eck/init.js +77 -0
  45. package/dist/eck/rules-loader.js +28 -0
  46. package/dist/index.js +186 -19
  47. package/dist/net-check.js +640 -0
  48. package/dist/paths.js +31 -40
  49. package/dist/utils/aid-lifecycle-log.js +33 -0
  50. package/dist/utils/atomic-write.js +10 -0
  51. package/dist/utils/cross-platform.js +17 -8
  52. package/dist/utils/error-utils.js +27 -15
  53. package/dist/utils/instance-registry.js +6 -5
  54. package/dist/utils/log-writer.js +2 -1
  55. package/dist/utils/logger.js +10 -0
  56. package/dist/utils/npm-ops.js +35 -3
  57. package/dist/utils/process-introspect.js +16 -38
  58. package/dist/utils/stats.js +216 -2
  59. package/dist/watch-msg.js +26 -11
  60. package/evolclaw-install-aun.md +14 -2
  61. package/kits/docs/GUIDE.md +20 -0
  62. package/kits/docs/INDEX.md +52 -0
  63. package/kits/docs/aun/CHEATSHEET.md +17 -0
  64. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  65. package/kits/docs/channels/feishu.md +27 -0
  66. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  67. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  68. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  69. package/kits/docs/eck_templates/runtime.template.md +19 -0
  70. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  71. package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
  72. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  73. package/kits/docs/identity/PATH_OPS.md +16 -0
  74. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  75. package/kits/docs/path-registry.md +43 -0
  76. package/kits/eck_manifest.json +95 -0
  77. package/kits/rules/01-overview.md +120 -0
  78. package/kits/rules/02-navigation.md +75 -0
  79. package/kits/rules/03-identity.md +34 -0
  80. package/kits/rules/04-relation.md +49 -0
  81. package/kits/rules/05-venue.md +45 -0
  82. package/kits/rules/06-channel.md +73 -0
  83. package/kits/templates/system-fragments/baseagent.md +2 -0
  84. package/kits/templates/system-fragments/channel.md +10 -0
  85. package/kits/templates/system-fragments/identity.md +12 -0
  86. package/kits/templates/system-fragments/relation.md +9 -0
  87. package/kits/templates/system-fragments/runtime.md +19 -0
  88. package/kits/templates/system-fragments/venue.md +5 -0
  89. package/package.json +7 -5
  90. package/dist/agents/templates.js +0 -122
  91. package/dist/data/prompts.md +0 -137
  92. package/kits/aun/meta.md +0 -25
  93. package/kits/aun/role.md +0 -25
  94. package/kits/templates/group.md +0 -20
  95. package/kits/templates/private.md +0 -9
  96. package/kits/templates/system-fragments/personal-context.md +0 -3
  97. package/kits/templates/system-fragments/self-intro.md +0 -5
  98. package/kits/templates/system-fragments/speaker-intro.md +0 -5
  99. package/kits/templates/system-fragments/venue-intro.md +0 -5
  100. /package/kits/{channels → docs/channels}/aun.md +0 -0
  101. /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
  102. /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
  103. /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
  104. /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')) {
@@ -237,27 +245,29 @@ export function isRetryableError(error) {
237
245
  return true;
238
246
  return false;
239
247
  }
240
- export function getErrorMessage(error, terminalReason) {
248
+ export function getErrorMessage(error, terminalReason, includeEmoji = true) {
241
249
  // terminalReason 提供更精确的错误提示(SDK 0.2.100+)
242
250
  if (terminalReason) {
251
+ const prefix = includeEmoji ? '❌ ' : '';
252
+ const warnPrefix = includeEmoji ? '⚠️ ' : '';
243
253
  switch (terminalReason) {
244
254
  case 'max_turns':
245
- return '❌ 任务达到最大轮次限制,请简化需求或分步执行';
255
+ return `${prefix}任务达到最大轮次限制,请简化需求或分步执行`;
246
256
  case 'prompt_too_long':
247
- return '⚠️ 输入过长,请精简提问或使用 /compact 压缩上下文';
257
+ return `${warnPrefix}输入过长,请精简提问或使用 /compact 压缩上下文`;
248
258
  case 'rapid_refill_breaker':
249
- return '⚠️ API 限流中,请稍后重试';
259
+ return `${warnPrefix}API 限流中,请稍后重试`;
250
260
  case 'context_compact_failed':
251
- return '⚠️ 上下文过长,自动压缩失败,请手动输入 /compact 重试';
261
+ return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
252
262
  case 'model_error':
253
- return '❌ 模型服务异常,请稍后重试';
263
+ return `${prefix}模型服务异常,请稍后重试`;
254
264
  case 'tool_error':
255
- return '❌ 工具执行失败,请检查操作或重试';
265
+ return `${prefix}工具执行失败,请检查操作或重试`;
256
266
  case 'permission_denied':
257
- return '❌ 权限被拒绝,操作已取消';
267
+ return `${prefix}权限被拒绝,操作已取消`;
258
268
  case 'aborted_streaming':
259
269
  case 'aborted_tools':
260
- return '❌ 任务已中断';
270
+ return `${prefix}任务已中断`;
261
271
  }
262
272
  }
263
273
  // 回退到原有的错误消息匹配逻辑
@@ -267,15 +277,17 @@ export function getErrorMessage(error, terminalReason) {
267
277
  if (rule?.message)
268
278
  return rule.message;
269
279
  // 内置兜底规则(结构性错误)
280
+ const warnPrefix = includeEmoji ? '⚠️ ' : '';
281
+ const errPrefix = includeEmoji ? '❌ ' : '';
270
282
  if (msg.includes('CONTEXT_COMPACT_FAILED') || msg.includes('context_length_exceeded')
271
283
  || msg.includes('Context limit')) {
272
- return '⚠️ 上下文过长,自动压缩失败,请手动输入 /compact 重试';
284
+ return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
273
285
  }
274
286
  if (msg.includes('401') || msg.includes('authentication_error')) {
275
- return '❌ API Key 无效,请检查密钥配置。使用 /status 查看当前配置';
287
+ return `${errPrefix}API Key 无效,请检查密钥配置。使用 /status 查看当前配置`;
276
288
  }
277
289
  if (msg.includes('timeout')) {
278
- return '⚠️ 请求超时,请重试';
290
+ return `${warnPrefix}请求超时,请重试`;
279
291
  }
280
- return '❌ 处理消息时出错,请稍后重试';
292
+ return `${errPrefix}处理消息时出错,请稍后重试`;
281
293
  }
@@ -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
  }