evolclaw 3.2.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 (95) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +7 -4
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -31
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1152 -140
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +58 -0
  9. package/dist/aun/aid/store.js +1 -1
  10. package/dist/aun/outbox.js +14 -2
  11. package/dist/aun/storage/download.js +1 -1
  12. package/dist/aun/storage/upload.js +13 -1
  13. package/dist/channels/aun.js +869 -358
  14. package/dist/channels/dingtalk.js +77 -140
  15. package/dist/channels/feishu.js +125 -154
  16. package/dist/channels/qqbot.js +75 -138
  17. package/dist/channels/wechat.js +75 -136
  18. package/dist/channels/wecom.js +75 -138
  19. package/dist/cli/agent-command.js +591 -0
  20. package/dist/cli/agent.js +23 -8
  21. package/dist/cli/aun-commands.js +1444 -0
  22. package/dist/cli/ctl-command.js +78 -0
  23. package/dist/cli/daemon-commands.js +2707 -0
  24. package/dist/cli/index.js +23 -4905
  25. package/dist/cli/init.js +33 -6
  26. package/dist/cli/model.js +1 -1
  27. package/dist/cli/restart-monitor.js +539 -0
  28. package/dist/cli/stats.js +558 -0
  29. package/dist/cli/version.js +87 -0
  30. package/dist/cli/watch-logs.js +33 -0
  31. package/dist/cli/watch-msg.js +5 -2
  32. package/dist/config-store.js +12 -6
  33. package/dist/core/channel-loader.js +88 -83
  34. package/dist/core/command/command-handler.js +1189 -0
  35. package/dist/core/command/menu-handler.js +1478 -0
  36. package/dist/core/command/slash-gate.js +142 -0
  37. package/dist/core/command/slash-handler.js +2090 -0
  38. package/dist/core/evolagent-registry.js +82 -0
  39. package/dist/core/evolagent.js +17 -1
  40. package/dist/core/interaction-router.js +8 -0
  41. package/dist/core/message/command-handler-agent-control.js +63 -1
  42. package/dist/core/message/im-renderer.js +91 -51
  43. package/dist/core/message/items-formatter.js +9 -1
  44. package/dist/core/message/message-bridge.js +73 -24
  45. package/dist/core/message/message-log.js +1 -0
  46. package/dist/core/message/message-processor.js +432 -94
  47. package/dist/core/message/message-queue.js +70 -2
  48. package/dist/core/message/pending-hints.js +232 -0
  49. package/dist/core/model/model-catalog.js +1 -1
  50. package/dist/core/model/model-scope.js +2 -2
  51. package/dist/core/permission.js +25 -12
  52. package/dist/core/relation/peer-identity.js +16 -1
  53. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  54. package/dist/core/session/session-manager.js +86 -26
  55. package/dist/core/session/session-title.js +26 -0
  56. package/dist/core/stats/billing.js +151 -0
  57. package/dist/core/stats/budget.js +93 -0
  58. package/dist/core/stats/db.js +334 -0
  59. package/dist/core/stats/eck-vars.js +84 -0
  60. package/dist/core/stats/index.js +10 -0
  61. package/dist/core/stats/normalizer.js +78 -0
  62. package/dist/core/stats/query.js +760 -0
  63. package/dist/core/stats/writer.js +115 -0
  64. package/dist/core/trigger/manager.js +34 -0
  65. package/dist/core/trigger/parser.js +9 -3
  66. package/dist/core/trigger/scheduler.js +20 -17
  67. package/dist/data/error-dict.json +7 -0
  68. package/dist/{agents → eck}/manifest-engine.js +20 -1
  69. package/dist/{agents → eck}/message-renderer.js +24 -1
  70. package/dist/index.js +174 -9
  71. package/dist/ipc.js +116 -1
  72. package/dist/utils/cross-platform.js +58 -5
  73. package/dist/utils/ecweb-launch.js +49 -0
  74. package/dist/utils/ecweb-pair.js +20 -0
  75. package/dist/utils/error-utils.js +18 -5
  76. package/dist/utils/npm-ops.js +38 -8
  77. package/dist/utils/stats.js +77 -6
  78. package/kits/docs/evolclaw/INDEX.md +3 -1
  79. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  80. package/kits/docs/evolclaw/fs.md +131 -0
  81. package/kits/docs/evolclaw/group-fs.md +209 -0
  82. package/kits/docs/evolclaw/stats.md +70 -0
  83. package/kits/docs/venues/aun-group.md +29 -6
  84. package/kits/docs/venues/group.md +5 -4
  85. package/kits/eck_message_manifest.json +30 -3
  86. package/kits/rules/05-venue.md +1 -1
  87. package/kits/templates/message-fragments/inject-default.md +2 -0
  88. package/package.json +5 -6
  89. package/dist/agents/baseagent-normalize.js +0 -19
  90. package/dist/core/command-handler.js +0 -3876
  91. package/dist/core/relation/peer-key.js +0 -16
  92. package/dist/evolclaw-config.js +0 -11
  93. package/dist/utils/channel-helpers.js +0 -46
  94. /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
  95. /package/dist/{agents → eck}/kit-renderer.js +0 -0
