evolclaw 3.1.11 → 3.3.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 (89) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +27 -2
  3. package/dist/agents/{resolve.js → baseagent.js} +34 -5
  4. package/dist/agents/claude-runner.js +120 -27
  5. package/dist/agents/codex-app-server-client.js +364 -0
  6. package/dist/agents/codex-runner.js +1069 -141
  7. package/dist/agents/gemini-runner.js +2 -2
  8. package/dist/agents/runner-types.js +28 -0
  9. package/dist/aun/aid/control-aid.js +67 -0
  10. package/dist/aun/aid/identity.js +20 -7
  11. package/dist/aun/aid/store.js +2 -2
  12. package/dist/aun/storage/download.js +1 -1
  13. package/dist/aun/storage/upload.js +13 -1
  14. package/dist/channels/aun.js +538 -325
  15. package/dist/channels/dingtalk.js +77 -140
  16. package/dist/channels/feishu.js +98 -151
  17. package/dist/channels/qqbot.js +75 -138
  18. package/dist/channels/wechat.js +75 -136
  19. package/dist/channels/wecom.js +75 -138
  20. package/dist/cli/agent.js +44 -13
  21. package/dist/cli/index.js +207 -46
  22. package/dist/cli/init-channel.js +38 -148
  23. package/dist/cli/init.js +192 -85
  24. package/dist/cli/model.js +1 -1
  25. package/dist/cli/stats.js +558 -0
  26. package/dist/cli/version.js +87 -0
  27. package/dist/cli/watch-msg.js +5 -2
  28. package/dist/config-store.js +48 -11
  29. package/dist/core/channel-loader.js +84 -82
  30. package/dist/core/command-handler.js +754 -172
  31. package/dist/core/daemon-file-cache.js +216 -0
  32. package/dist/core/evolagent-registry.js +4 -0
  33. package/dist/core/evolagent.js +28 -23
  34. package/dist/core/interaction-router.js +8 -0
  35. package/dist/core/message/command-handler-agent-control.js +215 -0
  36. package/dist/core/message/create-status.js +67 -0
  37. package/dist/core/message/im-renderer.js +35 -13
  38. package/dist/core/message/items-formatter.js +9 -1
  39. package/dist/core/message/message-bridge.js +52 -22
  40. package/dist/core/message/message-log.js +1 -0
  41. package/dist/core/message/message-processor.js +336 -68
  42. package/dist/core/message/message-queue.js +15 -8
  43. package/dist/core/message/pending-hints.js +232 -0
  44. package/dist/core/message/response-depth.js +56 -0
  45. package/dist/core/model/model-catalog.js +1 -1
  46. package/dist/core/model/model-scope.js +40 -7
  47. package/dist/core/permission.js +9 -12
  48. package/dist/core/relation/peer-identity.js +16 -1
  49. package/dist/core/session/adapters/claude-session-file-adapter.js +48 -5
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  51. package/dist/core/session/session-manager.js +27 -13
  52. package/dist/core/session/session-title.js +26 -0
  53. package/dist/core/stats/billing.js +151 -0
  54. package/dist/core/stats/budget.js +93 -0
  55. package/dist/core/stats/db.js +314 -0
  56. package/dist/core/stats/eck-vars.js +84 -0
  57. package/dist/core/stats/index.js +10 -0
  58. package/dist/core/stats/normalizer.js +78 -0
  59. package/dist/core/stats/query.js +760 -0
  60. package/dist/core/stats/writer.js +115 -0
  61. package/dist/core/trigger/manager.js +34 -0
  62. package/dist/core/trigger/parser.js +9 -3
  63. package/dist/core/trigger/scheduler.js +20 -17
  64. package/dist/{agents → eck}/kit-renderer.js +5 -1
  65. package/dist/{agents → eck}/manifest-engine.js +127 -35
  66. package/dist/{agents → eck}/message-renderer.js +26 -1
  67. package/dist/index.js +185 -8
  68. package/dist/ipc.js +22 -0
  69. package/dist/paths.js +7 -3
  70. package/dist/utils/cross-platform.js +23 -5
  71. package/dist/utils/ecweb-pair.js +20 -0
  72. package/dist/utils/stats.js +14 -0
  73. package/kits/docs/evolclaw/INDEX.md +3 -1
  74. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  75. package/kits/docs/evolclaw/fs.md +131 -0
  76. package/kits/docs/evolclaw/group-fs.md +209 -0
  77. package/kits/docs/evolclaw/stats.md +70 -0
  78. package/kits/docs/venues/aun-group.md +29 -6
  79. package/kits/docs/venues/group.md +5 -4
  80. package/kits/eck_manifest.json +12 -0
  81. package/kits/eck_message_manifest.json +30 -3
  82. package/kits/rules/05-venue.md +1 -1
  83. package/kits/templates/message-fragments/inject-default.md +2 -0
  84. package/kits/templates/message-fragments/item.md +1 -1
  85. package/kits/templates/system-fragments/response-depth.md +16 -0
  86. package/package.json +4 -4
  87. package/dist/agents/baseagent-normalize.js +0 -19
  88. package/dist/core/relation/peer-key.js +0 -16
  89. package/dist/utils/channel-helpers.js +0 -46
