evolclaw 2.6.0 → 2.6.2

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.
@@ -1,4 +1,4 @@
1
- import { AUNClient, GatewayDiscovery } from '@agentunion/aun-node';
1
+ import { AUNClient, GatewayDiscovery } from '@agentunion/fastaun';
2
2
  import crypto from 'crypto';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
@@ -58,9 +58,16 @@ export class AUNChannel {
58
58
  const logPath = path.join(resolvePaths().logs, `aun-${today}.log`);
59
59
  this.traceStream = fs.createWriteStream(logPath, { flags: 'a' });
60
60
  }
61
- /** 判断 channelId 是否为群组 ID(g-xxx.agentid.pub 或 grp_ 前缀) */
61
+ /** 判断 channelId 是否为群组 ID
62
+ * - 新格式:group.{issuer}/{group_no|group_name}
63
+ * - 数字群号:{group_no}.{issuer}(如 11117.agentid.pub)
64
+ * - 兼容旧格式:grp_xxx、g-xxx.agentid.pub
65
+ */
62
66
  isGroupId(id) {
63
- return id.startsWith('grp_') || (id.startsWith('g-') && id.includes('.'));
67
+ return (id.startsWith('group.') && id.includes('/'))
68
+ || /^\d+\./.test(id)
69
+ || id.startsWith('grp_')
70
+ || (id.startsWith('g-') && id.includes('.'));
64
71
  }
