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,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
  }
@@ -365,6 +365,8 @@ export class FeishuChannel {
365
365
  }
366
366
  catch (err) {
367
367
  logger.error('[Feishu] Failed to handle card action:', err);
368
+ const detail = err instanceof Error && err.message ? err.message : String(err || '未知错误');
369
+ return { toast: { type: 'error', content: `❌ 操作失败: ${detail}` } };
368
370
  }
369
371
  },
370
372
  });
@@ -1038,15 +1040,36 @@ export class FeishuChannel {
1038
1040
  return false;
1039
1041
  }
1040
1042
  }
1041
- addAckReaction(messageId) {
1043
+ // messageId → Pin reaction create promise(等待 reaction_id 落地,供 promoteAck 删除)
1044
+ pinReactions = new Map();
1045
+ /** 收到消息时添加 Pin 表情(表示"已收到,排队中") */
1046
+ addPinReaction(messageId) {
1042
1047
  if (!this.client)
1043
1048
  return;
1049
+ const p = this.client.im.messageReaction.create({
1050
+ path: { message_id: messageId },
1051
+ data: { reaction_type: { emoji_type: 'Pin' } },
1052
+ }).then((res) => res?.data?.reaction_id)
1053
+ .catch(() => undefined);
1054
+ this.pinReactions.set(messageId, p);
1055
+ }
1056
+ /** Runner 开始执行时:添加 CheckMark,然后异步移除 Pin(视觉无闪烁) */
1057
+ async promoteAckReaction(messageId) {
1058
+ if (!this.client)
1059
+ return;
1060
+ // 先加 CheckMark,再删 Pin——用户看到的是 Pin→Pin+CheckMark→CheckMark,无空窗
1044
1061
  this.client.im.messageReaction.create({
1045
1062
  path: { message_id: messageId },
1046
- data: {
1047
- reaction_type: { emoji_type: 'CheckMark' }
1048
- }
1063
+ data: { reaction_type: { emoji_type: 'CheckMark' } },
1049
1064
  }).catch(() => { });
1065
+ const pending = this.pinReactions.get(messageId);
1066
+ this.pinReactions.delete(messageId);
1067
+ const reactionId = await pending;
1068
+ if (reactionId) {
1069
+ this.client.im.messageReaction.delete({
1070
+ path: { message_id: messageId, reaction_id: reactionId },
1071
+ }).catch(() => { });
1072
+ }
1050
1073
  }
1051
1074
  }
1052
1075
  export class CardMetaStore {
@@ -1389,159 +1412,107 @@ export function hasMarkdownSyntax(text) {
1389
1412
  ];
1390
1413
  return markdownPatterns.some(pattern => pattern.test(text));
1391
1414
  }
1392
- import { normalizeChannelInstances, getChannelShowActivities } from '../utils/channel-helpers.js';
1415
+ import { resolveShowActivities, showActivitiesPolicy } from '../core/channel-loader.js';
1393
1416
  import { resolvePaths } from '../paths.js';