@@ -1,6 +1,6 @@
1
1
  import { logger } from '../utils/logger.js';
2
2
  import { requireOptional } from '../utils/npm-ops.js';
3
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
3
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
4
4
  import { formatItemsAsText } from '../core/message/items-formatter.js';
5
5
  // ── Webhook SSRF validation ────────────────────────────────────────────────────
6
6
  const WEBHOOK_RE = /^https:\/\/(api|oapi)\.dingtalk\.com\//;
@@ -420,145 +420,82 @@ function isValidCredential(value) {
420
420
  }
421
421
  export class DingtalkChannelPlugin {
422
422
  name = 'dingtalk';
423
- isEnabled(config) {
424
- const raw = config.channels?.dingtalk;
425
- if (!raw)
426
- return false;
427
- if (Array.isArray(raw)) {
428
- return raw.some(inst => inst.enabled !== false && isValidCredential(inst.clientId) && isValidCredential(inst.clientSecret));
429
- }
430
- if (raw.enabled === false)
431
- return false;
432
- return isValidCredential(raw.clientId) && isValidCredential(raw.clientSecret);
433
- }
434
- async createChannels(config) {
435
- const instances = normalizeChannelInstances(config.channels?.dingtalk, 'dingtalk');
436
- const result = [];
437
- for (const inst of instances) {
438
- if (inst.enabled === false)
439
- continue;
440
- if (!isValidCredential(inst.clientId) || !isValidCredential(inst.clientSecret))
441
- continue;
442
- const channel = new DingtalkChannel({
443
- clientId: inst.clientId,
444
- clientSecret: inst.clientSecret,
445
- requireMention: inst.requireMention,
446
- freeResponseChats: inst.freeResponseChats,
447
- });
448
- const adapter = {
449
- channelName: inst.name,
450
- channelKey: inst.name,
451
- capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
452
- send: async (envelope, payload) => {
453
- const ctx = envelope.replyContext;
454
- const channelId = envelope.channelId;
455
- switch (payload.kind) {
456
- case 'result.text':
457
- case 'command.result':
458
- case 'command.error':
459
- case 'system.notice':
460
- case 'system.error':
461
- case 'result.error':
462
- await channel.sendMessage(channelId, payload.text);
463
- return;
464
- case 'result.file':
465
- await channel.sendFile(channelId, payload.filePath);
466
- return;
467
- case 'result.image':
468
- await channel.sendImage(channelId, payload.data);
469
- return;
470
- case 'activity.batch': {
471
- const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
472
- const text = formatItemsAsText(filtered);
473
- if (text)
474
- await channel.sendMessage(channelId, text);
475
- return;
476
- }
477
- case 'interaction':
478
- if (payload.fallbackText)
479
- await channel.sendMessage(channelId, payload.fallbackText);
480
- return;
481
- case 'status.started':
482
- case 'status.completed':
483
- case 'status.interrupted':
484
- case 'status.error':
485
- case 'status.timeout':
486
- case 'status.progress':
487
- case 'custom':
488
- return;
489
- default:
490
- logger.warn(`[DingTalk] Unhandled payload kind: ${payload.kind}`);
423
+ async createInstance(inst, ctx) {
424
+ if (inst.enabled === false)
425
+ return null;
426
+ if (!isValidCredential(inst.clientId) || !isValidCredential(inst.clientSecret))
427
+ return null;
428
+ const channel = new DingtalkChannel({
429
+ clientId: inst.clientId,
430
+ clientSecret: inst.clientSecret,
431
+ requireMention: inst.requireMention,
432
+ freeResponseChats: inst.freeResponseChats,
433
+ });
434
+ const mode = resolveShowActivities(inst);
435
+ const adapter = {
436
+ channelName: inst.name,
437
+ channelKey: inst.name,
438
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
439
+ send: async (envelope, payload) => {
440
+ const channelId = envelope.channelId;
441
+ switch (payload.kind) {
442
+ case 'result.text':
443
+ case 'command.result':
444
+ case 'command.error':
445
+ case 'system.notice':
446
+ case 'system.error':
447
+ case 'result.error':
448
+ await channel.sendMessage(channelId, payload.text);
449
+ return;
450
+ case 'result.file':
451
+ await channel.sendFile(channelId, payload.filePath);
452
+ return;
453
+ case 'result.image':
454
+ await channel.sendImage(channelId, payload.data);
455
+ return;
456
+ case 'activity.batch': {
457
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
458
+ const text = formatItemsAsText(filtered);
459
+ if (text)
460
+ await channel.sendMessage(channelId, text);
461
+ return;
491
462
  }
492
- },
493
- };
494
- const policy = {
495
- canSwitchProject: (_chatType, identity) => identity === 'owner' || identity === 'admin',
496
- canListProjects: (_chatType, identity) => identity === 'owner' || identity === 'admin',
497
- canCreateSession: () => true,
498
- canDeleteSession: () => true,
499
- canImportCliSession: (_chatType, identity) => identity === 'owner' || identity === 'admin',
500
- messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
501
- showMiddleResult: (chatType, identity) => {
502
- const mode = getChannelShowActivities(config, inst.name);
503
- if (mode === 'none')
504
- return false;
505
- if (mode === 'dm-only')
506
- return chatType === 'private';
507
- if (mode === 'owner-dm-only')
508
- return chatType === 'private' && identity === 'owner';
509
- return true;
510
- },
511
- showIdleMonitor: (chatType, identity) => {
512
- const mode = getChannelShowActivities(config, inst.name);
513
- if (mode === 'none')
514
- return false;
515
- if (mode === 'dm-only')
516
- return chatType === 'private';
517
- if (mode === 'owner-dm-only')
518
- return chatType === 'private' && identity === 'owner';
519
- return true;
520
- },
521
- accumulateErrors: () => true,
522
- };
523
- const options = {
524
- fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
525
- supportsImages: true,
526
- flushDelay: inst.flushDelay,
527
- };
528
- result.push({
529
- channelType: 'dingtalk',
530
- adapter,
531
- channel,
532
- policy,
533
- options,
534
- connect: () => channel.connect(),
535
- disconnect: () => channel.disconnect(),
536
- onProjectPathRequest: () => Promise.resolve(config.projects?.defaultPath || process.cwd()),
537
- registerBridge(bridge, channelType) {
538
- bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
539
- await handler({
540
- channel: adapter.channelName,
541
- channelType,
542
- channelId: event.channelId,
543
- selfAID: inst.agentName,
544
- content: event.content,
545
- images: event.images,
546
- chatType: event.chatType || 'private',
547
- peerId: event.peerId || '',
548
- peerName: event.peerName,
549
- messageId: event.messageId,
550
- });
551
- }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
552
- },
553
- });
554
- }
555
- return result;
556
- }
557
- async createChannel(config) {
558
- const instances = await this.createChannels(config);
559
- if (instances.length === 0) {
560
- throw new Error('DingTalk config missing or invalid');
561
- }
562
- return instances[0];
463
+ case 'interaction':
464
+ if (payload.fallbackText)
465
+ await channel.sendMessage(channelId, payload.fallbackText);
466
+ return;
467
+ default: return;
468
+ }
469
+ },
470
+ };
471
+ const policy = {
472
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
473
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
474
+ canCreateSession: () => true,
475
+ canDeleteSession: () => true,
476
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
477
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
478
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
479
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
480
+ accumulateErrors: () => true,
481
+ };
482
+ return {
483
+ channelType: 'dingtalk', adapter, channel,
484
+ policy,
485
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
486
+ connect: () => channel.connect(),
487
+ disconnect: () => channel.disconnect(),
488
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
489
+ registerBridge(bridge, channelType) {
490
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async (event) => {
491
+ await handler({
492
+ channel: adapter.channelName, channelType, channelId: event.channelId,
493
+ selfAID: ctx.agentName, content: event.content, images: event.images,
494
+ chatType: event.chatType || 'private', peerId: event.peerId || '',
495
+ peerName: event.peerName, messageId: event.messageId,
496
+ });
497
+ }), (channelId, text) => channel.sendMessage(channelId, text), adapter, channelType);
498
+ },
499
+ };
563
500
  }
