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
@@ -1,7 +1,7 @@
1
1
  import { logger } from '../utils/logger.js';
2
2
  import { markdownToPlainText } from '../utils/rich-content-renderer.js';
3
3
  import { requireOptional } from '../utils/npm-ops.js';
4
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
4
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
5
5
  import { formatItemsAsText } from '../core/message/items-formatter.js';
6
6
  // ── QQBotChannel ────────────────────────────────────────────────────────────
7
7
  export class QQBotChannel {
@@ -309,143 +309,80 @@ function isValidCredential(value) {
309
309
  }
310
310
  export class QQBotChannelPlugin {
311
311
  name = 'qqbot';
312
- isEnabled(config) {
313
- const raw = config.channels?.qqbot;
314
- if (!raw)
315
- return false;
316
- if (Array.isArray(raw)) {
317
- return raw.some(inst => inst.enabled !== false && isValidCredential(inst.appId) && isValidCredential(inst.clientSecret));
318
- }
319
- if (raw.enabled === false)
320
- return false;
321
- return isValidCredential(raw.appId) && isValidCredential(raw.clientSecret);
322
- }
323
- async createChannels(config) {
324
- const instances = normalizeChannelInstances(config.channels?.qqbot, 'qqbot');
325
- const result = [];
326
- for (const inst of instances) {
327
- if (inst.enabled === false)
328
- continue;
329
- if (!isValidCredential(inst.appId) || !isValidCredential(inst.clientSecret))
330
- continue;
331
- const channel = new QQBotChannel({
332
- appId: inst.appId,
333
- clientSecret: inst.clientSecret,
334
- });
335
- const adapter = {
336
- channelName: inst.name,
337
- channelKey: inst.name,
338
- capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
339
- send: async (envelope, payload) => {
340
- const ctx = envelope.replyContext;
341
- const channelId = envelope.channelId;
342
- switch (payload.kind) {
343
- case 'result.text':
344
- case 'command.result':
345
- case 'command.error':
346
- case 'system.notice':
347
- case 'system.error':
348
- case 'result.error':
349
- await channel.sendMessage(channelId, payload.text);
350
- return;
351
- case 'result.file':
352
- await channel.sendFile(channelId, payload.filePath);
353
- return;
354
- case 'result.image':
355
- await channel.sendImage(channelId, payload.data);
356
- return;
357
- case 'activity.batch': {
358
- const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
359
- const text = formatItemsAsText(filtered);
360
- if (text)
361
- await channel.sendMessage(channelId, text);
362
- return;
363
- }
364
- case 'interaction':
365
- if (payload.fallbackText)
366
- await channel.sendMessage(channelId, payload.fallbackText);
367
- return;
368
- case 'status.started':
369
- case 'status.completed':
370
- case 'status.interrupted':
371
- case 'status.error':
372
- case 'status.timeout':
373
- case 'status.progress':
374
- case 'custom':
375
- return;
376
- default:
377
- logger.warn(`[QQBot] Unhandled payload kind: ${payload.kind}`);
312
+ async createInstance(inst, ctx) {
313
+ if (inst.enabled === false)
314
+ return null;
315
+ if (!isValidCredential(inst.appId) || !isValidCredential(inst.clientSecret))
316
+ return null;
317
+ const channel = new QQBotChannel({
318
+ appId: inst.appId,
319
+ clientSecret: inst.clientSecret,
320
+ });
321
+ const mode = resolveShowActivities(inst);
322
+ const adapter = {
323
+ channelName: inst.name,
324
+ channelKey: inst.name,
325
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
326
+ send: async (envelope, payload) => {
327
+ const channelId = envelope.channelId;
328
+ switch (payload.kind) {
329
+ case 'result.text':
330
+ case 'command.result':
331
+ case 'command.error':
332
+ case 'system.notice':
333
+ case 'system.error':
334
+ case 'result.error':
335
+ await channel.sendMessage(channelId, payload.text);
336
+ return;
337
+ case 'result.file':
338
+ await channel.sendFile(channelId, payload.filePath);
339
+ return;
340
+ case 'result.image':
341
+ await channel.sendImage(channelId, payload.data);
342
+ return;
343
+ case 'activity.batch': {
344
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
345
+ const text = formatItemsAsText(filtered);
346
+ if (text)
347
+ await channel.sendMessage(channelId, text);
348
+ return;
378
349
  }
379
- },
380
- };
381
- const policy = {
382
- canSwitchProject: (_chatType, identity) => identity === 'owner' || identity === 'admin',
383
- canListProjects: (_chatType, identity) => identity === 'owner' || identity === 'admin',
384
- canCreateSession: () => true,
385
- canDeleteSession: () => true,
386
- canImportCliSession: (_chatType, identity) => identity === 'owner' || identity === 'admin',
387
- messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
388
- showMiddleResult: (chatType, identity) => {
389
- const mode = getChannelShowActivities(config, inst.name);
390
- if (mode === 'none')
391
- return false;
392
- if (mode === 'dm-only')
393
- return chatType === 'private';
394
- if (mode === 'owner-dm-only')
395
- return chatType === 'private' && identity === 'owner';
396
- return true;
397
- },
398
- showIdleMonitor: (chatType, identity) => {
399
- const mode = getChannelShowActivities(config, inst.name);
400
- if (mode === 'none')
401
- return false;
402
- if (mode === 'dm-only')
403
- return chatType === 'private';
404
- if (mode === 'owner-dm-only')
405
- return chatType === 'private' && identity === 'owner';
406
- return true;
407
- },
408
- accumulateErrors: () => true,
409
- };
410
- const options = {
411
- fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
412
- supportsImages: true,
413
- flushDelay: inst.flushDelay,
414
- };
415
- result.push({
416
- channelType: 'qqbot',
417
- adapter,
418
- channel,
419
- policy,
420
- options,
421
- connect: () => channel.connect(),
422
- disconnect: () => channel.disconnect(),
423
- onProjectPathRequest: () => Promise.resolve(config.projects?.defaultPath || process.cwd()),
424
- registerBridge(bridge, channelType) {
425
- bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
426
- handler({
427
- channel: adapter.channelName,
428
- channelType,
429
- channelId: event.channelId,
430
- selfAID: inst.agentName,
431
- content: event.content,
432
- images: event.images,
433
- chatType: event.chatType || 'private',
434
- peerId: event.peerId || '',
435
- peerName: event.peerName,
436
- messageId: event.messageId,
437
- });
438
- }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
439
- },
440
- });
441
- }
442
- return result;
443
- }
444
- async createChannel(config) {
445
- const instances = await this.createChannels(config);
446
- if (instances.length === 0) {
447
- throw new Error('QQBot config missing or invalid');
448
- }
449
- return instances[0];
350
+ case 'interaction':
351
+ if (payload.fallbackText)
352
+ await channel.sendMessage(channelId, payload.fallbackText);
353
+ return;
354
+ default: return;
355
+ }
356
+ },
357
+ };
358
+ const policy = {
359
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
360
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
361
+ canCreateSession: () => true,
362
+ canDeleteSession: () => true,
363
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
364
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
365
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
366
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
367
+ accumulateErrors: () => true,
368
+ };
369
+ return {
370
+ channelType: 'qqbot', adapter, channel,
371
+ policy,
372
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
373
+ connect: () => channel.connect(),
374
+ disconnect: () => channel.disconnect(),
375
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
376
+ registerBridge(bridge, channelType) {
377
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
378
+ handler({
379
+ channel: adapter.channelName, channelType, channelId: event.channelId,
380
+ selfAID: ctx.agentName, content: event.content, images: event.images,
381
+ chatType: event.chatType || 'private', peerId: event.peerId || '',
382
+ peerName: event.peerName, messageId: event.messageId,
383
+ });
384
+ }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
385
+ },
386
+ };
450
387
  }
451
388
  }
@@ -712,145 +712,84 @@ export class WechatChannel {
712
712
  });
