evolclaw 3.3.0 → 3.4.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 (44) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +7 -3
  3. package/dist/agents/claude-runner.js +23 -27
  4. package/dist/agents/codex-runner.js +90 -6
  5. package/dist/agents/runner-types.js +30 -0
  6. package/dist/aun/outbox.js +14 -2
  7. package/dist/channels/aun.js +506 -108
  8. package/dist/channels/feishu.js +29 -5
  9. package/dist/cli/agent-command.js +591 -0
  10. package/dist/cli/agent.js +15 -3
  11. package/dist/cli/aun-commands.js +1444 -0
  12. package/dist/cli/ctl-command.js +78 -0
  13. package/dist/cli/daemon-commands.js +2707 -0
  14. package/dist/cli/index.js +12 -5027
  15. package/dist/cli/restart-monitor.js +539 -0
  16. package/dist/cli/watch-logs.js +33 -0
  17. package/dist/core/channel-loader.js +4 -1
  18. package/dist/core/command/command-handler.js +1189 -0
  19. package/dist/core/command/menu-handler.js +1478 -0
  20. package/dist/core/command/slash-gate.js +142 -0
  21. package/dist/core/command/slash-handler.js +2090 -0
  22. package/dist/core/evolagent-registry.js +81 -0
  23. package/dist/core/evolagent.js +16 -0
  24. package/dist/core/message/im-renderer.js +67 -49
  25. package/dist/core/message/message-bridge.js +30 -9
  26. package/dist/core/message/message-processor.js +200 -122
  27. package/dist/core/message/message-queue.js +68 -0
  28. package/dist/core/permission.js +16 -0
  29. package/dist/core/session/session-manager.js +59 -13
  30. package/dist/core/stats/db.js +20 -0
  31. package/dist/core/stats/writer.js +3 -3
  32. package/dist/data/error-dict.json +7 -0
  33. package/dist/index.js +49 -6
  34. package/dist/ipc.js +99 -0
  35. package/dist/utils/cross-platform.js +35 -0
  36. package/dist/utils/ecweb-launch.js +49 -0
  37. package/dist/utils/error-utils.js +18 -5
  38. package/dist/utils/npm-ops.js +38 -8
  39. package/dist/utils/stats.js +63 -6
  40. package/kits/eck_manifest.json +0 -12
  41. package/package.json +2 -3
  42. package/dist/core/command-handler.js +0 -4235
  43. package/dist/core/message/response-depth.js +0 -56
  44. package/kits/templates/system-fragments/response-depth.md +0 -16
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
- import { execFile } from 'child_process';
11
+ import { execFile, execSync } from 'child_process';
12
12
  import { promisify } from 'util';
13
13
  import { getPackageRoot } from '../paths.js';
14
14
  import { isWindows } from './cross-platform.js';
@@ -92,6 +92,28 @@ export function getLocalVersion() {
92
92
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
93
93
  return pkg.version;
94
94
  }
95
+ /**
96
+ * 解析全局安装包的已装版本(通过 `npm root -g` 定位全局 node_modules)。
97
+ * 用于 evolclaw-web 这类独立全局命令包——它们不在 evolclaw 的依赖树里。
98
+ * 未安装或查询失败返回 null。
99
+ */
100
+ export function resolveGlobalPkg(pkgName) {
101
+ try {
102
+ // 命令完全静态(npm root -g,无用户输入),用 execSync 传完整命令字符串,
103
+ // 避免 "args 数组 + shell:true" 组合触发 Node DeprecationWarning,且无注入风险。
104
+ const npmCmd = isWindows ? 'npm.cmd' : 'npm';
105
+ const globalRoot = execSync(`${npmCmd} root -g`, {
106
+ encoding: 'utf-8', timeout: 10000,
107
+ }).trim();
108
+ const pkgPath = path.join(globalRoot, ...pkgName.split('/'), 'package.json');
109
+ if (fs.existsSync(pkgPath)) {
110
+ const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
111
+ return { version: data.version, path: pkgPath };
112
+ }
113
+ }
114
+ catch { /* not found */ }
115
+ return null;
116
+ }
95
117
  /**
96
118
  * 查询 npm registry 上指定包的最新版本。
97
119
  * 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
@@ -147,19 +169,20 @@ export async function tryUpgrade() {
147
169
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
148
170
  }
149
171
  /**
150
- * AUN SDK 升级流程:检查 → 比较 → 安装
151
- * 仅在 SDK 已安装时检查升级,未安装则跳过。
172
+ * 通用全局包升级流程:检查 → 比较 → 安装(失败重试一次)。
173
+ * 仅在包已安装时检查升级,未安装则跳过(resolveFn 返回 null)。
174
+ * fastaun / evolclaw-web 等独立全局包共用此逻辑。
152
175
  */