@@ -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
  }
@@ -104,6 +132,8 @@ export class AidStatsCollector {
104
132
  queueStatsProvider;
105
133
  /** sessionId → 当前正在跑该 session 的 agent,task:started 写入,task:completed/error 清除 */
106
134
  sessionToAgent = new Map();
135
+ /** 外部注入的持久化回调(写入 message_events 表) */
136
+ onMessage;
107
137
  constructor(eventBus) {
108
138
  if (!eventBus)
109
139
  return;
@@ -128,6 +158,14 @@ export class AidStatsCollector {
128
158
  if (e.sessionId)
129
159
  this.sessionToAgent.delete(e.sessionId);
130
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
+ });
131
169
  // thought.put 次数 + 最后一次 thought 文本
132
170
  // 注意:thought.put 是 fire-and-forget async,可能在 task:completed 之后才到达,
133
171
  // 所以同时累加到 currentTask(task 进行中)或 lastTaskEnd(task 已结束但 thought 属于它)
@@ -192,7 +230,10 @@ export class AidStatsCollector {
192
230
  lastSentTo: null,
193
231
  lastSentEncrypt: null,
194
232
  lastSentChatmode: null,
233
+ lastReceivedKind: null,
234
+ lastSentKind: null,
195
235
  uniquePeers: new Set(),
236
+ recentMessages: [],
196
237
  currentTaskStartAt: null,
197
238
  currentTaskReplyCount: 0,
198
239
  currentTaskThoughtPutCount: 0,
@@ -324,7 +365,7 @@ export class AidStatsCollector {
324
365
  const entry = this.getOrCreate(aid);
325
366
  entry.selfName = name;
326
367
  }
327
- recordInbound(aid, fromPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
368
+ recordInbound(aid, fromPeer, byteLength, text, isSystem = false, encrypt, chatmode, kind = 'send') {
328
369
  const entry = this.getOrCreate(aid);
329
370
  if (isSystem) {
330
371
  entry.systemReceived++;
@@ -339,11 +380,24 @@ export class AidStatsCollector {
339
380
  entry.lastReceivedEncrypt = encrypt;
340
381
  if (chatmode)
341
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
+ }
342
390
  }
343
391
  entry.bytesReceived += byteLength;
344
392
  entry.uniquePeers.add(fromPeer);
393
+ // 持久化
394
+ this.onMessage?.({
395
+ ts: Date.now(), agent_aid: aid, peer_key: `aun#${fromPeer}`,
396
+ direction: 'in', msg_type: isSystem ? 'system' : 'private',
397
+ bytes: byteLength, encrypted: encrypt, chatmode,
398
+ });
345
399
  }
346
- recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
400
+ recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode, kind = 'send') {
347
401
  const entry = this.getOrCreate(aid);
348
402
  if (isSystem) {
349
403
  entry.systemSent++;
@@ -358,6 +412,13 @@ export class AidStatsCollector {
358
412
  entry.lastSentEncrypt = encrypt;
359
413
  if (chatmode)
360
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
+ }
361
422
  // 累计当前 task 的回复数
362
423
  if (entry.currentTaskStartAt != null) {
363
424
  entry.currentTaskReplyCount++;
@@ -369,13 +430,19 @@ export class AidStatsCollector {
369
430
  }
370
431
  entry.bytesSent += byteLength;
371
432
  entry.uniquePeers.add(toPeer);
433
+ // 持久化
434
+ this.onMessage?.({
435
+ ts: Date.now(), agent_aid: aid, peer_key: `aun#${toPeer}`,
436
+ direction: 'out', msg_type: isSystem ? 'system' : 'private',
437
+ bytes: byteLength, encrypted: encrypt, chatmode,
438
+ });
372
439
  }
373
440
  getAllSnapshots() {
374
441
  const out = [];
375
442
  for (const entry of this.entries.values()) {
376
443
  const queueStats = this.queueStatsProvider
377
444
  ? this.queueStatsProvider(entry.aid)
378
- : { processing: 0, queued: 0 };
445
+ : { processing: 0, queued: 0, muted: false };
379
446
  out.push({
380
447
  aid: entry.aid,
381
448
  selfName: entry.selfName,
@@ -395,9 +462,13 @@ export class AidStatsCollector {
395
462
  lastSentTo: entry.lastSentTo,
396
463
  lastSentEncrypt: entry.lastSentEncrypt,
397
464
  lastSentChatmode: entry.lastSentChatmode,
465
+ lastReceivedKind: entry.lastReceivedKind,
466
+ lastSentKind: entry.lastSentKind,
398
467
  uniquePeerCount: entry.uniquePeers.size,
399
468
  processing: queueStats.processing,
400
469
  queued: queueStats.queued,
470
+ muted: queueStats.muted,
471
+ recentMessages: entry.recentMessages,
401
472
  lastTaskEnd: entry.lastTaskEnd,
402
473
  });
403
474
  }
@@ -14,7 +14,8 @@
14
14
  | `ec msg` | 私聊收发消息 | 回复/发消息/拉取/撤回/查在线 | 有对端(peerId) | `msg.md` |
15
15
  | `ec group` | 群聊收发与群管理 | 群发/建群/邀请/踢人/退群/群成员 | 群聊(groupId) | `group.md` |
16
16
  | `ec aid` | AID 身份管理 | 身份/证书/名片/探测对端 | 任意有渠道场景 | `aid.md` |
17
- | `ec storage` | 文件存储 | 上传/下载/配额 | 任意有渠道场景 | `storage.md` |
17
+ | `ec fs` | 网络文件系统(统一前端) | 上传/下载/看文件/列目录/分享/共享/挂载/配额 | 任意有渠道场景 | `fs.md`(架构全景 `fs-architecture.md`) |
18
+ | `ec storage` | 文件存储(底层调试) | 上传/下载/配额 | 任意有渠道场景 | `storage.md` |
18
19
  | `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md` |
19
20
  | `ec rpc` | 底层 AUN RPC(逃生通道) | 直接调协议方法 | 高级/兜底 | `rpc.md` |
20
21
 
@@ -29,6 +30,7 @@
29
30
  | 命令集 | 用途 | 触发词 | 文档 |
30
31
  |--------|------|--------|------|
31
32
  | `ec model` | 模型管理(按作用域持久化) | 切模型/列模型/看当前/改强度 | `model.md` |
33
+ | `ec stats` | Token 用量与费用统计 | 用量/费用/统计/预算/token/cost | `stats.md` |
32
34
 
33
35
  ## 开发者工具(非 agent 会话能力)
34
36