evolclaw 2.8.3 → 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.
- package/README.md +21 -12
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +108 -46
- package/dist/agents/codex-runner.js +13 -14
- package/dist/agents/gemini-runner.js +15 -17
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/agents/resolve.js +134 -0
- package/dist/aun/aid/agentmd.js +186 -0
- package/dist/aun/aid/client.js +134 -0
- package/dist/aun/aid/identity.js +159 -0
- package/dist/aun/aid/index.js +3 -0
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/msg/group.js +293 -0
- package/dist/aun/msg/index.js +4 -0
- package/dist/aun/msg/p2p.js +147 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +98 -0
- package/dist/aun/outbox.js +138 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +34 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +35 -0
- package/dist/channels/aun.js +1340 -349
- package/dist/channels/dingtalk.js +59 -5
- package/dist/channels/feishu.js +381 -32
- package/dist/channels/qqbot.js +68 -12
- package/dist/channels/wechat.js +63 -4
- package/dist/channels/wecom.js +59 -5
- package/dist/cli/agent.js +800 -0
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +4513 -0
- package/dist/{utils → cli}/init-channel.js +211 -621
- package/dist/cli/init.js +178 -0
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +645 -0
- package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
- package/dist/core/channel-loader.js +176 -12
- package/dist/core/command-handler.js +883 -848
- package/dist/core/evolagent-registry.js +191 -371
- package/dist/core/evolagent.js +202 -238
- package/dist/core/interaction-router.js +52 -5
- package/dist/core/message/im-renderer.js +486 -0
- package/dist/core/message/items-formatter.js +68 -0
- package/dist/core/message/message-bridge.js +109 -56
- package/dist/core/message/message-log.js +93 -0
- package/dist/core/message/message-processor.js +430 -212
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/permission.js +116 -11
- package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
- package/dist/core/session/session-fs-store.js +230 -0
- package/dist/core/session/session-manager.js +740 -777
- package/dist/core/session/session-mapper.js +87 -0
- package/dist/core/trigger/manager.js +122 -0
- package/dist/core/trigger/parser.js +128 -0
- package/dist/core/trigger/scheduler.js +224 -0
- 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 +560 -283
- package/dist/ipc.js +49 -0
- package/dist/net-check.js +640 -0
- package/dist/paths.js +73 -9
- package/dist/types.js +8 -2
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +89 -0
- package/dist/utils/channel-helpers.js +46 -0
- package/dist/utils/cross-platform.js +17 -26
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +434 -0
- package/dist/utils/log-writer.js +217 -0
- package/dist/utils/logger.js +34 -77
- package/dist/utils/media-cache.js +23 -0
- package/dist/utils/npm-ops.js +163 -0
- package/dist/utils/process-introspect.js +122 -0
- package/dist/utils/stats.js +192 -0
- package/dist/watch-msg.js +544 -0
- package/evolclaw-install-aun.md +127 -47
- 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/aun.md +25 -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/AGENT_CMD.md +31 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/evolclaw/self-summary.md +29 -0
- package/kits/docs/evolclaw/tools.md +25 -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/identity/identity-tools.md +26 -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 +43 -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 +10 -6
- package/data/evolclaw.sample.json +0 -60
- package/dist/agents/templates.js +0 -122
- package/dist/channels/aun-ops.js +0 -275
- package/dist/cli.js +0 -2178
- package/dist/config.js +0 -591
- package/dist/core/agent-registry.js +0 -450
- package/dist/core/evolagent-schema.js +0 -72
- package/dist/core/message/stream-flusher.js +0 -238
- package/dist/core/message/thought-emitter.js +0 -162
- package/dist/core/reload-hooks.js +0 -87
- package/dist/prompts/templates.js +0 -122
- package/dist/templates/prompts.md +0 -104
- package/dist/templates/skills.md +0 -66
- package/dist/utils/channel-fingerprint.js +0 -59
- package/dist/utils/error-dict.js +0 -63
- package/dist/utils/format.js +0 -32
- package/dist/utils/init.js +0 -645
- package/dist/utils/migrate-project.js +0 -122
- package/dist/utils/reload-hooks.js +0 -87
- package/dist/utils/stats-collector.js +0 -99
- package/dist/utils/upgrade.js +0 -100
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import { resolvePaths } from '../paths.js';
|
|
5
|
-
import { loadConfig, saveConfig } from '../config.js';
|
|
6
|
-
/** 将绝对路径编码为 Claude Code 的目录名格式(/ \ . 替换为 -) */
|
|
7
|
-
function encodePath(p) {
|
|
8
|
-
return p.replace(/[/\\\.]/g, '-');
|
|
9
|
-
}
|
|
10
|
-
/** 查找最新的 ~/.codex/state_*.sqlite */
|
|
11
|
-
function findCodexDb() {
|
|
12
|
-
const codexHome = path.join(os.homedir(), '.codex');
|
|
13
|
-
if (!fs.existsSync(codexHome))
|
|
14
|
-
return null;
|
|
15
|
-
const files = fs.readdirSync(codexHome)
|
|
16
|
-
.filter(f => /^state_\d+\.sqlite$/.test(f))
|
|
17
|
-
.sort((a, b) => {
|
|
18
|
-
const va = parseInt(a.match(/state_(\d+)/)?.[1] || '0');
|
|
19
|
-
const vb = parseInt(b.match(/state_(\d+)/)?.[1] || '0');
|
|
20
|
-
return vb - va;
|
|
21
|
-
});
|
|
22
|
-
return files.length > 0 ? path.join(codexHome, files[0]) : null;
|
|
23
|
-
}
|
|
24
|
-
export async function migrateProject(oldPath, newPath) {
|
|
25
|
-
const result = {
|
|
26
|
-
claudeSessionsMoved: false,
|
|
27
|
-
claudeHistoryUpdated: false,
|
|
28
|
-
codexUpdated: 0,
|
|
29
|
-
evolclawDbUpdated: 0,
|
|
30
|
-
evolclawConfigUpdated: false,
|
|
31
|
-
directoryMoved: false,
|
|
32
|
-
};
|
|
33
|
-
const oldAbs = path.resolve(oldPath);
|
|
34
|
-
const newAbs = path.resolve(newPath);
|
|
35
|
-
if (!fs.existsSync(oldAbs))
|
|
36
|
-
throw new Error(`源目录不存在: ${oldAbs}`);
|
|
37
|
-
if (fs.existsSync(newAbs))
|
|
38
|
-
throw new Error(`目标目录已存在: ${newAbs}`);
|
|
39
|
-
const claudeProjects = path.join(os.homedir(), '.claude', 'projects');
|
|
40
|
-
const oldEncoded = encodePath(oldAbs);
|
|
41
|
-
const newEncoded = encodePath(newAbs);
|
|
42
|
-
// 1. 迁移 ~/.claude/projects/{encoded}/
|
|
43
|
-
const oldClaudeDir = path.join(claudeProjects, oldEncoded);
|
|
44
|
-
const newClaudeDir = path.join(claudeProjects, newEncoded);
|
|
45
|
-
if (fs.existsSync(oldClaudeDir)) {
|
|
46
|
-
fs.renameSync(oldClaudeDir, newClaudeDir);
|
|
47
|
-
result.claudeSessionsMoved = true;
|
|
48
|
-
}
|
|
49
|
-
// 2. .jsonl 内部路径不需要替换 — 它们是历史对话记录,
|
|
50
|
-
// resume 时模型会根据当前 cwd 工作,旧路径只是历史上下文
|
|
51
|
-
// 3. 更新 ~/.claude/history.jsonl
|
|
52
|
-
const historyFile = path.join(os.homedir(), '.claude', 'history.jsonl');
|
|
53
|
-
if (fs.existsSync(historyFile)) {
|
|
54
|
-
const lines = fs.readFileSync(historyFile, 'utf-8').split('\n');
|
|
55
|
-
const updated = lines.map(line => {
|
|
56
|
-
if (!line.trim())
|
|
57
|
-
return line;
|
|
58
|
-
try {
|
|
59
|
-
const obj = JSON.parse(line);
|
|
60
|
-
if (obj.project === oldAbs) {
|
|
61
|
-
obj.project = newAbs;
|
|
62
|
-
return JSON.stringify(obj);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch { /* skip */ }
|
|
66
|
-
return line;
|
|
67
|
-
});
|
|
68
|
-
const newContent = updated.join('\n');
|
|
69
|
-
if (newContent !== fs.readFileSync(historyFile, 'utf-8')) {
|
|
70
|
-
fs.writeFileSync(historyFile, newContent, 'utf-8');
|
|
71
|
-
result.claudeHistoryUpdated = true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// 4. 更新 Codex SQLite threads.cwd
|
|
75
|
-
const codexDbPath = findCodexDb();
|
|
76
|
-
if (codexDbPath) {
|
|
77
|
-
try {
|
|
78
|
-
const { DatabaseSync } = await import('node:sqlite');
|
|
79
|
-
const db = new DatabaseSync(codexDbPath);
|
|
80
|
-
const r = db.prepare('UPDATE threads SET cwd = ? WHERE cwd = ?').run(newAbs, oldAbs);
|
|
81
|
-
result.codexUpdated = r.changes ?? 0;
|
|
82
|
-
db.close();
|
|
83
|
-
}
|
|
84
|
-
catch { /* Codex not installed or DB locked */ }
|
|
85
|
-
}
|
|
86
|
-
// 5. 移动项目目录
|
|
87
|
-
fs.renameSync(oldAbs, newAbs);
|
|
88
|
-
result.directoryMoved = true;
|
|
89
|
-
// 6. 更新 EvolClaw sessions.db
|
|
90
|
-
const p = resolvePaths();
|
|
91
|
-
if (fs.existsSync(p.db)) {
|
|
92
|
-
try {
|
|
93
|
-
const { DatabaseSync } = await import('node:sqlite');
|
|
94
|
-
const db = new DatabaseSync(p.db);
|
|
95
|
-
const r = db.prepare('UPDATE sessions SET project_path = ? WHERE project_path = ?').run(newAbs, oldAbs);
|
|
96
|
-
result.evolclawDbUpdated = r.changes ?? 0;
|
|
97
|
-
db.close();
|
|
98
|
-
}
|
|
99
|
-
catch { /* DB not accessible */ }
|
|
100
|
-
}
|
|
101
|
-
// 7. 更新 evolclaw.json projects.list
|
|
102
|
-
if (fs.existsSync(p.config)) {
|
|
103
|
-
try {
|
|
104
|
-
const config = loadConfig(p.config);
|
|
105
|
-
if (config.projects?.list) {
|
|
106
|
-
let changed = false;
|
|
107
|
-
for (const [k, v] of Object.entries(config.projects.list)) {
|
|
108
|
-
if (v === oldAbs) {
|
|
109
|
-
config.projects.list[k] = newAbs;
|
|
110
|
-
changed = true;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (changed) {
|
|
114
|
-
saveConfig(config, p.config);
|
|
115
|
-
result.evolclawConfigUpdated = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch { /* config not accessible */ }
|
|
120
|
-
}
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reload Hooks
|
|
3
|
-
*
|
|
4
|
-
* Extracted from index.ts main() for testability. Builds the ReloadHooks
|
|
5
|
-
* implementation used by EvolAgentRegistry.reload() to drain/disconnect/start
|
|
6
|
-
* channels during a hot reload.
|
|
7
|
-
*/
|
|
8
|
-
import { logger } from './logger.js';
|
|
9
|
-
export function buildReloadHooks(deps) {
|
|
10
|
-
const { channelLoader, channelInstances, registerChannelInstance, messageQueue } = deps;
|
|
11
|
-
const drainDelayMs = deps.drainDelayMs ?? 500;
|
|
12
|
-
const drainTimeoutMs = deps.drainTimeoutMs ?? 30000;
|
|
13
|
-
return {
|
|
14
|
-
async drainChannel(channelName) {
|
|
15
|
-
logger.info(`[Reload] Draining channel: ${channelName}`);
|
|
16
|
-
if (messageQueue) {
|
|
17
|
-
// Real drain: poll until empty or timeout
|
|
18
|
-
const pollMs = 100;
|
|
19
|
-
const start = Date.now();
|
|
20
|
-
while (messageQueue.isChannelProcessing(channelName)) {
|
|
21
|
-
if (Date.now() - start > drainTimeoutMs) {
|
|
22
|
-
logger.warn(`[Reload] Drain timeout (${drainTimeoutMs}ms) for channel: ${channelName}, proceeding anyway`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
await new Promise(r => setTimeout(r, pollMs));
|
|
26
|
-
}
|
|
27
|
-
logger.info(`[Reload] Drain complete: ${channelName}`);
|
|
28
|
-
}
|
|
29
|
-
else if (drainDelayMs > 0) {
|
|
30
|
-
await new Promise(r => setTimeout(r, drainDelayMs));
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
async disconnectChannel(channelName) {
|
|
34
|
-
const inst = channelInstances.find(i => i.adapter.channelName === channelName);
|
|
35
|
-
if (!inst) {
|
|
36
|
-
logger.warn(`[Reload] Channel ${channelName} not found, skipping disconnect`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
await inst.disconnect();
|
|
41
|
-
const idx = channelInstances.indexOf(inst);
|
|
42
|
-
if (idx >= 0)
|
|
43
|
-
channelInstances.splice(idx, 1);
|
|
44
|
-
logger.info(`[Reload] Disconnected channel: ${channelName}`);
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
logger.error(`[Reload] Failed to disconnect ${channelName}: ${e}`);
|
|
48
|
-
throw e;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
async startChannel(agent, channelName) {
|
|
52
|
-
const channels = agent.config.channels;
|
|
53
|
-
let channelType = null;
|
|
54
|
-
for (const [type, raw] of Object.entries(channels)) {
|
|
55
|
-
const instances = Array.isArray(raw) ? raw : [raw];
|
|
56
|
-
for (const inst of instances) {
|
|
57
|
-
const name = inst.name ?? type;
|
|
58
|
-
if (name === channelName) {
|
|
59
|
-
channelType = type;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (channelType)
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
if (!channelType) {
|
|
67
|
-
const msg = `[Reload] Channel ${channelName} not found in agent ${agent.name} config`;
|
|
68
|
-
logger.error(msg);
|
|
69
|
-
throw new Error(msg);
|
|
70
|
-
}
|
|
71
|
-
const partialConfig = {
|
|
72
|
-
agents: agent.config.agents,
|
|
73
|
-
channels: { [channelType]: channels[channelType] },
|
|
74
|
-
projects: agent.config.projects,
|
|
75
|
-
};
|
|
76
|
-
const newInstances = await channelLoader.createAll(partialConfig);
|
|
77
|
-
const newInst = newInstances.find(i => i.adapter.channelName === channelName);
|
|
78
|
-
if (!newInst) {
|
|
79
|
-
throw new Error(`[Reload] Failed to create instance ${channelName}`);
|
|
80
|
-
}
|
|
81
|
-
registerChannelInstance(newInst);
|
|
82
|
-
await newInst.connect();
|
|
83
|
-
channelInstances.push(newInst);
|
|
84
|
-
logger.info(`[Reload] Started channel: ${channelName}`);
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export class StatsCollector {
|
|
2
|
-
events = [];
|
|
3
|
-
startTime;
|
|
4
|
-
HOUR_MS = 3_600_000;
|
|
5
|
-
constructor(eventBus) {
|
|
6
|
-
this.startTime = Date.now();
|
|
7
|
-
// 订阅相关事件
|
|
8
|
-
eventBus.subscribe('message:received', (event) => {
|
|
9
|
-
const e = event;
|
|
10
|
-
this.recordEvent({ type: 'received', timestamp: e.timestamp || Date.now(), agentName: e.agentName });
|
|
11
|
-
});
|
|
12
|
-
eventBus.subscribe('message:completed', (event) => {
|
|
13
|
-
const e = event;
|
|
14
|
-
this.recordEvent({ type: 'completed', timestamp: e.timestamp || Date.now(), durationMs: e.durationMs, agentName: e.agentName });
|
|
15
|
-
});
|
|
16
|
-
eventBus.subscribe('message:error', (event) => {
|
|
17
|
-
const e = event;
|
|
18
|
-
this.recordEvent({ type: 'error', timestamp: Date.now(), errorType: e.errorType, agentName: e.agentName });
|
|
19
|
-
});
|
|
20
|
-
eventBus.subscribe('message:interrupted', (event) => {
|
|
21
|
-
const e = event;
|
|
22
|
-
this.recordEvent({ type: 'interrupted', timestamp: Date.now(), agentName: e.agentName });
|
|
23
|
-
});
|
|
24
|
-
eventBus.subscribe('tool:result', (event) => {
|
|
25
|
-
const e = event;
|
|
26
|
-
if (e.isError) {
|
|
27
|
-
this.recordEvent({ type: 'tool-error', timestamp: Date.now(), toolName: e.toolName, agentName: e.agentName });
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
recordEvent(record) {
|
|
32
|
-
this.events.push(record);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 获取统计快照。可选 agentName 过滤:未传则全局;传入则只统计该 agent。
|
|
36
|
-
* 自动裁剪 >1h 的事件。
|
|
37
|
-
*/
|
|
38
|
-
getSnapshot(agentName) {
|
|
39
|
-
const now = Date.now();
|
|
40
|
-
const cutoff = now - this.HOUR_MS;
|
|
41
|
-
// 裁剪过期事件
|
|
42
|
-
this.events = this.events.filter(e => e.timestamp >= cutoff);
|
|
43
|
-
// 聚合统计(可按 agent 过滤)
|
|
44
|
-
const filtered = agentName === undefined
|
|
45
|
-
? this.events
|
|
46
|
-
: this.events.filter(e => (e.agentName ?? '[default]') === agentName);
|
|
47
|
-
let received = 0;
|
|
48
|
-
let completed = 0;
|
|
49
|
-
let errors = 0;
|
|
50
|
-
const errorsByType = {};
|
|
51
|
-
let toolErrors = 0;
|
|
52
|
-
const toolErrorsByName = {};
|
|
53
|
-
let interrupts = 0;
|
|
54
|
-
let totalDuration = 0;
|
|
55
|
-
let durationCount = 0;
|
|
56
|
-
for (const event of filtered) {
|
|
57
|
-
switch (event.type) {
|
|
58
|
-
case 'received':
|
|
59
|
-
received++;
|
|
60
|
-
break;
|
|
61
|
-
case 'completed':
|
|
62
|
-
completed++;
|
|
63
|
-
if (event.durationMs !== undefined) {
|
|
64
|
-
totalDuration += event.durationMs;
|
|
65
|
-
durationCount++;
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
case 'error':
|
|
69
|
-
errors++;
|
|
70
|
-
if (event.errorType) {
|
|
71
|
-
errorsByType[event.errorType] = (errorsByType[event.errorType] || 0) + 1;
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
case 'tool-error':
|
|
75
|
-
toolErrors++;
|
|
76
|
-
if (event.toolName) {
|
|
77
|
-
toolErrorsByName[event.toolName] = (toolErrorsByName[event.toolName] || 0) + 1;
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
case 'interrupted':
|
|
81
|
-
interrupts++;
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
uptimeMs: now - this.startTime,
|
|
87
|
-
lastHour: {
|
|
88
|
-
received,
|
|
89
|
-
completed,
|
|
90
|
-
errors,
|
|
91
|
-
errorsByType,
|
|
92
|
-
toolErrors,
|
|
93
|
-
toolErrorsByName,
|
|
94
|
-
interrupts,
|
|
95
|
-
avgResponseMs: durationCount > 0 ? totalDuration / durationCount : 0
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
package/dist/utils/upgrade.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { execFile } from 'child_process';
|
|
4
|
-
import { getPackageRoot } from '../paths.js';
|
|
5
|
-
/**
|
|
6
|
-
* 比较两个 semver 版本号 (a.b.c 格式)
|
|
7
|
-
* 返回 -1 (a < b), 0 (a == b), 1 (a > b)
|
|
8
|
-
* 自动剥离 pre-release 标签 (e.g. 2.6.0-beta.1 → 2.6.0)
|
|
9
|
-
*/
|
|
10
|
-
export function compareVersions(a, b) {
|
|
11
|
-
const pa = a.split('-')[0].split('.').map(Number);
|
|
12
|
-
const pb = b.split('-')[0].split('.').map(Number);
|
|
13
|
-
const len = Math.max(pa.length, pb.length);
|
|
14
|
-
for (let i = 0; i < len; i++) {
|
|
15
|
-
const na = pa[i] ?? 0;
|
|
16
|
-
const nb = pb[i] ?? 0;
|
|
17
|
-
if (na < nb)
|
|
18
|
-
return -1;
|
|
19
|
-
if (na > nb)
|
|
20
|
-
return 1;
|
|
21
|
-
}
|
|
22
|
-
return 0;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* 检查当前安装是否为 npm link 开发模式。
|
|
26
|
-
* 正式全局安装的路径结构为 .../node_modules/evolclaw,
|
|
27
|
-
* 而 npm link 指向项目源码目录,其父目录不是 node_modules。
|
|
28
|
-
*/
|
|
29
|
-
export function isLinkedInstall() {
|
|
30
|
-
const pkgRoot = getPackageRoot();
|
|
31
|
-
return path.basename(path.dirname(pkgRoot)) !== 'node_modules';
|
|
32
|
-
}
|
|
33
|
-
/** 获取本地 package.json 中的版本号 */
|
|
34
|
-
export function getLocalVersion() {
|
|
35
|
-
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
36
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
37
|
-
return pkg.version;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* 查询 npm registry 上 evolclaw 的最新版本。
|
|
41
|
-
* 超时 15 秒,失败返回 null。
|
|
42
|
-
*/
|
|
43
|
-
export function checkLatestVersion() {
|
|
44
|
-
return new Promise((resolve) => {
|
|
45
|
-
execFile('npm', ['view', 'evolclaw', 'version'], { timeout: 15000 }, (err, stdout) => {
|
|
46
|
-
if (err) {
|
|
47
|
-
resolve(null);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const ver = stdout.trim();
|
|
51
|
-
resolve(ver || null);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 执行 npm install -g evolclaw@latest
|
|
57
|
-
*/
|
|
58
|
-
function runInstall() {
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
execFile('npm', ['install', '-g', 'evolclaw@latest'], { timeout: 120000 }, (err, _stdout, stderr) => {
|
|
61
|
-
if (err) {
|
|
62
|
-
resolve({ ok: false, error: stderr || err.message });
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
resolve({ ok: true });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 完整升级流程:检查 → 比较 → 安装(失败重试一次)
|
|
72
|
-
*/
|
|
73
|
-
export async function tryUpgrade() {
|
|
74
|
-
// 开发模式跳过
|
|
75
|
-
if (isLinkedInstall()) {
|
|
76
|
-
return { status: 'skipped' };
|
|
77
|
-
}
|
|
78
|
-
const localVer = getLocalVersion();
|
|
79
|
-
// 查询 registry
|
|
80
|
-
const remoteVer = await checkLatestVersion();
|
|
81
|
-
if (!remoteVer) {
|
|
82
|
-
return { status: 'skipped', error: 'Failed to check remote version' };
|
|
83
|
-
}
|
|
84
|
-
// 版本比较
|
|
85
|
-
if (compareVersions(localVer, remoteVer) >= 0) {
|
|
86
|
-
return { status: 'no-update', from: localVer };
|
|
87
|
-
}
|
|
88
|
-
// 有新版本,执行升级(失败重试一次)
|
|
89
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
90
|
-
const result = await runInstall();
|
|
91
|
-
if (result.ok) {
|
|
92
|
-
return { status: 'upgraded', from: localVer, to: remoteVer };
|
|
93
|
-
}
|
|
94
|
-
if (attempt === 1) {
|
|
95
|
-
return { status: 'failed', from: localVer, to: remoteVer, error: result.error };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// unreachable
|
|
99
|
-
return { status: 'failed', from: localVer, to: remoteVer };
|
|
100
|
-
}
|