153
- export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
176
+ export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
154
177
  if (isLinkedInstall()) {
155
178
  return { status: 'skipped' };
156
179
  }
157
- const installed = resolveAunCoreSdkPkg();
180
+ const installed = resolveInstalled();
158
181
  if (!installed) {
159
- return { status: 'skipped' }; // SDK not installed, skip
182
+ return { status: 'skipped' }; // not installed, skip
160
183
  }
161
184
  const localVer = installed.version;
162
- const remoteVer = await checkLatestVersion(AUN_CORE_SDK_PKG);
185
+ const remoteVer = await checkLatestVersion(pkgName);
163
186
  if (!remoteVer) {
164
187
  return { status: 'skipped', error: 'Failed to check remote version' };
165
188
  }
@@ -169,7 +192,7 @@ export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
169
192
  let lastError;
170
193
  for (let attempt = 0; attempt < 2; attempt++) {
171
194
  try {
172
- await npmInstallGlobal(`${AUN_CORE_SDK_PKG}@latest`);
195
+ await npmInstallGlobal(`${pkgName}@latest`);
173
196
  return { status: 'upgraded', from: localVer, to: remoteVer };
174
197
  }
175
198
  catch (e) {
@@ -178,3 +201,10 @@ export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
178
201
  }
179
202
  return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
180
203
  }
204
+ /**
205
+ * AUN SDK 升级流程:检查 → 比较 → 安装
206
+ * 仅在 SDK 已安装时检查升级,未安装则跳过。
207
+ */
208
+ export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
209
+ return tryUpgradeGlobalPkg(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
210
+ }
@@ -2,6 +2,8 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  export class StatsCollector {
4
4
  events = [];
5
+ recentErrors = [];
6
+ MAX_RECENT_ERRORS = 50;
5
7
  startTime;
6
8
  HOUR_MS = 3_600_000;
7
9
  constructor(eventBus) {
@@ -17,7 +19,12 @@ export class StatsCollector {
17
19
  });
18
20
  eventBus.subscribe('task:error', (event) => {
19
21
  const e = event;
20
- this.recordEvent({ type: 'error', timestamp: Date.now(), errorType: e.errorType, agentName: e.agentName });
22
+ const ts = Date.now();
23
+ this.recordEvent({ type: 'error', timestamp: ts, errorType: e.errorType, agentName: e.agentName });
24
+ this.recordRecentError({
25
+ ts, kind: 'task', agentName: e.agentName, errorType: e.errorType,
26
+ message: this.truncate(e.error),
27
+ });
21
28
  });
22
29
  eventBus.subscribe('task:interrupted', (event) => {
23
30
  const e = event;
@@ -26,10 +33,26 @@ export class StatsCollector {
26
33
  eventBus.subscribe('tool:result', (event) => {
27
34
  const e = event;
28
35
  if (e.isError) {
29
- this.recordEvent({ type: 'tool-error', timestamp: Date.now(), toolName: e.toolName, agentName: e.agentName });
36
+ const ts = Date.now();
37
+ this.recordEvent({ type: 'tool-error', timestamp: ts, toolName: e.toolName, agentName: e.agentName });
38
+ this.recordRecentError({
39
+ ts, kind: 'tool', agentName: e.agentName, toolName: e.toolName,
40
+ });
30
41
  }
31
42
  });
32
43
  }
44
+ truncate(s) {
45
+ if (!s)
46
+ return undefined;
47
+ const oneLine = String(s).replace(/\s+/g, ' ').trim();
48
+ return oneLine.length > 200 ? oneLine.slice(0, 200) + '…' : oneLine;
49
+ }
50
+ recordRecentError(err) {
51
+ this.recentErrors.unshift(err); // 新在前
52
+ if (this.recentErrors.length > this.MAX_RECENT_ERRORS) {
53
+ this.recentErrors.length = this.MAX_RECENT_ERRORS;
54
+ }
55
+ }
33
56
  recordEvent(record) {
34
57
  this.events.push(record);
35
58
  }
@@ -84,6 +107,10 @@ export class StatsCollector {
84
107
  break;
85
108
  }
86
109
  }
110
+ // 最近错误:按 agent 过滤(与上面一致),取前 20 条
111
+ const recentErrors = (agentName === undefined
112
+ ? this.recentErrors
113
+ : this.recentErrors.filter(e => (e.agentName ?? '<unknown>') === agentName)).slice(0, 20);
87
114
  return {
88
115
  uptimeMs: now - this.startTime,
89
116
  lastHour: {
@@ -95,7 +122,8 @@ export class StatsCollector {
95
122
  toolErrorsByName,
96
123
  interrupts,
97
124
  avgResponseMs: durationCount > 0 ? totalDuration / durationCount : 0
98
- }
125
+ },
126
+ recentErrors,
99
127
  };
100
128
  }
101
129
  }
