evolclaw 3.2.0 → 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 (83) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -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/store.js +1 -1
  10. package/dist/aun/storage/download.js +1 -1
  11. package/dist/aun/storage/upload.js +13 -1
  12. package/dist/channels/aun.js +406 -293
  13. package/dist/channels/dingtalk.js +77 -140
  14. package/dist/channels/feishu.js +97 -150
  15. package/dist/channels/qqbot.js +75 -138
  16. package/dist/channels/wechat.js +75 -136
  17. package/dist/channels/wecom.js +75 -138
  18. package/dist/cli/agent.js +8 -5
  19. package/dist/cli/index.js +177 -44
  20. package/dist/cli/init.js +33 -6
  21. package/dist/cli/model.js +1 -1
  22. package/dist/cli/stats.js +558 -0
  23. package/dist/cli/version.js +87 -0
  24. package/dist/cli/watch-msg.js +5 -2
  25. package/dist/config-store.js +12 -6
  26. package/dist/core/channel-loader.js +84 -82
  27. package/dist/core/command-handler.js +473 -114
  28. package/dist/core/evolagent-registry.js +1 -0
  29. package/dist/core/evolagent.js +1 -1
  30. package/dist/core/interaction-router.js +8 -0
  31. package/dist/core/message/command-handler-agent-control.js +63 -1
  32. package/dist/core/message/im-renderer.js +35 -13
  33. package/dist/core/message/items-formatter.js +9 -1
  34. package/dist/core/message/message-bridge.js +49 -21
  35. package/dist/core/message/message-log.js +1 -0
  36. package/dist/core/message/message-processor.js +295 -35
  37. package/dist/core/message/message-queue.js +2 -2
  38. package/dist/core/message/pending-hints.js +232 -0
  39. package/dist/core/message/response-depth.js +56 -0
  40. package/dist/core/model/model-catalog.js +1 -1
  41. package/dist/core/model/model-scope.js +2 -2
  42. package/dist/core/permission.js +9 -12
  43. package/dist/core/relation/peer-identity.js +16 -1
  44. package/dist/core/session/adapters/codex-session-file-adapter.js +4 -2
  45. package/dist/core/session/session-manager.js +27 -13
  46. package/dist/core/session/session-title.js +26 -0
  47. package/dist/core/stats/billing.js +151 -0
  48. package/dist/core/stats/budget.js +93 -0
  49. package/dist/core/stats/db.js +314 -0
  50. package/dist/core/stats/eck-vars.js +84 -0
  51. package/dist/core/stats/index.js +10 -0
  52. package/dist/core/stats/normalizer.js +78 -0
  53. package/dist/core/stats/query.js +760 -0
  54. package/dist/core/stats/writer.js +115 -0
  55. package/dist/core/trigger/manager.js +34 -0
  56. package/dist/core/trigger/parser.js +9 -3
  57. package/dist/core/trigger/scheduler.js +20 -17
  58. package/dist/{agents → eck}/manifest-engine.js +20 -1
  59. package/dist/{agents → eck}/message-renderer.js +24 -1
  60. package/dist/index.js +130 -8
  61. package/dist/ipc.js +17 -1
  62. package/dist/utils/cross-platform.js +23 -5
  63. package/dist/utils/ecweb-pair.js +20 -0
  64. package/dist/utils/stats.js +14 -0
  65. package/kits/docs/evolclaw/INDEX.md +3 -1
  66. package/kits/docs/evolclaw/fs-architecture.md +1215 -0
  67. package/kits/docs/evolclaw/fs.md +131 -0
  68. package/kits/docs/evolclaw/group-fs.md +209 -0
  69. package/kits/docs/evolclaw/stats.md +70 -0
  70. package/kits/docs/venues/aun-group.md +29 -6
  71. package/kits/docs/venues/group.md +5 -4
  72. package/kits/eck_manifest.json +12 -0
  73. package/kits/eck_message_manifest.json +30 -3
  74. package/kits/rules/05-venue.md +1 -1
  75. package/kits/templates/message-fragments/inject-default.md +2 -0
  76. package/kits/templates/system-fragments/response-depth.md +16 -0
  77. package/package.json +4 -4
  78. package/dist/agents/baseagent-normalize.js +0 -19
  79. package/dist/core/relation/peer-key.js +0 -16
  80. package/dist/evolclaw-config.js +0 -11
  81. package/dist/utils/channel-helpers.js +0 -46
  82. /package/dist/core/{cache/file-cache.js → daemon-file-cache.js} +0 -0
  83. /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
  }
@@ -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, 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];
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
  }