65
72
  getShortAid(aid) {
66
73
  if (!aid)
@@ -114,6 +121,7 @@ export class AUNChannel {
114
121
  this.messageSeqMap.delete(messageId);
115
122
  }
116
123
  _aid;
124
+ _selfName; // 本地 agent.md 中的 name,首次 connect 时读取
117
125
  _chatId = ''; // aid:device_id:slot_id — 多实例回声过滤
118
126
  seenMessages = new Map();
119
127
  peerInfoCache = new Map();
@@ -262,6 +270,7 @@ export class AUNChannel {
262
270
  this._aid = this.client.aid ?? undefined;
263
271
  const deviceId = this.client._device_id ?? '';
264
272
  this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
273
+ this._selfName = this.loadSelfName(aidName);
265
274
  this.connected = true;
266
275
  this.reconnectAttempt = 0;
267
276
  // Workaround: SDK e2ee uses _identity.cert for sender_cert_fingerprint;
@@ -317,7 +326,8 @@ export class AUNChannel {
317
326
  if (ownerInfo.type !== null && ownerInfo.type !== 'human') {
318
327
  logger.warn(`[AUN] Owner ${owner} type is "${ownerInfo.type}" (not human). Consider using a human AID as owner.`);
319
328
  }
320
- // Name: owner agent.md name (first 12 chars) fallback to owner AID first label (first 12 chars)
329
+ // Name: prefer existing agent.md name if user has customized it,
330
+ // otherwise generate "{ownerName}的Evol助手 ({aidLabel})" for disambiguation
321
331
  const ownerAidClean = owner.startsWith('@') ? owner.slice(1) : owner;
322
332
  let ownerDisplayName;
323
333
  if (ownerInfo.name) {
@@ -326,7 +336,18 @@ export class AUNChannel {
326
336
  else {
327
337
  ownerDisplayName = ownerAidClean.split('.')[0].slice(0, 12);
328
338
  }
329
- const agentDisplayName = `${ownerDisplayName}的Evol助手`;
339
+ // Check if init wrote a meaningful name (vs just the aid first label default)
340
+ const currentNameMatch = frontmatter.match(/^name:\s*"?([^"\n]+)/m);
341
+ const currentName = currentNameMatch?.[1]?.trim();
342
+ const aidLabel = aidName.split('.')[0];
343
+ let agentDisplayName;
344
+ if (currentName && currentName !== aidLabel) {
345
+ // User or previous init set a custom name — keep it
346
+ agentDisplayName = currentName;
347
+ }
348
+ else {
349
+ agentDisplayName = `${ownerDisplayName}的Evol助手 (${aidLabel})`;
350
+ }
330
351
  // Generate new agent.md with proper fields
331
352
  const newAgentMd = `---
332
353
  aid: "${aid}"
@@ -376,7 +397,21 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
376
397
  - 所有命令以 \`/\` 开头
377
398
 
378
399
  现在,请先使用 \`/bind\` 命令绑定您的项目目录,然后就可以开始工作了!`;
379
- await this.sendMessage(owner, welcomeText);
400
+ // First contact with Owner races against Owner's async cert fetch from
401
+ // gateway PKI; a 3s pause lets the cert propagate. persist_required asks
402
+ // the gateway to durably store the message so Owner can recover it via
403
+ // pull if the initial E2EE push still arrives before the cert resolves.
404
+ await new Promise(resolve => setTimeout(resolve, 3000));
405
+ if (!this.client) {
406
+ logger.warn('[AUN] Client disconnected before welcome message could be sent');
407
+ return;
408
+ }
409
+ await this.client.call('message.send', {
410
+ to: owner,
411
+ payload: { type: 'text', text: welcomeText },
412
+ encrypt: true,
413
+ persist_required: true,
414
+ });
380
415
  logger.info(`[AUN] Welcome message sent to owner: ${owner}`);
381
416
  }
382
417
  catch (e) {
@@ -531,11 +566,14 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
531
566
  this.acknowledgeImmediately(messageId, seq);
532
567
  return;
533
568
  }
569
+ // dispatch_mode from server tells agent how to work in this group
570
+ const dispatchMode = msg.dispatch_mode ?? payload?.dispatch_mode ?? 'mention';
534
571
  const mentionedSelf = this._aid
535
572
  ? (this.hasExplicitMention(text, this._aid) || payloadMentions.includes(this._aid))
536
573
  : false;
537
574
  const mentionedAll = this.hasExplicitMention(text, 'all') || payloadMentions.includes('all');
538
- if (!mentionedSelf && !mentionedAll) {
575
+ // In mention mode, only respond when explicitly mentioned; in broadcast mode, respond to all
576
+ if (dispatchMode === 'mention' && !mentionedSelf && !mentionedAll) {
539
577
  this.acknowledgeImmediately(messageId, seq);
540
578
  return;
541
579
  }
@@ -550,7 +588,9 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
550
588
  this.acknowledgeImmediately(messageId, seq);
551
589
  return;
552
590
  }
553
- const mentions = mentionedAll ? ['all'] : (this._aid ? [this._aid] : []);
591
+ const mentions = mentionedAll
592
+ ? ['all']
593
+ : mentionedSelf && this._aid ? [this._aid] : [];
554
594
  // Process attachments
555
595
  let finalText = strippedText;
556
596
  if (hasAttachments && this.client) {
@@ -708,7 +748,8 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
708
748
  const payload = { type: 'text', text: finalText };
709
749
  if (context?.threadId)
710
750
  payload.thread_id = context.threadId;
711
- const params = { payload, encrypt: true };
751
+ const isGroup = this.isGroupId(channelId);
752
+ const params = { payload, encrypt: !isGroup };
712
753
  // Multi-instance routing: channelId may be "aid:device_id:slot_id"
713
754
  const colonIdx = channelId.indexOf(':');
714
755
  const targetAid = colonIdx > 0 ? channelId.substring(0, colonIdx) : channelId;
@@ -716,7 +757,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
716
757
  params.payload.chat_id = channelId;
717
758
  }
718
759
  try {
719
- if (this.isGroupId(channelId)) {
760
+ if (isGroup) {
720
761
  params.group_id = channelId;
721
762
  this.trace('OUT', 'group.send', params);
722
763
  await this.client.call('group.send', params);
@@ -732,6 +773,50 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
732
773
  logger.error(`[AUN] Send failed to ${channelId}: ${e}`);
733
774
  }
734
775
  }
776
+ /**
777
+ * 发送 thought 内容(Proactive 模式可观测)
778
+ * 群聊:调用 group.thought.put
779
+ * 单聊:调用 message.thought.put
780
+ *
781
+ * selector 使用 context: { type: 'task', id: taskId }
782
+ * 存储键:group_id/peer_aid + sender_aid + context.type + context.id
783
+ */
784
+ async sendThought(channelId, taskId, payload) {
785
+ if (!this.connected || !this.client)
786
+ return;
787
+ if (!taskId)
788
+ return;
789
+ // Multi-instance routing
790
+ const colonIdx = channelId.indexOf(':');
791
+ const targetId = colonIdx > 0 ? channelId.substring(0, colonIdx) : channelId;
792
+ const params = {
793
+ context: { type: 'task', id: taskId },
794
+ payload,
795
+ encrypt: true,
796
+ };
797
+ try {
798
+ if (this.isGroupId(channelId)) {
799
+ params.group_id = targetId;
800
+ this.trace('OUT', 'group.thought.put', params);
801
+ await this.client.call('group.thought.put', params);
802
+ }
803
+ else {
804
+ params.to = targetId;
805
+ this.trace('OUT', 'message.thought.put', params);
806
+ await this.client.call('message.thought.put', params);
807
+ }
808
+ }
809
+ catch (e) {
810
+ const err = e;
811
+ this.trace('OUT', 'thought.put.error', {
812
+ channelId,
813
+ errorName: err?.name,
814
+ errorCode: err?.code,
815
+ errorMessage: err?.message,
816
+ });
817
+ logger.debug(`[AUN] thought.put failed to ${channelId}: ${err?.name}(${err?.code})=${err?.message}`);
818
+ }
819
+ }
735
820
  async sendFile(channelId, filePath, context) {
736
821
  if (!this.connected || !this.client) {
737
822
  logger.warn('[AUN] Cannot sendFile: not connected');
@@ -805,14 +890,15 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
805
890
  };
806
891
  if (context?.threadId)
807
892
  filePayload.thread_id = context.threadId;
808
- const params = { payload: filePayload, encrypt: true };
893
+ const isGroup = this.isGroupId(channelId);
894
+ const params = { payload: filePayload, encrypt: !isGroup };
809
895
  // Multi-instance routing
810
896
  const fileColonIdx = channelId.indexOf(':');
811
897
  const fileTargetAid = fileColonIdx > 0 ? channelId.substring(0, fileColonIdx) : channelId;
812
898
  if (fileColonIdx > 0) {
813
899
  params.payload.chat_id = channelId;
814
900
  }
815
- if (this.isGroupId(channelId)) {
901
+ if (isGroup) {
816
902
  params.group_id = channelId;
817
903
  this.trace('OUT', 'group.send.file', params);
818
904
  await this.client.call('group.send', params);
@@ -834,7 +920,7 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
834
920
  // to avoid duplicate "已送达" at the sender CLI
835
921
  this.messageSeqMap.delete(messageId);
836
922
  }
837
- sendProcessingStatus(channelId, status, sessionId, context) {
923
+ sendProcessingStatus(channelId, status, sessionId, taskId, context) {
838
924
  if (status === 'start')
839
925
  this.sentCount.delete(channelId); // 新任务开始,重置计数
840
926
  if (!this.client || !this.connected)
@@ -849,19 +935,20 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
849
935
  const payload = {
850
936
  type: 'event',
851
937
  event: eventMap[status] ?? `task.${status}`,
852
- data: { session_id: sessionId },
938
+ data: { task_id: taskId, session_id: sessionId },
853
939
  severity: status === 'error' || status === 'timeout' ? 'error' : 'info',
854
940
  };
855
941
  if (context?.threadId)
856
942
  payload.thread_id = context.threadId;
857
- const params = { payload, encrypt: true };
943
+ const isGroup = this.isGroupId(channelId);
944
+ const params = { payload, encrypt: !isGroup };
858
945
  // Multi-instance routing
859
946
  const statusColonIdx = channelId.indexOf(':');
860
947
  const statusTargetAid = statusColonIdx > 0 ? channelId.substring(0, statusColonIdx) : channelId;
861
948
  if (statusColonIdx > 0) {
862
949
  payload.chat_id = channelId;
863
950
  }
864
- if (this.isGroupId(channelId)) {
951
+ if (isGroup) {
865
952
  params.group_id = channelId;
866
953
  this.trace('OUT', 'group.send.status', params);
867
954
  this.client.call('group.send', params).catch(e => {
@@ -983,6 +1070,27 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
983
1070
  maxAttempts: AUNChannel.RECONNECT_DELAYS.length,
984
1071
  };
985
1072
  }
1073
+ /** 读取本地 agent.md 中的 name(用于身份上下文展示) */
1074
+ loadSelfName(aid) {
1075
+ try {
1076
+ const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
1077
+ const agentMdPath = path.join(os.homedir(), '.aun', 'AIDs', aidName, 'agent.md');
1078
+ if (!fs.existsSync(agentMdPath))
1079
+ return undefined;
1080
+ const content = fs.readFileSync(agentMdPath, 'utf-8');
1081
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
1082
+ if (!fmMatch)
1083
+ return undefined;
1084
+ const nameMatch = fmMatch[1].match(/^name:\s*["']?(.+?)["']?\s*$/m);
1085
+ return nameMatch?.[1]?.trim() || undefined;
1086
+ }
1087
+ catch {
1088
+ return undefined;
1089
+ }
1090
+ }
1091
+ getSelfName() {
1092
+ return this._selfName;
1093
+ }
986
1094
  async fetchPeerInfo(aid) {
987
1095
  const cached = this.peerInfoCache.get(aid);
988
1096
  if (cached !== undefined)
@@ -1048,11 +1156,13 @@ export class AUNChannelPlugin {
1048
1156
  sendText: (id, text, context) => channel.sendMessage(id, text, context),
1049
1157
  sendFile: (id, filePath, context) => channel.sendFile(id, filePath, context),
1050
1158
  acknowledge: (messageId) => { channel.acknowledge(messageId); return Promise.resolve(); },
1051
- sendProcessingStatus: (id, status, sessionId, context) => channel.sendProcessingStatus(id, status, sessionId, context),
1159
+ sendProcessingStatus: (id, status, sessionId, taskId, context) => channel.sendProcessingStatus(id, status, sessionId, taskId, context),
1052
1160
  sendCustomPayload: (id, payload) => channel.sendCustomPayload(id, payload),
1053
1161
  uploadAgentMd: (content) => channel.uploadAgentMd(content),
1054
1162
  downloadAgentMd: (aid) => channel.downloadAgentMd(aid),
1163
+ putThought: (id, taskId, payload) => channel.sendThought(id, taskId, payload),
1055
1164
  _selfAid: () => channel.getStatus().aid,
1165
+ _selfName: () => channel.getSelfName(),
1056
1166
  };
1057
1167
  const policy = {
1058
1168
  canSwitchProject: (chatType, identity) => identity === 'owner' || identity === 'admin',
@@ -499,11 +499,12 @@ export class FeishuChannel {
499
499
  return;
500
500
  try {
501
501
  // 检测是否为图片,是则走 sendImage(内联预览)而非文件卡片
502
- const header = Buffer.alloc(12);
502
+ // 读取足够字节供 file-type 解析(ZIP-based 格式如 PPTX 需要更多字节)
503
+ const header = Buffer.alloc(4100);
503
504
  const fd = fs.openSync(filePath, 'r');
504
- fs.readSync(fd, header, 0, 12, 0);
505
+ const bytesRead = fs.readSync(fd, header, 0, 4100, 0);
505
506
  fs.closeSync(fd);
506
- const imgType = await imageType(header);
507
+ const imgType = await imageType(header.subarray(0, bytesRead)).catch(() => undefined);
507
508
  if (imgType) {
508
509
  logger.info(`[Feishu] Detected image (${imgType.mime}), sending as inline image:`, filePath);
509
510
  const buf = fs.readFileSync(filePath);
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import { ipcQuery } from './ipc.js';
12
12
  import { cmdInitWechat, cmdInitFeishu, cmdInitAun, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom } from './utils/init-channel.js';
13
13
  import * as platform from './utils/cross-platform.js';
14
14
  import { EventBus } from './core/event-bus.js';
15
+ import { tryUpgrade } from './utils/upgrade.js';
15
16
  // Suppress Node.js ExperimentalWarning (e.g. SQLite) from cluttering CLI output
16
17
  process.removeAllListeners('warning');
17
18
  process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
@@ -372,6 +373,25 @@ async function cmdStop() {
372
373
  async function cmdRestart() {
373
374
  console.log('🔄 Restarting EvolClaw...');
374
375
  const p = resolvePaths();
376
+ // 版本检查与自动升级
377
+ console.log('📦 Checking for updates...');
378
+ const upgrade = await tryUpgrade();
379
+ switch (upgrade.status) {
380
+ case 'upgraded':
381
+ console.log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
382
+ break;
383
+ case 'no-update':
384
+ console.log(`✓ Already up to date (${upgrade.from})`);
385
+ break;
386
+ case 'skipped':
387
+ console.log(upgrade.error
388
+ ? '⏭ Skipped upgrade (network unavailable)'
389
+ : '⏭ Skipped upgrade check (dev mode)');
390
+ break;
391
+ case 'failed':
392
+ console.log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}), continuing with current version`);
393
+ break;
394
+ }
375
395
  await stopAndWait(p.pid);
376
396
  setTimeout(() => cmdStart(), 1000);
377
397
  }
@@ -737,6 +757,27 @@ async function cmdRestartMonitor() {
737
757
  });
738
758
  await sleep(3000);
739
759
  }
760
+ // 版本检查与自动升级
761
+ log('Checking for updates...');
762
+ const upgrade = await tryUpgrade();
763
+ switch (upgrade.status) {
764
+ case 'upgraded':
765
+ log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
766
+ await notifyChannel(p, pendingInfo, `📦 已升级 ${upgrade.from} → ${upgrade.to}`, log);
767
+ break;
768
+ case 'no-update':
769
+ log(`Already up to date (${upgrade.from})`);
770
+ break;
771
+ case 'skipped':
772
+ log(upgrade.error
773
+ ? 'Skipped upgrade (network unavailable)'
774
+ : 'Skipped upgrade check (dev mode)');
775
+ break;
776
+ case 'failed':
777
+ log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}): ${upgrade.error}`);
778
+ await notifyChannel(p, pendingInfo, `⚠️ 升级失败,使用当前版本继续`, log);
779
+ break;
780
+ }
740
781
  // 启动并检测 ready signal
741
782
  let started = await spawnAndWaitReady(p, log, READY_TIMEOUT);
742
783
  if (started) {
@@ -1244,14 +1285,43 @@ async function cmdDiagnose() {
1244
1285
  // ==================== Ctl ====================
1245
1286
  async function cmdCtl(args) {
1246
1287
  if (args.length === 0) {
1247
- console.error('用法: evolclaw ctl <command> [args...]');
1248
- console.error('示例: evolclaw ctl model sonnet');
1249
- console.error(' evolclaw ctl status');
1250
- console.error(' evolclaw ctl effort high');
1251
- console.error(' evolclaw ctl send "<消息内容>" # proactive 模式主动发消息');
1252
- console.error(' evolclaw ctl chatmode proactive # 切换会话模式');
1288
+ console.error(`用法: evolclaw ctl <command> [args...]
1289
+
1290
+ 查询:
1291
+ status 查看会话状态
1292
+ check 检查渠道健康状态
1293
+ help 显示帮助
1294
+
1295
+ 配置:
1296
+ model [model-id] 查看/切换模型(如 opus, sonnet, haiku)
1297
+ effort [low|medium|high] 查看/切换推理强度
1298
+ compact 压缩当前会话上下文
1299
+ chatmode [mode] 查看/切换会话模式
1300
+ activity [all|dm|owner|none] 查看/控制中间输出显示模式
1301
+ perm [mode] 查看/切换权限模式
1302
+
1303
+ 项目:
1304
+ bind <path> 注册项目目录(不切换当前会话)
1305
+
1306
+ 消息:
1307
+ send <消息内容> 主动发送文本消息(proactive 模式)
1308
+ file [channel] <path> 发送项目内文件
1309
+
1310
+ 运维:
1311
+ agentmd [put|set <内容>] 查看/管理 agent.md(仅 AUN 通道)
1312
+ restart [channel] 重启服务或重连指定渠道
1313
+
1314
+ 示例:
1315
+ evolclaw ctl model sonnet
1316
+ evolclaw ctl effort high
1317
+ evolclaw ctl compact
1318
+ evolclaw ctl chatmode proactive`);
1253
1319
  process.exit(1);
1254
1320
  }
1321
+ // help 不需要连接服务,直接复用无参数时的帮助输出
1322
+ if (args[0] === 'help') {
1323
+ return cmdCtl([]);
1324
+ }
1255
1325
  const sessionId = process.env.EVOLCLAW_SESSION_ID;
1256
1326
  if (!sessionId) {
1257
1327
  console.error('错误: EVOLCLAW_SESSION_ID 未设置(仅在 evolclaw 托管环境中可用)');
@@ -1294,7 +1364,32 @@ export async function main(args) {
1294
1364
  }
1295
1365
  switch (cmd) {
1296
1366
  case 'init':
1297
- if (args[1] === 'wechat') {
1367
+ if (args[1] === 'help') {
1368
+ console.log(`用法: evolclaw init [渠道] [选项]
1369
+
1370
+ 交互式初始化:
1371
+ evolclaw init 创建基础配置文件(交互式)
1372
+ evolclaw init feishu 飞书扫码登录并写入配置
1373
+ evolclaw init wechat 微信扫码登录并写入配置
1374
+ evolclaw init dingtalk 钉钉扫码登录并写入配置
1375
+ evolclaw init qqbot QQ 机器人扫码绑定并写入配置
1376
+ evolclaw init wecom 企业微信 AI Bot 配置(手动输入)
1377
+ evolclaw init aun AUN 交互式配置(AID 创建 + Owner 绑定)
1378
+
1379
+ 非交互式初始化:
1380
+ evolclaw init --non-interactive [选项]
1381
+
1382
+ 选项:
1383
+ --default-path <path> 项目目录(默认: 当前目录)
1384
+ --channel <name> 渠道类型(默认: aun)
1385
+ --aun-aid <aid> AUN Agent ID(必填,如 mybot.agentid.pub)
1386
+ --aun-owner <aid> Owner AID(可选,如 alice.agentid.pub)
1387
+
1388
+ 示例:
1389
+ evolclaw init --non-interactive --aun-aid mybot.agentid.pub --aun-owner alice.agentid.pub
1390
+ evolclaw init --non-interactive --default-path /home/user/project --aun-aid bot.agentid.pub`);
1391
+ }
1392
+ else if (args[1] === 'wechat') {
1298
1393
  await cmdInitWechat();
1299
1394
  }
1300
1395
  else if (args[1] === 'feishu') {
@@ -1380,6 +1475,8 @@ Commands:
1380
1475
  --level error|warn 只显示指定级别及以上
1381
1476
  --module <name> 只显示指定模块(如 feishu、AgentRunner)
1382
1477
  --raw 原始输出,不着色
1478
+ ctl 运行时自管理(模型切换、推理强度、压缩上下文等)
1479
+ evolclaw ctl help 查看完整命令列表
1383
1480
  diagnose 诊断启动环境(配置、数据库、进程)
1384
1481
  mv <old> <new> 迁移项目目录(保留 Claude/Codex/EvolClaw 会话)
1385
1482
 
package/dist/config.js CHANGED
@@ -177,7 +177,7 @@ export function saveConfig(config, configPath = resolvePaths().config) {
177
177
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
178
178
  }
179
179
  // ── Channel instance normalization ──
180
- export const channelTypes = ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot'];
180
+ export const channelTypes = ['feishu', 'wechat', 'aun', 'dingtalk', 'qqbot', 'wecom'];
181
181
  /**
182
182
  * Normalize a channel config value (single object, array, or undefined) into an array
183
183
  * where every element has a `name` field.