@@ -130,6 +158,14 @@ export class AidStatsCollector {
130
158
  if (e.sessionId)
131
159
  this.sessionToAgent.delete(e.sessionId);
132
160
  });
161
+ // 用户主动打断(新消息/​/stop/​撤回):任务有自己的生命周期收尾,不计为 error
162
+ eventBus.subscribe('task:interrupted', (event) => {
163
+ const e = event;
164
+ if (e.agentName)
165
+ this.onTaskEnd(e.agentName, 'interrupted');
166
+ if (e.sessionId)
167
+ this.sessionToAgent.delete(e.sessionId);
168
+ });
133
169
  // thought.put 次数 + 最后一次 thought 文本
134
170
  // 注意:thought.put 是 fire-and-forget async,可能在 task:completed 之后才到达,
135
171
  // 所以同时累加到 currentTask(task 进行中)或 lastTaskEnd(task 已结束但 thought 属于它)
@@ -194,7 +230,10 @@ export class AidStatsCollector {
194
230
  lastSentTo: null,
195
231
  lastSentEncrypt: null,
196
232
  lastSentChatmode: null,
233
+ lastReceivedKind: null,
234
+ lastSentKind: null,
197
235
  uniquePeers: new Set(),
236
+ recentMessages: [],
198
237
  currentTaskStartAt: null,
199
238
  currentTaskReplyCount: 0,
200
239
  currentTaskThoughtPutCount: 0,
@@ -326,7 +365,7 @@ export class AidStatsCollector {
326
365
  const entry = this.getOrCreate(aid);
327
366
  entry.selfName = name;
328
367
  }
329
- recordInbound(aid, fromPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
368
+ recordInbound(aid, fromPeer, byteLength, text, isSystem = false, encrypt, chatmode, kind = 'send') {
330
369
  const entry = this.getOrCreate(aid);
331
370
  if (isSystem) {
332
371
  entry.systemReceived++;
@@ -341,6 +380,13 @@ export class AidStatsCollector {
341
380
  entry.lastReceivedEncrypt = encrypt;
342
381
  if (chatmode)
343
382
  entry.lastReceivedChatmode = chatmode;
383
+ entry.lastReceivedKind = kind;
384
+ // ring buffer:最近 10 轮
385
+ if (text) {
386
+ entry.recentMessages.unshift({ dir: 'in', peer: fromPeer, text: text.slice(0, 100), ts: Date.now(), kind, encrypt: encrypt ?? null, chatmode: chatmode ?? null });
387
+ if (entry.recentMessages.length > 10)
388
+ entry.recentMessages.pop();
389
+ }
344
390
  }
345
391
  entry.bytesReceived += byteLength;
346
392
  entry.uniquePeers.add(fromPeer);
@@ -351,7 +397,7 @@ export class AidStatsCollector {
351
397
  bytes: byteLength, encrypted: encrypt, chatmode,
352
398
  });
353
399
  }
354
- recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
400
+ recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode, kind = 'send') {
355
401
  const entry = this.getOrCreate(aid);
356
402
  if (isSystem) {
357
403
  entry.systemSent++;
@@ -366,6 +412,13 @@ export class AidStatsCollector {
366
412
  entry.lastSentEncrypt = encrypt;
367
413
  if (chatmode)
368
414
  entry.lastSentChatmode = chatmode;
415
+ entry.lastSentKind = kind;
416
+ // ring buffer:最近 10 轮
417
+ if (text) {
418
+ entry.recentMessages.unshift({ dir: 'out', peer: toPeer, text: text.slice(0, 100), ts: Date.now(), kind, encrypt: encrypt ?? null, chatmode: chatmode ?? null });
419
+ if (entry.recentMessages.length > 10)
420
+ entry.recentMessages.pop();
421
+ }
369
422
  // 累计当前 task 的回复数
370
423
  if (entry.currentTaskStartAt != null) {
371
424
  entry.currentTaskReplyCount++;
@@ -389,7 +442,7 @@ export class AidStatsCollector {
389
442
  for (const entry of this.entries.values()) {
390
443
  const queueStats = this.queueStatsProvider
391
444
  ? this.queueStatsProvider(entry.aid)
392
- : { processing: 0, queued: 0 };
445
+ : { processing: 0, queued: 0, muted: false };
393
446
  out.push({
394
447
  aid: entry.aid,
395
448
  selfName: entry.selfName,
@@ -409,9 +462,13 @@ export class AidStatsCollector {
409
462
  lastSentTo: entry.lastSentTo,
410
463
  lastSentEncrypt: entry.lastSentEncrypt,
411
464
  lastSentChatmode: entry.lastSentChatmode,
465
+ lastReceivedKind: entry.lastReceivedKind,
466
+ lastSentKind: entry.lastSentKind,
412
467
  uniquePeerCount: entry.uniquePeers.size,
413
468
  processing: queueStats.processing,
414
469
  queued: queueStats.queued,
470
+ muted: queueStats.muted,
471
+ recentMessages: entry.recentMessages,
415
472
  lastTaskEnd: entry.lastTaskEnd,
416
473
  });
417
474
  }
@@ -100,18 +100,6 @@
100
100
  "when": { "var": "clientType", "neq": null },
101
101
  "description": "客户端环境文档"
102
102
  },
103
- {
104
- "id": "response-depth",
105
- "type": "file",
106
- "file": "$KITS_FRAGMENTS/response-depth.md",
107
- "order": 45,
108
- "needsInjection": true,
109
- "when": { "and": [
110
- { "var": "chatType", "eq": "group" },
111
- { "var": "responseDepth", "in": ["lightweight", "deep"] }
112
- ]},
113
- "description": "群聊响应深度指引(lightweight/deep 时注入)"
114
- },
115
103
  {
116
104
  "id": "channel-layer",
117
105
  "type": "file",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,8 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@agentunion/fastaun": "^0.4.13",
36
- "@anthropic-ai/claude-agent-sdk": "^0.3.156",
37
- "@anthropic-ai/sdk": "^0.100.1",
36
+ "@anthropic-ai/claude-agent-sdk": "^0.3.170",
38
37
  "@modelcontextprotocol/sdk": "^1.29.0",
39
38
  "cron-parser": "^5.5.0",
40
39
  "dotenv": "^17.4.2",