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
@@ -109,6 +109,14 @@ export class MessageBridge {
109
109
  return;
110
110
  // 3. session 解析(使用 Channel 层填充的 chatType)
111
111
  const chatType = msg.chatType || 'private';
112
+ if (!(await this.canCreateThreadSession(channelName, msg, chatType))) {
113
+ // 静默丢弃:绝不向群里注入回复。
114
+ // 拒绝消息本身会带原 thread_id(AUN replyContext 透传),变成一条新群消息;
115
+ // 若发送者也是 agent,该拒绝消息又会 @ 回对方 → A 拒绝→B 收到→B 拒绝→A 收到 的无限循环。
116
+ // AUN 自主模式下「不响应」是合法的,因此无权限创建话题时只记日志、直接 return。
117
+ logger.info(`[MessageBridge] Thread creation denied (silent drop): channel=${channelName} channelId=${msg.channelId} thread=${msg.threadId} sender=${msg.peerId}`);
118
+ return;
119
+ }
112
120
  const metadata = {};
113
121
  // 话题会话创建时写入 replyContext(用于 threadId 路由);主会话不写(避免群聊覆盖)
114
122
  if (msg.threadId && msg.replyContext)
@@ -134,7 +142,7 @@ export class MessageBridge {
134
142
  const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
135
143
  const effectiveProjectPath = owningAgent?.projectPath
136
144
  ?? this.defaultProjectPath;
137
- const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType);
145
+ const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, undefined, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType);
138
146
  // 4. 群聊发送者标注由消息渲染层(message-renderer)逐条承担,不再在此硬编码前缀,
139
147
  // 消息日志因此保存干净原文。policy.messagePrefix 暂保留(未来清理)。
140
148
  // 5. 构造完整消息(channel 字段存实例名,用于 session 精确匹配)
