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.
- package/README.md +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +47 -12
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +42 -1
- package/dist/channels/aun.js +427 -146
- package/dist/channels/dingtalk.js +3 -1
- package/dist/channels/feishu.js +128 -7
- package/dist/channels/qqbot.js +3 -1
- package/dist/channels/wechat.js +4 -1
- package/dist/channels/wecom.js +3 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +418 -40
- package/dist/cli/init.js +3 -4
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +666 -0
- package/dist/config-store.js +82 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +127 -99
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +93 -48
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +11 -2
- package/dist/core/message/message-log.js +8 -1
- package/dist/core/message/message-processor.js +194 -127
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +103 -65
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +7 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +186 -19
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +27 -15
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/utils/stats.js +216 -2
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +73 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /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-
|
|
39
|
+
// ── 新结构(evolclaw-directory-design.md)────────────────
|
|
40
40
|
defaultsConfig: path.join(root, 'agents', 'defaults.json'),
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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.
|
|
99
|
+
fs.mkdirSync(p.eckDir, { recursive: true });
|
|
100
|
+
fs.mkdirSync(eckDebugDir(), { recursive: true });
|
|
94
101
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
72
|
-
const output =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
257
|
+
return `${warnPrefix}输入过长,请精简提问或使用 /compact 压缩上下文`;
|
|
248
258
|
case 'rapid_refill_breaker':
|
|
249
|
-
return
|
|
259
|
+
return `${warnPrefix}API 限流中,请稍后重试`;
|
|
250
260
|
case 'context_compact_failed':
|
|
251
|
-
return
|
|
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
|
|
284
|
+
return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
|
|
273
285
|
}
|
|
274
286
|
if (msg.includes('401') || msg.includes('authentication_error')) {
|
|
275
|
-
return
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
})
|
|
410
|
+
});
|
|
411
|
+
return result.stdout?.trim() || '';
|
|
411
412
|
}
|
|
412
413
|
catch {
|
|
413
414
|
return '';
|
package/dist/utils/log-writer.js
CHANGED
|
@@ -207,7 +207,8 @@ export class LogWriter {
|
|
|
207
207
|
continue;
|
|
208
208
|
const full = path.join(logDir, name);
|
|
209
209
|
try {
|
|
210
|
-
|
|
210
|
+
const st = fs.lstatSync(full);
|
|
211
|
+
if (st.isSymbolicLink() || st.mtimeMs < cutoff)
|
|
211
212
|
fs.unlinkSync(full);
|
|
212
213
|
}
|
|
213
214
|
catch { }
|
package/dist/utils/logger.js
CHANGED
|
@@ -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
|
};
|
package/dist/utils/npm-ops.js
CHANGED
|
@@ -83,12 +83,12 @@ export function getLocalVersion() {
|
|
|
83
83
|
return pkg.version;
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* 查询 npm registry
|
|
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',
|
|
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 {
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
}
|