1394
1417
  export class FeishuChannelPlugin {
1395
1418
  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}`);
1419
+ async createInstance(inst, ctx) {
1420
+ if (inst.enabled === false || !inst.appId || !inst.appSecret)
1421
+ return null;
1422
+ const channel = new FeishuChannel({
1423
+ appId: inst.appId,
1424
+ appSecret: inst.appSecret,
1425
+ enableRichContent: ctx.enableRichContent,
1426
+ seenMsgFile: path.join(resolvePaths().dataDir, `feishu-seen-${inst.name}.jsonl`),
1427
+ });
1428
+ const mode = resolveShowActivities(inst);
1429
+ const adapter = {
1430
+ channelName: inst.name,
1431
+ channelKey: inst.name,
1432
+ capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true, thread: true },
1433
+ send: async (envelope, payload) => {
1434
+ const replyCtx = envelope.replyContext;
1435
+ const channelId = envelope.channelId;
1436
+ switch (payload.kind) {
1437
+ case 'result.text':
1438
+ case 'command.result':
1439
+ case 'command.error':
1440
+ case 'system.notice':
1441
+ case 'system.error':
1442
+ case 'result.error': {
1443
+ const sendCtx = { ...(replyCtx ?? {}) };
1444
+ if (payload.kind === 'result.text' && payload.isFinal)
1445
+ sendCtx.title = '✅ 最终回复:';
1446
+ if (replyCtx?.metadata?.onThreadCreated)
1447
+ sendCtx.onThreadCreated = replyCtx.metadata.onThreadCreated;
1448
+ await channel.sendMessage(channelId, payload.text, sendCtx);
1449
+ return;
1475
1450
  }
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, mentionAids, 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, mentionAids, 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];
1451
+ case 'result.file':
1452
+ await channel.sendFile(channelId, payload.filePath, replyCtx);
1453
+ return;
1454
+ case 'result.image':
1455
+ await channel.sendImage(channelId, payload.data, replyCtx);
1456
+ return;
1457
+ case 'activity.batch': {
1458
+ const filtered = payload.items.filter((i) => !(i.kind === 'tool_result' && i.ok));
1459
+ const text = formatItemsAsText(filtered);
1460
+ if (text)
1461
+ await channel.sendMessage(channelId, text, replyCtx);
1462
+ return;
1463
+ }
1464
+ case 'status.started':
1465
+ case 'status.completed':
1466
+ case 'status.interrupted':
1467
+ case 'status.error':
1468
+ case 'status.timeout':
1469
+ case 'status.progress': return;
1470
+ case 'interaction': {
1471
+ const sent = await channel.sendInteraction(channelId, payload.interaction, replyCtx);
1472
+ if (!sent)
1473
+ throw new Error('sendInteraction returned false');
1474
+ return;
1475
+ }
1476
+ case 'custom': return;
1477
+ default: logger.warn(`[Feishu] Unhandled payload kind: ${payload.kind}`);
1478
+ }
1479
+ },
1480
+ acknowledge: (messageId) => { channel.addPinReaction(messageId); return Promise.resolve(); },
1481
+ promoteAck: (messageId) => channel.promoteAckReaction(messageId),
1482
+ onInteraction: (callback) => channel.onInteraction(callback),
1483
+ };
1484
+ const policy = {
1485
+ canSwitchProject: (_, identity) => identity === 'owner' || identity === 'admin',
1486
+ canListProjects: (_, identity) => identity === 'owner' || identity === 'admin',
1487
+ canCreateSession: () => true,
1488
+ canDeleteSession: () => true,
1489
+ canImportCliSession: (_, identity) => identity === 'owner' || identity === 'admin',
1490
+ messagePrefix: (chatType, peerName) => (chatType === 'group' && peerName) ? `[${peerName}] ` : '',
1491
+ showMiddleResult: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
1492
+ showIdleMonitor: (chatType, identity) => showActivitiesPolicy(mode, chatType, identity),
1493
+ accumulateErrors: () => true,
1494
+ };
1495
+ return {
1496
+ channelType: 'feishu', adapter, channel,
1497
+ policy,
1498
+ options: { fileMarkerPattern: /\[SEND_FILE:(?:(\w+):)?([^\]]+)\]/g, supportsImages: true, flushDelay: inst.flushDelay },
1499
+ connect: () => channel.connect(),
1500
+ disconnect: () => channel.disconnect(),
1501
+ onProjectPathRequest: () => Promise.resolve(ctx.defaultProjectPath),
1502
+ registerBridge(bridge, channelType) {
1503
+ bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, mentionAids, threadId, rootId, chatType, source }) => {
1504
+ await handler({
1505
+ channel: adapter.channelName, channelType, channelId: chatId, content, images,
1506
+ selfAID: ctx.agentName, chatType: chatType || 'private',
1507
+ peerId: peerId || '', peerName, messageId, mentions, mentionAids, threadId,
1508
+ replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
1509
+ source,
1510
+ });
1511
+ }), (channelId, text, replyContext) => channel.sendMessage(channelId, text, {
1512
+ replyToMessageId: replyContext?.replyToMessageId,
1513
+ replyInThread: replyContext?.replyInThread,
1514
+ }), adapter, channelType);
1515
+ },
1516
+ };
1546
1517
  }
1547
1518
  }