@@ -143,6 +151,7 @@ export class MessageBridge {
143
151
  channelType: msg.channelType || effectiveChannelType,
144
152
  channelId: msg.channelId, content,
145
153
  selfAID: msg.selfAID,
154
+ agentId: session.agentId,
146
155
  chatType,
147
156
  images: msg.images, timestamp: Date.now(),
148
157
  peerId: msg.peerId, peerName: msg.peerName,
@@ -152,25 +161,30 @@ export class MessageBridge {
152
161
  sameEgressIp: msg.sameEgressIp,
153
162
  messageId: msg.messageId,
154
163
  mentions: msg.mentions, mentionAids: msg.mentionAids, threadId: msg.threadId,
164
+ topicName: this.extractTopicName(msg),
155
165
  replyContext: msg.replyContext,
166
+ source: msg.source,
167
+ dispatchMode: msg.dispatchMode,
156
168
  };
157
- // 5.5 写入消息记录(入方向)
158
- const chatDir = this.sessionManager.getChatDir(session);
159
- const inboundEncrypt = msg.replyContext?.metadata?.encrypted != null ? !!(msg.replyContext.metadata.encrypted) : undefined;
160
- const inboundChatmode = msg.replyContext?.metadata?.chatmode;
161
- appendMessageLog(chatDir, buildInboundEntry({
162
- from: msg.peerId || 'unknown',
163
- to: msg.selfAID || 'self',
164
- chatType,
165
- groupId: msg.groupId ?? null,
166
- msgId: msg.messageId ?? null,
167
- content,
168
- replyTo: msg.replyContext?.replyToMessageId ?? null,
169
- permMode: session.identity?.role ?? null,
170
- timestamp: fullMessage.timestamp,
171
- encrypt: inboundEncrypt,
172
- chatmode: inboundChatmode,
173
- }));
169
+ // 5.5 写入消息记录(入方向)。
170
+ {
171
+ const chatDir = this.sessionManager.getChatDir(session);
172
+ const inboundEncrypt = msg.replyContext?.metadata?.encrypted != null ? !!(msg.replyContext.metadata.encrypted) : undefined;
173
+ const inboundChatmode = msg.replyContext?.metadata?.chatmode;
174
+ appendMessageLog(chatDir, buildInboundEntry({
175
+ from: msg.peerId || 'unknown',
176
+ to: msg.selfAID || 'self',
177
+ chatType,
178
+ groupId: msg.groupId ?? null,
179
+ msgId: msg.messageId ?? null,
180
+ content,
181
+ replyTo: msg.replyContext?.replyToMessageId ?? null,
182
+ permMode: session.identity?.role ?? null,
183
+ timestamp: fullMessage.timestamp,
184
+ encrypt: inboundEncrypt,
185
+ chatmode: inboundChatmode,
186
+ }));
187
+ }
174
188
  // 6. ACK + debounce/enqueue
175
189
  // ACK 在到达时立即做(每条独立 ACK),不等合并
176
190
  // Interrupt 模式(单聊)→ 入队前 debounce 合并
@@ -209,6 +223,7 @@ export class MessageBridge {
209
223
  static MENU_NAME_MAP = {
210
224
  pwd: '/pwd',
211
225
  session: '/session',
226
+ topic: '/topic',
212
227
  baseagent: '/baseagent',
213
228
  model: '/model',
214
229
  effort: '/effort',
@@ -220,7 +235,33 @@ export class MessageBridge {
220
235
  cli: '/cli',
221
236
  agent: '/agent',
222
237
  trigger: '/trigger',
238
+ file: '/file',
223
239
  };
240
+ extractTopicName(msg) {
241
+ const raw = msg.topicName
242
+ ?? msg.replyContext?.title
243
+ ?? msg.replyContext?.metadata?.topicName
244
+ ?? msg.replyContext?.metadata?.title;
245
+ const name = typeof raw === 'string' ? raw.trim() : '';
246
+ return name || undefined;
247
+ }
248
+ async canCreateThreadSession(channel, msg, chatType) {
249
+ if (chatType !== 'group' || !msg.threadId)
250
+ return true;
251
+ const existing = await this.sessionManager.getThreadSession(channel, msg.channelId, msg.threadId);
252
+ if (existing)
253
+ return true;
254
+ // 群话题创建权限只看「发送者在该群里的角色」(AUN 经 group.get_admins 实时查询,权威源)。
255
+ // 仅群 owner/admin 可建话题;member / 非成员 / 查询失败(undefined)一律 fail-closed 拒绝。
256
+ // 这与 bot 的 owner/admin 无关——不引入 resolveIdentity 兜底。
257
+ // 不暴露群角色的渠道(adapter 无 getGroupMemberRole)不受此守卫约束,放行。
258
+ const adapter = this.processor?.getChannelInfo?.(channel)?.adapter;
259
+ if (adapter?.getGroupMemberRole) {
260
+ const groupRole = await adapter.getGroupMemberRole(msg.channelId, msg.peerId);
261
+ return groupRole === 'owner' || groupRole === 'admin';
262
+ }
263
+ return true;
264
+ }
224
265
  resolveCmd(name, cmd) {
225
266
  if (cmd)
226
267
  return cmd;
@@ -264,7 +305,7 @@ export class MessageBridge {
264
305
  const { id } = req;
265
306
  try {
266
307
  const identity = this.sessionManager.resolveIdentity(channel, msg.peerId);
267
- const data = this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private');
308
+ const data = this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private', msg.isControlChannel ? 'control' : 'agent');
268
309
  await this.sendMenuResponse(adapter, channel, msg.channelId, { type: 'menu.response', id, data }, sendReply);
269
310
  }
270
311
  catch (err) {
@@ -278,7 +319,7 @@ export class MessageBridge {
278
319
  const { id, name, cmd } = req;
279
320
  try {
280
321
  const resolvedCmd = this.resolveCmd(name, cmd);
281
- const result = await this.cmdHandler.execMenuQuery(resolvedCmd, channel, msg.channelId, msg.peerId, req.args);
322
+ const result = await this.cmdHandler.execMenuQuery(resolvedCmd, channel, msg.channelId, msg.peerId, req.args, msg.chatType, msg.isControlChannel ?? false);
282
323
  if ('error' in result)
283
324
  throw { code: result.code || 'EXEC_FAILED', message: result.error };
284
325
  await this.sendMenuResponse(adapter, channel, msg.channelId, { type: 'menu.response', id, name, data: result.data }, sendReply);
@@ -294,7 +335,7 @@ export class MessageBridge {
294
335
  const { id, name, cmd } = req;
295
336
  try {
296
337
  const resolvedCmd = this.resolveCmd(name, cmd);
297
- const data = await this.cmdHandler.getSubMenuItems(resolvedCmd, channel, msg.channelId, msg.peerId, req.args) ?? [];
338
+ const data = await this.cmdHandler.getSubMenuItems(resolvedCmd, channel, msg.channelId, msg.peerId, req.args, undefined, msg.chatType, msg.isControlChannel ?? false) ?? [];
298
339
  await this.sendMenuResponse(adapter, channel, msg.channelId, { type: 'menu.response', id, name, data }, sendReply);
299
340
  }
300
341
  catch (err) {
@@ -305,12 +346,12 @@ export class MessageBridge {
305
346
  }
306
347
  }
307
348
  async handleMenuUpdate(req, channel, msg, adapter, sendReply) {
308
- const { id, name, cmd, value } = req;
349
+ const { id, name, cmd, value, args } = req;
309
350
  try {
310
351
  if (!value)
311
352
  throw { code: 'MISSING_VALUE', message: '缺少 value 参数' };
312
353
  const resolvedCmd = this.resolveCmd(name, cmd);
313
- const result = await this.cmdHandler.execMenuUpdate(resolvedCmd, value, channel, msg.channelId, msg.peerId);
354
+ const result = await this.cmdHandler.execMenuUpdate(resolvedCmd, value, channel, msg.channelId, msg.peerId, undefined, msg.isControlChannel ?? false, args);
314
355
  if ('error' in result)
315
356
  throw { code: result.code || 'EXEC_FAILED', message: result.error };
316
357
  await this.sendMenuResponse(adapter, channel, msg.channelId, { type: 'menu.response', id, name, data: result.data }, sendReply);
@@ -328,7 +369,7 @@ export class MessageBridge {
328
369
  if (!action)
329
370
  throw { code: 'MISSING_VALUE', message: '缺少 action 参数' };
330
371
  const resolvedCmd = this.resolveCmd(name, cmd);
331
- const result = await this.cmdHandler.execMenuAction(resolvedCmd, action, args, channel, msg.channelId, msg.peerId);
372
+ const result = await this.cmdHandler.execMenuAction(resolvedCmd, action, args, channel, msg.channelId, msg.peerId, undefined, msg.chatType, id, msg.isControlChannel ?? false);
332
373
  if ('error' in result)
333
374
  throw { code: result.code || 'EXEC_FAILED', message: result.error };
334
375
  await this.sendMenuResponse(adapter, channel, msg.channelId, { type: 'menu.response', id, name, data: result.data }, sendReply);
@@ -445,4 +486,12 @@ export class MessageBridge {
445
486
  d.dispose();
446
487
  this.debouncers.clear();
447
488
  }
489
+ /** 注销单个渠道的 debouncer(热重载断开渠道时调用) */
490
+ removeChannel(channelName) {
491
+ const d = this.debouncers.get(channelName);
492
+ if (d) {
493
+ d.dispose();
494
+ this.debouncers.delete(channelName);
495
+ }
496
+ }
448
497
  }
@@ -68,6 +68,7 @@ export function buildInboundEntry(opts) {
68
68
  durationMs: null,
69
69
  encrypt: opts.encrypt,
70
70
  chatmode: opts.chatmode,
71
+ source: opts.source,
71
72
  };
72
73
  }
73
74
  export function buildOutboundEntry(opts) {