564
501
  }
@@ -207,7 +207,7 @@ export class FeishuChannel {
207
207
  // 清理残留的 mention 占位符(@_user_N 代表机器人)
208
208
  content = content.replace(/@_user_\d+/g, '').trim();
209
209
  const finalContent = quotedText + content;
210
- await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, peerId, peerName, messageId: msg.message_id, mentions: mentions.length > 0 ? mentions : undefined, threadId, rootId, chatType });
210
+ await this.messageHandler({ channelId: msg.chat_id, content: finalContent, images: quotedImages.length > 0 ? quotedImages : undefined, peerId, peerName, messageId: msg.message_id, mentions: mentions.length > 0 ? mentions : undefined, mentionAids: mentions.length > 0 ? mentions.map((m) => m.userId) : undefined, threadId, rootId, chatType });
211
211
  }
212
212
  // 处理图片消息
213
213
  else if (msg.message_type === 'image') {
@@ -1389,159 +1389,106 @@ export function hasMarkdownSyntax(text) {
1389
1389
  ];
1390
1390
  return markdownPatterns.some(pattern => pattern.test(text));
1391
1391
  }
1392
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
1392
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
1393
1393
  import { resolvePaths } from '../paths.js';
1394
1394
  export class FeishuChannelPlugin {
1395
1395
  name = 'feishu';
1396
- isEnabled(config) {
1397
- const raw = config.channels?.feishu;
1398
- if (!raw)
1399
- return false;
1400
- if (Array.isArray(raw)) {
1401
- return raw.some(inst => inst.enabled !== false && inst.appId && inst.appSecret);
1402
- }
1403
- if (raw.enabled === false)
1404
- return false;
1405
- return !!(raw.appId && raw.appSecret);
1406
- }
1407
- async createChannels(config) {
1408
- const instances = normalizeChannelInstances(config.channels?.feishu, 'feishu');
1409
- const result = [];
1410
- for (const inst of instances) {
1411
- if (inst.enabled === false || !inst.appId || !inst.appSecret)
1412
- continue;
1413
- const channel = new FeishuChannel({
1414
- appId: inst.appId,
1415
- appSecret: inst.appSecret,
1416
- enableRichContent: config.enableRichContent,
1417
- seenMsgFile: path.join(resolvePaths().dataDir, `feishu-seen-${inst.name}.jsonl`),
1418
- });
1419
- const adapter = {
1420
- channelName: inst.name,
1421
- channelKey: inst.name,
1422
- capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true, thread: true },
1423
- send: async (envelope, payload) => {
1424
- const ctx = envelope.replyContext;
1425
- const channelId = envelope.channelId;
1426
- switch (payload.kind) {
1427
- case 'result.text':
1428
- case 'command.result':
1429
- case 'command.error':
1430
- case 'system.notice':
1431
- case 'system.error':
1432
- case 'result.error': {
1433
- const sendCtx = { ...(ctx ?? {}) };
1434
- if (payload.kind === 'result.text' && payload.isFinal)
1435
- sendCtx.title = '✅ 最终回复:';
1436
- if (ctx?.metadata?.onThreadCreated)
1437
- sendCtx.onThreadCreated = ctx.metadata.onThreadCreated;
1438
- await channel.sendMessage(channelId, payload.text, sendCtx);
1439
- return;
1440
- }
1441
- case 'result.file':
1442
- await channel.sendFile(channelId, payload.filePath, ctx);
1443
- return;
1444
- case 'result.image':
1445
- await channel.sendImage(channelId, payload.data, ctx);
1446
- return;
1447
- case 'activity.batch': {
1448
- // Feishu 不发送成功的 tool_result(信息密度低,刷屏)
1449
- const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
1450
- const text = formatItemsAsText(filtered);
1451
- if (text) {
1452
- await channel.sendMessage(channelId, text, ctx);
1453
- }
1454
- return;
1455
- }
1456
- case 'status.started':
1457
- case 'status.completed':
1458
- case 'status.interrupted':
1459
- case 'status.error':
1460
- case 'status.timeout':
1461
- case 'status.progress':
1462
- // Feishu 通过 acknowledge (✓ 表情) 表达状态,由 channel 自行处理
1463
- return;
1464
- case 'interaction': {
1465
- const sent = await channel.sendInteraction(channelId, payload.interaction, ctx);
1466
- if (!sent)
1467
- throw new Error('sendInteraction returned false');
1468
- return;
1469
- }
1470
- case 'custom':
1471
- // Feishu 不支持自定义 payload
1472
- return;
1473
- default:
1474
- logger.warn(`[Feishu] Unhandled payload kind: ${payload.kind}`);
1396
+ async createInstance(inst, ctx) {
1397
+ if (inst.enabled === false || !inst.appId || !inst.appSecret)
1398
+ return null;
1399
+ const channel = new FeishuChannel({
1400
+ appId: inst.appId,
1401
+ appSecret: inst.appSecret,
1402
+ enableRichContent: ctx.enableRichContent,
1403
+ seenMsgFile: path.join(resolvePaths().dataDir, `feishu-seen-${inst.name}.jsonl`),
1404
+ });
1405
+ const mode = resolveShowActivities(inst);
1406
+ const adapter = {
1407
+ channelName: inst.name,
1408
+ channelKey: inst.name,
1409
+ capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true, thread: true },
1410
+ send: async (envelope, payload) => {
1411
+ const replyCtx = envelope.replyContext;
1412
+ const channelId = envelope.channelId;
1413
+ switch (payload.kind) {
1414
+ case 'result.text':
1415
+ case 'command.result':
1416
+ case 'command.error':
1417
+ case 'system.notice':
1418
+ case 'system.error':
1419
+ case 'result.error': {
1420
+ const sendCtx = { ...(replyCtx ?? {}) };
1421
+ if (payload.kind === 'result.text' && payload.isFinal)
1422
+ sendCtx.title = '✅ 最终回复:';
1423
+ if (replyCtx?.metadata?.onThreadCreated)
1424
+ sendCtx.onThreadCreated = replyCtx.metadata.onThreadCreated;
1425
+ await channel.sendMessage(channelId, payload.text, sendCtx);
1426
+ return;
1475
1427
  }
1476
- }, acknowledge: (messageId) => { channel.addAckReaction(messageId); return Promise.resolve(); }, onInteraction: (callback) => channel.onInteraction(callback),
1477
- };
1478
- const policy = {
1479
- canSwitchProject: (chatType, identity) => identity === 'owner' || identity === 'admin',
1480
- canListProjects: (chatType, identity) => identity === 'owner' || identity === 'admin',
1481
- canCreateSession: (chatType, identity) => true,
1482
- canDeleteSession: (chatType, identity) => true,
1483
- canImportCliSession: (chatType, identity) => identity === 'owner' || identity === 'admin',
1484
- messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
1485
- showMiddleResult: (chatType, identity) => {
1486
- const mode = getChannelShowActivities(config, inst.name);
1487
- if (mode === 'none')
1488
- return false;
1489
- if (mode === 'dm-only')
1490
- return chatType === 'private';
1491
- if (mode === 'owner-dm-only')
1492
- return chatType === 'private' && identity === 'owner';
1493
- return true;
1494
- },
1495
- showIdleMonitor: (chatType, identity) => {
1496
- const mode = getChannelShowActivities(config, inst.name);
1497
- if (mode === 'none')
1498
- return false;
1499
- if (mode === 'dm-only')
1500
- return chatType === 'private';
1501
- if (mode === 'owner-dm-only')
1502
- return chatType === 'private' && identity === 'owner';
1503
- return true;
1504
- },
1505
- accumulateErrors: (chatType, identity) => true,
1506
- };
1507
- const options = {
1508
- fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g,
1509
- supportsImages: true,
1510
- flushDelay: inst.flushDelay,
1511
- };
1512
- result.push({
1513
- channelType: 'feishu',
1514
- adapter,
1515
- channel,
1516
- policy,
1517
- options,
1518
- connect: () => channel.connect(),
1519
- disconnect: () => channel.disconnect(),
1520
- onProjectPathRequest: (channelId) => Promise.resolve(config.projects?.defaultPath || process.cwd()),
1521
- registerBridge(bridge, channelType) {
1522
- bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, threadId, rootId, chatType, source }) => {
1523
- await handler({
1524
- channel: adapter.channelName, channelType, channelId: chatId, content, images,
1525
- selfAID: inst.agentName,
1526
- chatType: chatType || 'private',
1527
- peerId: peerId || '', peerName, messageId, mentions, threadId,
1528
- replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
1529
- source,
1530
- });
1531
- }), (channelId, text, replyContext) => channel.sendMessage(channelId, text, {
1532
- replyToMessageId: replyContext?.replyToMessageId,
1533
- replyInThread: replyContext?.replyInThread,
1534
- }), adapter, channelType);
1535
- },
1536
- });
1537
- }
1538
- return result;
1539
- }
1540
- async createChannel(config) {
1541
- const instances = await this.createChannels(config);
1542
- if (instances.length === 0) {
1543
- throw new Error('Feishu config missing');
1544
- }
1545
- return instances[0];
1428
+ case 'result.file':
1429
+ await channel.sendFile(channelId, payload.filePath, replyCtx);
1430
+ return;
1431
+ case 'result.image':
1432
+ await channel.sendImage(channelId, payload.data, replyCtx);
1433
+ return;
1434
+ case 'activity.batch': {
1435
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
1436
+ const text = formatItemsAsText(filtered);
1437
+ if (text)
1438
+ await channel.sendMessage(channelId, text, replyCtx);
1439
+ return;
1440
+ }
1441
+ case 'status.started':
1442
+ case 'status.completed':
1443
+ case 'status.interrupted':
1444
+ case 'status.error':
1445
+ case 'status.timeout':
1446
+ case 'status.progress': return;
1447
+ case 'interaction': {
1448
+ const sent = await channel.sendInteraction(channelId, payload.interaction, replyCtx);
1449
+ if (!sent)
1450
+ throw new Error('sendInteraction returned false');
1451
+ return;
1452
+ }
1453
+ case 'custom': return;
1454
+ default: logger.warn(`[Feishu] Unhandled payload kind: ${payload.kind}`);
1455
+ }
1456
+ },
1457
+ acknowledge: (messageId) => { channel.addAckReaction(messageId); return Promise.resolve(); },
1458
+ onInteraction: (callback) => channel.onInteraction(callback),
1459
+ };
1460
+ const policy = {
1461
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
1462
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
1463
+ canCreateSession: () => true,
1464
+ canDeleteSession: () => true,
1465
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
1466
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
1467
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
1468
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
1469
+ accumulateErrors: () => true,
1470
+ };
1471
+ return {
1472
+ channelType: 'feishu', adapter, channel,
1473
+ policy,
1474
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
1475
+ connect: () => channel.connect(),
1476
+ disconnect: () => channel.disconnect(),
1477
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
1478
+ registerBridge(bridge, channelType) {
1479
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, mentionAids, threadId, rootId, chatType, source }) => {
1480
+ await handler({
1481
+ channel: adapter.channelName, channelType, channelId: chatId, content, images,
1482
+ selfAID: ctx.agentName, chatType: chatType || 'private',
1483
+ peerId: peerId || '', peerName, messageId, mentions, mentionAids, threadId,
1484
+ replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
1485
+ source,
1486
+ });
1487
+ }), (channelId, text, replyContext) => channel.sendMessage(channelId, text, {
1488
+ replyToMessageId: replyContext?.replyToMessageId,
1489
+ replyInThread: replyContext?.replyInThread,
1490
+ }), adapter, channelType);
1491
+ },
1492
+ };
1546
1493
  }
1547
1494
  }