713
713
  }
714
714
  }
715
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
715
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
716
716
  export class WechatChannelPlugin {
717
717
  name = 'wechat';
718
- isEnabled(config) {
719
- const raw = config.channels?.wechat;
720
- if (!raw)
721
- return false;
722
- if (Array.isArray(raw)) {
723
- return raw.some(inst => inst.enabled !== false && !!inst.token);
724
- }
725
- return raw.enabled === true && !!raw.token;
726
- }
727
- async createChannels(config) {
728
- const instances = normalizeChannelInstances(config.channels?.wechat, 'wechat');
729
- const result = [];
730
- for (const inst of instances) {
731
- if (inst.enabled === false || !inst.token)
732
- continue;
733
- const channel = new WechatChannel({
734
- baseUrl: inst.baseUrl || 'https://ilinkai.weixin.qq.com',
735
- token: inst.token,
736
- });
737
- const adapter = {
738
- channelName: inst.name,
739
- channelKey: inst.name,
740
- capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true, thread: false },
741
- send: async (envelope, payload) => {
742
- const channelId = envelope.channelId;
743
- switch (payload.kind) {
744
- case 'result.text':
745
- case 'command.result':
746
- case 'command.error':
747
- case 'system.notice':
748
- case 'system.error':
749
- case 'result.error':
750
- await channel.sendMessage(channelId, payload.text);
751
- return;
752
- case 'result.file': {
753
- const name = payload.fileName || payload.filePath;
754
- await channel.sendMessage(channelId, `\ud83d\udcce \u6587\u4ef6\u5df2\u751f\u6210\uff1a${name}\n\u8def\u5f84\uff1a${payload.filePath}`);
755
- return;
756
- }
757
- case 'result.image':
758
- return;
759
- case 'activity.batch': {
760
- // WeChat 不发送成功的 tool_result
761
- const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
762
- const text = formatItemsAsText(filtered);
763
- if (text)
764
- await channel.sendMessage(channelId, text);
765
- return;
766
- }
767
- case 'interaction':
768
- if (payload.fallbackText)
769
- await channel.sendMessage(channelId, payload.fallbackText);
770
- return;
771
- case 'status.started':
772
- case 'status.completed':
773
- case 'status.interrupted':
774
- case 'status.error':
775
- case 'status.timeout':
776
- case 'status.progress':
777
- case 'custom':
778
- return;
779
- default:
780
- logger.warn(`[WeChat] Unhandled payload kind: ${payload.kind}`);
718
+ async createInstance(inst, ctx) {
719
+ if (inst.enabled === false || !inst.token)
720
+ return null;
721
+ const channel = new WechatChannel({
722
+ baseUrl: inst.baseUrl || 'https://ilinkai.weixin.qq.com',
723
+ token: inst.token,
724
+ });
725
+ const mode = resolveShowActivities(inst);
726
+ const adapter = {
727
+ channelName: inst.name,
728
+ channelKey: inst.name,
729
+ capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true, thread: false },
730
+ send: async (envelope, payload) => {
731
+ const channelId = envelope.channelId;
732
+ switch (payload.kind) {
733
+ case 'result.text':
734
+ case 'command.result':
735
+ case 'command.error':
736
+ case 'system.notice':
737
+ case 'system.error':
738
+ case 'result.error':
739
+ await channel.sendMessage(channelId, payload.text);
740
+ return;
741
+ case 'result.file': {
742
+ const name = payload.fileName || payload.filePath;
743
+ await channel.sendMessage(channelId, `📎 文件已生成:${name}\n路径:${payload.filePath}`);
744
+ return;
781
745
  }
782
- },
783
- };
784
- const policy = {
785
- canSwitchProject: (chatType, identity) => identity === 'owner' || identity === 'admin',
786
- canListProjects: (chatType, identity) => identity === 'owner' || identity === 'admin',
787
- canCreateSession: (chatType, identity) => true,
788
- canDeleteSession: (chatType, identity) => true,
789
- canImportCliSession: (chatType, identity) => identity === 'owner' || identity === 'admin',
790
- messagePrefix: (chatType, peerName) => '',
791
- showMiddleResult: (chatType, identity) => {
792
- const mode = getChannelShowActivities(config, inst.name);
793
- if (mode === 'none')
794
- return false;
795
- if (mode === 'dm-only')
796
- return chatType === 'private';
797
- if (mode === 'owner-dm-only')
798
- return chatType === 'private' && identity === 'owner';
799
- return true;
800
- },
801
- showIdleMonitor: (chatType, identity) => {
802
- const mode = getChannelShowActivities(config, inst.name);
803
- if (mode === 'none')
804
- return false;
805
- if (mode === 'dm-only')
806
- return chatType === 'private';
807
- if (mode === 'owner-dm-only')
808
- return chatType === 'private' && identity === 'owner';
809
- return true;
810
- },
811
- accumulateErrors: (chatType, identity) => true,
812
- };
813
- const options = {
814
- fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
815
- flushDelay: inst.flushDelay ?? 3, // WeChat 默认 3s
816
- };
817
- result.push({
818
- channelType: 'wechat',
819
- adapter,
820
- channel,
821
- policy,
822
- options,
823
- connect: () => channel.connect(),
824
- disconnect: () => channel.disconnect(),
825
- onProjectPathRequest: (channelId) => Promise.resolve(config.projects?.defaultPath || process.cwd()),
826
- registerBridge(bridge, channelType) {
827
- bridge.register(adapter.channelName, (handler) => channel.onMessage(async (channelId, content, peerId, images, chatType) => {
828
- await handler({
829
- channel: adapter.channelName,
830
- channelType,
831
- channelId,
832
- selfAID: inst.agentName,
833
- content,
834
- images,
835
- chatType: chatType || 'private',
836
- peerId: peerId || '',
837
- });
838
- }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
839
- },
840
- registerHooks(ctx) {
841
- if (channel.setEventBus) {
842
- channel.setEventBus(ctx.eventBus);
746
+ case 'result.image': return;
747
+ case 'activity.batch': {
748
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
749
+ const text = formatItemsAsText(filtered);
750
+ if (text)
751
+ await channel.sendMessage(channelId, text);
752
+ return;
843
753
  }
844
- },
845
- });
846
- }
847
- return result;
848
- }
849
- async createChannel(config) {
850
- const instances = await this.createChannels(config);
851
- if (instances.length === 0) {
852
- throw new Error('WeChat config missing');
853
- }
854
- return instances[0];
754
+ case 'interaction':
755
+ if (payload.fallbackText)
756
+ await channel.sendMessage(channelId, payload.fallbackText);
757
+ return;
758
+ default: return;
759
+ }
760
+ },
761
+ };
762
+ const policy = {
763
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
764
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
765
+ canCreateSession: () => true,
766
+ canDeleteSession: () => true,
767
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
768
+ messagePrefix: () => '',
769
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
770
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
771
+ accumulateErrors: () => true,
772
+ };
773
+ return {
774
+ channelType: 'wechat', adapter, channel,
775
+ policy,
776
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, flushDelay: inst.flushDelay ?? 3 },
777
+ connect: () => channel.connect(),
778
+ disconnect: () => channel.disconnect(),
779
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
780
+ registerBridge(bridge, channelType) {
781
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async (channelId, content, peerId, images, chatType) => {
782
+ await handler({
783
+ channel: adapter.channelName, channelType, channelId,
784
+ selfAID: ctx.agentName, content, images,
785
+ chatType: chatType || 'private', peerId: peerId || '',
786
+ });
787
+ }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
788
+ },
789
+ registerHooks(hookCtx) {
790
+ if (channel.setEventBus)
791
+ channel.setEventBus(hookCtx.eventBus);
792
+ },
793
+ };
855
794
  }
856
795
  }