@agent-wechat/wechat 0.8.0 → 0.8.1

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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +312 -68
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -146,8 +146,8 @@ All config lives under `channels.wechat` in OpenClaw's config file:
146
146
  | `dmPolicy` | `"open" \| "allowlist" \| "disabled"` | `"disabled"` | Who can DM the bot |
147
147
  | `allowFrom` | string[] | `[]` | wxid allowlist for DMs (when policy is `allowlist`) |
148
148
  | `groupPolicy` | `"open" \| "allowlist" \| "disabled"` | `"disabled"` | Group message policy |
149
- | `groupAllowFrom` | string[] | `[]` | wxid allowlist for group senders |
150
- | `groups` | object | `{}` | Per-group overrides (e.g. `{ "id@chatroom": { "requireMention": false } }`) |
149
+ | `groupAllowFrom` | string[] | `[]` | Global allowlist of group sender IDs (`wxid_...`) |
150
+ | `groups` | object | `{}` | Per-group overrides (e.g. `{ "id@chatroom": { "requireMention": false, "enabled": true, "groupPolicy": "allowlist", "allowFrom": ["wxid_..."] } }`) |
151
151
  | `pollIntervalMs` | integer | `1000` | Message polling interval |
152
152
  | `authPollIntervalMs` | integer | `30000` | Auth status check interval |
153
153
 
package/dist/index.js CHANGED
@@ -1085,6 +1085,12 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
1085
1085
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID2 } from "openclaw/plugin-sdk";
1086
1086
 
1087
1087
  // src/types.ts
1088
+ function normalizeDmPolicy(policy) {
1089
+ return policy === "allowlist" || policy === "open" || policy === "disabled" ? policy : "disabled";
1090
+ }
1091
+ function normalizeGroupPolicy(policy) {
1092
+ return policy === "open" || policy === "disabled" || policy === "allowlist" ? policy : "disabled";
1093
+ }
1088
1094
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
1089
1095
  var DEFAULT_AUTH_POLL_INTERVAL_MS = 3e4;
1090
1096
  var DEFAULT_ACCOUNT_ID = "default";
@@ -1096,9 +1102,9 @@ function resolveWeChatAccount(cfg, accountId) {
1096
1102
  enabled: wechat.enabled !== false,
1097
1103
  serverUrl: wechat.serverUrl,
1098
1104
  token: wechat.token,
1099
- dmPolicy: wechat.dmPolicy ?? "disabled",
1105
+ dmPolicy: normalizeDmPolicy(wechat.dmPolicy),
1100
1106
  allowFrom: wechat.allowFrom ?? [],
1101
- groupPolicy: wechat.groupPolicy ?? "disabled",
1107
+ groupPolicy: normalizeGroupPolicy(wechat.groupPolicy),
1102
1108
  groupAllowFrom: wechat.groupAllowFrom ?? [],
1103
1109
  groups: wechat.groups ?? {},
1104
1110
  pollIntervalMs: wechat.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
@@ -5505,6 +5511,172 @@ var agentConfigSchema = external_exports.object({
5505
5511
 
5506
5512
  // src/monitor.ts
5507
5513
  import { createReplyPrefixOptions } from "openclaw/plugin-sdk";
5514
+
5515
+ // src/access-control.ts
5516
+ import {
5517
+ buildChannelKeyCandidates,
5518
+ resolveAllowlistProviderRuntimeGroupPolicy,
5519
+ resolveChannelEntryMatchWithFallback,
5520
+ resolveDefaultGroupPolicy,
5521
+ resolveSenderCommandAuthorization
5522
+ } from "openclaw/plugin-sdk";
5523
+ function unique(values) {
5524
+ return Array.from(new Set(values));
5525
+ }
5526
+ function normalizeDmPolicy2(policy) {
5527
+ if (policy === "open" || policy === "allowlist" || policy === "disabled") {
5528
+ return policy;
5529
+ }
5530
+ return "disabled";
5531
+ }
5532
+ function normalizeGroupPolicy2(policy) {
5533
+ if (policy === "open" || policy === "allowlist" || policy === "disabled") {
5534
+ return policy;
5535
+ }
5536
+ return void 0;
5537
+ }
5538
+ function firstDefined(...values) {
5539
+ for (const value of values) {
5540
+ if (value !== void 0) {
5541
+ return value;
5542
+ }
5543
+ }
5544
+ return void 0;
5545
+ }
5546
+ function normalizeWeChatId(raw) {
5547
+ const trimmed = raw.trim();
5548
+ if (!trimmed) {
5549
+ return "";
5550
+ }
5551
+ return trimmed.replace(/^wechat:/i, "").trim();
5552
+ }
5553
+ function normalizeWeChatAllowFrom(values) {
5554
+ const normalized = (values ?? []).map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry === "*" ? "*" : normalizeWeChatId(entry)).filter(Boolean);
5555
+ return unique(normalized);
5556
+ }
5557
+ function isWeChatSenderAllowed(senderId, allowFrom) {
5558
+ if (allowFrom.includes("*")) {
5559
+ return true;
5560
+ }
5561
+ const normalizedSender = senderId ? normalizeWeChatId(senderId) : "";
5562
+ if (!normalizedSender) {
5563
+ return false;
5564
+ }
5565
+ return allowFrom.includes(normalizedSender);
5566
+ }
5567
+ function resolveGroupEntry(params) {
5568
+ const groups = params.account.groups ?? {};
5569
+ const normalizedChatId = normalizeWeChatId(params.chatId);
5570
+ const keys = buildChannelKeyCandidates(params.chatId, normalizedChatId);
5571
+ const match = resolveChannelEntryMatchWithFallback({
5572
+ entries: groups,
5573
+ keys,
5574
+ wildcardKey: "*",
5575
+ normalizeKey: normalizeWeChatId
5576
+ });
5577
+ return {
5578
+ groupEntry: match.entry,
5579
+ wildcardEntry: match.wildcardEntry
5580
+ };
5581
+ }
5582
+ function resolveWeChatPolicyContext(params) {
5583
+ const dmPolicy = normalizeDmPolicy2(params.account.dmPolicy);
5584
+ const configuredAllowFrom = normalizeWeChatAllowFrom(params.account.allowFrom);
5585
+ const configuredGroupAllowFrom = normalizeWeChatAllowFrom(params.account.groupAllowFrom);
5586
+ const normalizedStoreAllowFrom = dmPolicy === "allowlist" ? [] : normalizeWeChatAllowFrom(params.storeAllowFrom);
5587
+ const effectiveAllowFrom = unique([...configuredAllowFrom, ...normalizedStoreAllowFrom]);
5588
+ const groupBase = configuredGroupAllowFrom.length > 0 ? configuredGroupAllowFrom : configuredAllowFrom;
5589
+ const effectiveGroupAllowFrom = unique([...groupBase, ...normalizedStoreAllowFrom]);
5590
+ const { groupEntry, wildcardEntry } = resolveGroupEntry({
5591
+ account: params.account,
5592
+ chatId: params.chatId
5593
+ });
5594
+ const groupEnabled = firstDefined(groupEntry?.enabled, wildcardEntry?.enabled, true) !== false;
5595
+ const requireMention = firstDefined(groupEntry?.requireMention, wildcardEntry?.requireMention, true) !== false;
5596
+ const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
5597
+ const { groupPolicy: fallbackGroupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
5598
+ providerConfigPresent: params.cfg.channels?.wechat !== void 0,
5599
+ groupPolicy: normalizeGroupPolicy2(params.account.groupPolicy),
5600
+ defaultGroupPolicy: normalizeGroupPolicy2(defaultGroupPolicy)
5601
+ });
5602
+ const groupPolicy = normalizeGroupPolicy2(
5603
+ firstDefined(
5604
+ groupEntry?.groupPolicy,
5605
+ wildcardEntry?.groupPolicy,
5606
+ params.account.groupPolicy,
5607
+ defaultGroupPolicy
5608
+ )
5609
+ ) ?? fallbackGroupPolicy;
5610
+ const groupAllowOverride = normalizeWeChatAllowFrom(
5611
+ firstDefined(groupEntry?.allowFrom, wildcardEntry?.allowFrom)
5612
+ );
5613
+ const groupAllowFrom = groupAllowOverride.length > 0 ? unique([...groupAllowOverride, ...normalizedStoreAllowFrom]) : effectiveGroupAllowFrom;
5614
+ return {
5615
+ dmPolicy,
5616
+ groupPolicy,
5617
+ requireMention,
5618
+ groupEnabled,
5619
+ effectiveAllowFrom,
5620
+ effectiveGroupAllowFrom: groupAllowFrom
5621
+ };
5622
+ }
5623
+ function resolveWeChatInboundAccessDecision(params) {
5624
+ if (params.isGroup) {
5625
+ if (!params.policy.groupEnabled) {
5626
+ return { allowed: false, reason: "group-config-disabled" };
5627
+ }
5628
+ if (params.policy.groupPolicy === "disabled") {
5629
+ return { allowed: false, reason: "groupPolicy=disabled" };
5630
+ }
5631
+ if (params.policy.groupPolicy === "allowlist") {
5632
+ if (params.policy.effectiveGroupAllowFrom.length === 0) {
5633
+ return { allowed: false, reason: "groupPolicy=allowlist (empty allowlist)" };
5634
+ }
5635
+ if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveGroupAllowFrom)) {
5636
+ return { allowed: false, reason: "groupPolicy=allowlist (sender not allowlisted)" };
5637
+ }
5638
+ }
5639
+ return { allowed: true, reason: `groupPolicy=${params.policy.groupPolicy}` };
5640
+ }
5641
+ if (params.policy.dmPolicy === "disabled") {
5642
+ return { allowed: false, reason: "dmPolicy=disabled" };
5643
+ }
5644
+ if (params.policy.dmPolicy === "allowlist") {
5645
+ if (params.policy.effectiveAllowFrom.length === 0) {
5646
+ return { allowed: false, reason: "dmPolicy=allowlist (empty allowlist)" };
5647
+ }
5648
+ if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveAllowFrom)) {
5649
+ return { allowed: false, reason: "dmPolicy=allowlist (sender not allowlisted)" };
5650
+ }
5651
+ }
5652
+ return { allowed: true, reason: `dmPolicy=${params.policy.dmPolicy}` };
5653
+ }
5654
+ async function resolveWeChatCommandAuthorization(params) {
5655
+ const normalizedSenderId = normalizeWeChatId(params.senderId ?? "");
5656
+ const { commandAuthorized } = await resolveSenderCommandAuthorization({
5657
+ cfg: params.cfg,
5658
+ rawBody: params.rawBody,
5659
+ isGroup: params.isGroup,
5660
+ dmPolicy: params.dmPolicy,
5661
+ configuredAllowFrom: params.allowFromForCommands,
5662
+ senderId: normalizedSenderId,
5663
+ isSenderAllowed: (senderId, allowFrom) => isWeChatSenderAllowed(senderId, normalizeWeChatAllowFrom(allowFrom)),
5664
+ readAllowFromStore: async () => normalizeWeChatAllowFrom(await params.deps.readAllowFromStore()),
5665
+ shouldComputeCommandAuthorized: params.deps.shouldComputeCommandAuthorized,
5666
+ resolveCommandAuthorizedFromAuthorizers: params.deps.resolveCommandAuthorizedFromAuthorizers
5667
+ });
5668
+ return commandAuthorized;
5669
+ }
5670
+ function resolveWeChatMentionGate(params) {
5671
+ const implicitMention = params.implicitMention === true;
5672
+ const baseWasMentioned = params.wasMentioned || implicitMention;
5673
+ const shouldBypassMention = params.isGroup && params.requireMention && !baseWasMentioned && params.allowTextCommands && params.hasControlCommand && params.commandAuthorized;
5674
+ const effectiveWasMentioned = baseWasMentioned || shouldBypassMention;
5675
+ const shouldSkip = params.requireMention && params.canDetectMention && !effectiveWasMentioned;
5676
+ return { effectiveWasMentioned, shouldSkip, shouldBypassMention };
5677
+ }
5678
+
5679
+ // src/monitor.ts
5508
5680
  var MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34]);
5509
5681
  var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
5510
5682
  var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
@@ -5533,20 +5705,6 @@ async function pollMedia(client, chatId, localId, log, maxAttempts = 15, interva
5533
5705
  }
5534
5706
  return null;
5535
5707
  }
5536
- function isMessageAllowed(account, isGroup, senderId) {
5537
- if (isGroup) {
5538
- if (account.groupPolicy === "disabled") return false;
5539
- if (account.groupPolicy === "allowlist") {
5540
- return account.groupAllowFrom.includes(senderId);
5541
- }
5542
- return true;
5543
- }
5544
- if (account.dmPolicy === "disabled") return false;
5545
- if (account.dmPolicy === "allowlist") {
5546
- return account.allowFrom.includes(senderId);
5547
- }
5548
- return true;
5549
- }
5550
5708
  function enqueueWeChatSystemEvent(text, contextKey) {
5551
5709
  try {
5552
5710
  const core = getWeChatRuntime();
@@ -5684,7 +5842,7 @@ async function startWeChatMonitor(opts) {
5684
5842
  connected: false
5685
5843
  });
5686
5844
  }
5687
- async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
5845
+ async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, log) {
5688
5846
  const core = getWeChatRuntime();
5689
5847
  if (msg.isSelf) {
5690
5848
  log?.info?.(`[wechat:${liveAccount.accountId}] Skipping self-sent msg ${msg.localId}`);
@@ -5693,8 +5851,15 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
5693
5851
  const isGroup = chatId.includes("@chatroom");
5694
5852
  const senderId = msg.sender ?? chatId;
5695
5853
  const senderName = msg.senderName ?? msg.sender ?? chat.name;
5696
- if (!isMessageAllowed(liveAccount, isGroup, senderId)) {
5697
- log?.info?.(`[wechat:${liveAccount.accountId}] Blocked by policy: ${isGroup ? "group" : "dm"} from ${senderId}`);
5854
+ const access = resolveWeChatInboundAccessDecision({
5855
+ isGroup,
5856
+ senderId,
5857
+ policy
5858
+ });
5859
+ if (!access.allowed) {
5860
+ log?.info?.(
5861
+ `[wechat:${liveAccount.accountId}] Blocked by policy (${access.reason}) from ${senderId}`
5862
+ );
5698
5863
  return null;
5699
5864
  }
5700
5865
  let mediaPath;
@@ -5772,6 +5937,7 @@ ${replyBlock}` : replyBlock;
5772
5937
  return {
5773
5938
  msg,
5774
5939
  rawBody,
5940
+ commandBody: rawBody,
5775
5941
  mediaPath,
5776
5942
  mediaMime,
5777
5943
  senderName,
@@ -5801,27 +5967,50 @@ function buildSegments(processed) {
5801
5967
  }
5802
5968
  return segments;
5803
5969
  }
5804
- async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg, log, remainingSegments, groupHistory) {
5970
+ async function dispatchSegment(segment, client, chatId, chat, liveAccount, policy, storeAllowFrom, allowTextCommands, cfg, log, remainingSegments) {
5805
5971
  const core = getWeChatRuntime();
5806
5972
  const lastMsg = segment[segment.length - 1];
5807
- const { isGroup, senderId, senderName, timestamp, rawBody, msg } = lastMsg;
5973
+ const { isGroup, senderId, senderName, timestamp, rawBody, commandBody, msg } = lastMsg;
5808
5974
  const mediaMsg = segment.find((pm) => pm.mediaPath);
5809
5975
  const mediaPath = mediaMsg?.mediaPath;
5810
5976
  const mediaMime = mediaMsg?.mediaMime;
5811
5977
  log?.info?.(
5812
5978
  `[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? ` media=${mediaPath}` : ""}`
5813
5979
  );
5814
- if (isGroup) {
5815
- const wechatCfg = cfg?.channels?.wechat;
5816
- const groupEntry = wechatCfg?.groups?.[chatId];
5817
- const defaultEntry = wechatCfg?.groups?.["*"];
5818
- const requireMention = groupEntry?.requireMention ?? defaultEntry?.requireMention ?? true;
5819
- if (requireMention && !lastMsg.isMentioned) {
5820
- log?.info?.(
5821
- `[wechat:${liveAccount.accountId}] Skipping group message (mention required, not mentioned) in ${chatId}`
5822
- );
5823
- return;
5980
+ const hasControlCommand = allowTextCommands && core.channel.text.hasControlCommand(commandBody, cfg);
5981
+ const commandAuthorized = await resolveWeChatCommandAuthorization({
5982
+ cfg,
5983
+ rawBody: commandBody,
5984
+ isGroup,
5985
+ senderId,
5986
+ dmPolicy: policy.dmPolicy,
5987
+ allowFromForCommands: isGroup ? policy.effectiveGroupAllowFrom : policy.effectiveAllowFrom,
5988
+ deps: {
5989
+ shouldComputeCommandAuthorized: (raw, loadedCfg) => core.channel.commands.shouldComputeCommandAuthorized(raw, loadedCfg),
5990
+ resolveCommandAuthorizedFromAuthorizers: (params) => core.channel.commands.resolveCommandAuthorizedFromAuthorizers(params),
5991
+ readAllowFromStore: async () => storeAllowFrom
5824
5992
  }
5993
+ });
5994
+ if (isGroup && allowTextCommands && hasControlCommand && commandAuthorized !== true) {
5995
+ log?.info?.(
5996
+ `[wechat:${liveAccount.accountId}] Dropping unauthorized group control command from ${senderId} in ${chatId}`
5997
+ );
5998
+ return false;
5999
+ }
6000
+ const mentionGate = resolveWeChatMentionGate({
6001
+ isGroup,
6002
+ requireMention: policy.requireMention,
6003
+ canDetectMention: true,
6004
+ wasMentioned: segment.some((pm) => pm.isMentioned),
6005
+ allowTextCommands,
6006
+ hasControlCommand,
6007
+ commandAuthorized: commandAuthorized === true
6008
+ });
6009
+ if (isGroup && mentionGate.shouldSkip) {
6010
+ log?.info?.(
6011
+ `[wechat:${liveAccount.accountId}] Skipping group segment (mention required) in ${chatId}`
6012
+ );
6013
+ return false;
5825
6014
  }
5826
6015
  try {
5827
6016
  const route = core.channel.routing.resolveAgentRoute({
@@ -5896,7 +6085,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
5896
6085
  Body: body,
5897
6086
  BodyForAgent: rawBody,
5898
6087
  RawBody: rawBody,
5899
- CommandBody: rawBody,
6088
+ CommandBody: commandBody,
5900
6089
  InboundHistory: inboundHistory,
5901
6090
  From: isGroup ? `wechat:group:${chatId}` : `wechat:${senderId}`,
5902
6091
  To: `wechat:${chatId}`,
@@ -5909,7 +6098,8 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
5909
6098
  Provider: "wechat",
5910
6099
  Surface: "wechat",
5911
6100
  MessageSid: `wechat:${chatId}:${msg.localId}`,
5912
- WasMentioned: isGroup ? lastMsg.isMentioned : void 0,
6101
+ WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : void 0,
6102
+ CommandAuthorized: commandAuthorized,
5913
6103
  OriginatingChannel: "wechat",
5914
6104
  OriginatingTo: `wechat:${chatId}`,
5915
6105
  ...mediaPath ? { MediaPath: mediaPath, MediaUrl: mediaPath, MediaType: mediaMime } : {},
@@ -6012,13 +6202,12 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
6012
6202
  direction: "inbound",
6013
6203
  at: timestamp
6014
6204
  });
6015
- if (isGroup && groupHistory) {
6016
- groupHistory.set(chatId, []);
6017
- }
6205
+ return true;
6018
6206
  } catch (err) {
6019
6207
  log?.error?.(
6020
6208
  `[wechat:${liveAccount.accountId}] Failed to dispatch segment (last msg ${msg.localId}): ${err}`
6021
6209
  );
6210
+ return false;
6022
6211
  }
6023
6212
  }
6024
6213
  function bufferGroupHistory(groupHistory, chatId, pm, limit) {
@@ -6035,8 +6224,20 @@ function bufferGroupHistory(groupHistory, chatId, pm, limit) {
6035
6224
  }
6036
6225
  }
6037
6226
  async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, skipOpen, groupHistory, groupHistoryLimit) {
6227
+ const core = getWeChatRuntime();
6038
6228
  const liveAccount = resolveWeChatAccount(cfg, account.accountId) ?? account;
6039
6229
  const chatId = chat.username ?? chat.id;
6230
+ const storeAllowFrom = await core.channel.pairing.readAllowFromStore("wechat", process.env, liveAccount.accountId).catch(() => []);
6231
+ const policy = resolveWeChatPolicyContext({
6232
+ account: liveAccount,
6233
+ cfg,
6234
+ chatId,
6235
+ storeAllowFrom
6236
+ });
6237
+ const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
6238
+ cfg,
6239
+ surface: "wechat"
6240
+ });
6040
6241
  if (!skipOpen) {
6041
6242
  log?.info?.(`[wechat:${liveAccount.accountId}] Opening chat ${chatId}...`);
6042
6243
  try {
@@ -6094,20 +6295,18 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
6094
6295
  log?.info?.(
6095
6296
  `[wechat:${liveAccount.accountId}] Processing msg ${msg.localId}: type=${msg.type}, sender=${msg.sender}, isSelf=${msg.isSelf}, content=${(msg.content || "").slice(0, 50)}`
6096
6297
  );
6097
- const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, log);
6298
+ const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, policy, log);
6098
6299
  if (pm) {
6099
6300
  processed.push(pm);
6100
6301
  }
6101
6302
  }
6102
6303
  const isGroup = chatId.includes("@chatroom");
6304
+ let clearBufferedHistory = false;
6305
+ const hasControlCommandInWindow = allowTextCommands && processed.some((pm) => core.channel.text.hasControlCommand(pm.commandBody, cfg));
6103
6306
  if (isGroup && groupHistory) {
6104
- const wechatCfg = cfg?.channels?.wechat;
6105
- const groupEntry = wechatCfg?.groups?.[chatId];
6106
- const defaultEntry = wechatCfg?.groups?.["*"];
6107
- const requireMention = groupEntry?.requireMention ?? defaultEntry?.requireMention ?? true;
6108
- if (requireMention) {
6307
+ if (policy.requireMention) {
6109
6308
  const hasMention = processed.some((pm) => pm.isMentioned);
6110
- if (!hasMention) {
6309
+ if (!hasMention && !hasControlCommandInWindow) {
6111
6310
  const limit = groupHistoryLimit ?? 50;
6112
6311
  for (const pm of processed) {
6113
6312
  bufferGroupHistory(groupHistory, chatId, pm, limit);
@@ -6117,37 +6316,67 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
6117
6316
  lastSeenId.set(chatId, maxId2);
6118
6317
  return;
6119
6318
  }
6120
- const buffered = groupHistory.get(chatId) ?? [];
6121
- if (buffered.length > 0) {
6122
- for (const pm of buffered) {
6123
- pm.isMentioned = true;
6319
+ if (hasMention) {
6320
+ clearBufferedHistory = true;
6321
+ const buffered = groupHistory.get(chatId) ?? [];
6322
+ if (buffered.length > 0) {
6323
+ for (const pm of buffered) {
6324
+ pm.isMentioned = true;
6325
+ }
6326
+ processed.unshift(...buffered);
6327
+ log?.info?.(
6328
+ `[wechat:${liveAccount.accountId}] Injected ${buffered.length} buffered msg(s) as history in ${chatId}`
6329
+ );
6124
6330
  }
6125
- processed.unshift(...buffered);
6126
- groupHistory.set(chatId, []);
6127
- log?.info?.(`[wechat:${liveAccount.accountId}] Injected ${buffered.length} buffered msg(s) as history in ${chatId}`);
6128
- }
6129
- let latestMediaIdx = -1;
6130
- for (let i = processed.length - 1; i >= 0; i--) {
6131
- if (processed[i].mediaPath) {
6132
- latestMediaIdx = i;
6133
- break;
6331
+ let latestMediaIdx = -1;
6332
+ for (let i = processed.length - 1; i >= 0; i--) {
6333
+ if (processed[i].mediaPath) {
6334
+ latestMediaIdx = i;
6335
+ break;
6336
+ }
6134
6337
  }
6135
- }
6136
- for (let i = 0; i < processed.length; i++) {
6137
- if (processed[i].mediaPath && i !== latestMediaIdx) {
6138
- processed[i] = { ...processed[i], mediaPath: void 0, mediaMime: void 0, hasMedia: false };
6338
+ for (let i = 0; i < processed.length; i++) {
6339
+ if (processed[i].mediaPath && i !== latestMediaIdx) {
6340
+ processed[i] = {
6341
+ ...processed[i],
6342
+ mediaPath: void 0,
6343
+ mediaMime: void 0,
6344
+ hasMedia: false
6345
+ };
6346
+ }
6139
6347
  }
6140
6348
  }
6349
+ } else {
6350
+ clearBufferedHistory = true;
6141
6351
  }
6142
6352
  }
6143
6353
  if (processed.length > 0) {
6144
- const segments = buildSegments(processed);
6354
+ const segments = hasControlCommandInWindow ? processed.map((pm) => [pm]) : buildSegments(processed);
6145
6355
  log?.info?.(
6146
6356
  `[wechat:${liveAccount.accountId}] ${chatId}: ${processed.length} dispatchable msg(s) in ${segments.length} segment(s)`
6147
6357
  );
6358
+ let allDispatched = true;
6148
6359
  for (let i = 0; i < segments.length; i++) {
6149
6360
  const remaining = segments.length - i - 1;
6150
- await dispatchSegment(segments[i], client, chatId, chat, liveAccount, cfg, log, remaining, groupHistory);
6361
+ const dispatched = await dispatchSegment(
6362
+ segments[i],
6363
+ client,
6364
+ chatId,
6365
+ chat,
6366
+ liveAccount,
6367
+ policy,
6368
+ storeAllowFrom,
6369
+ allowTextCommands,
6370
+ cfg,
6371
+ log,
6372
+ hasControlCommandInWindow ? void 0 : remaining
6373
+ );
6374
+ if (!dispatched) {
6375
+ allDispatched = false;
6376
+ }
6377
+ }
6378
+ if (clearBufferedHistory && allDispatched && groupHistory) {
6379
+ groupHistory.set(chatId, []);
6151
6380
  }
6152
6381
  }
6153
6382
  const maxId = Math.max(...newMessages.map((m) => m.localId));
@@ -6521,7 +6750,7 @@ var wechatOnboardingAdapter = {
6521
6750
  wechatCfg.groupPolicy = groupPolicy;
6522
6751
  if (groupPolicy === "allowlist") {
6523
6752
  const raw = await prompter.text({
6524
- message: "Allowed group IDs (comma-separated xxx@chatroom values)"
6753
+ message: "Allowed group sender IDs (comma-separated wxid_xxx values; use * to allow any sender)"
6525
6754
  });
6526
6755
  wechatCfg.groupAllowFrom = raw.split(",").map((s) => s.trim()).filter(Boolean);
6527
6756
  }
@@ -6755,7 +6984,13 @@ var wechatPlugin = {
6755
6984
  additionalProperties: {
6756
6985
  type: "object",
6757
6986
  properties: {
6758
- requireMention: { type: "boolean" }
6987
+ enabled: { type: "boolean" },
6988
+ requireMention: { type: "boolean" },
6989
+ groupPolicy: {
6990
+ type: "string",
6991
+ enum: ["open", "allowlist", "disabled"]
6992
+ },
6993
+ allowFrom: { type: "array", items: { type: "string" } }
6759
6994
  }
6760
6995
  }
6761
6996
  },
@@ -6800,7 +7035,8 @@ var wechatPlugin = {
6800
7035
  allowFrom: account.allowFrom ?? [],
6801
7036
  allowFromPath: "channels.wechat.allowFrom",
6802
7037
  policyPath: "channels.wechat.dmPolicy",
6803
- approveHint: "Add the wxid to channels.wechat.allowFrom"
7038
+ approveHint: "Add the wxid to channels.wechat.allowFrom",
7039
+ normalizeEntry: (raw) => raw.replace(/^wechat:/i, "").trim()
6804
7040
  })
6805
7041
  },
6806
7042
  // ---- Groups adapter ----
@@ -6808,10 +7044,18 @@ var wechatPlugin = {
6808
7044
  resolveRequireMention: ({ cfg, groupId }) => {
6809
7045
  const wechat = cfg?.channels?.wechat;
6810
7046
  if (!wechat) return true;
6811
- if (groupId && wechat.groups?.[groupId]?.requireMention != null) {
6812
- return wechat.groups[groupId].requireMention;
7047
+ if (!groupId) {
7048
+ return wechat.groups?.["*"]?.requireMention ?? true;
7049
+ }
7050
+ const exact = wechat.groups?.[groupId];
7051
+ if (exact?.requireMention != null) {
7052
+ return exact.requireMention;
7053
+ }
7054
+ const normalizedGroupId = normalizeWeChatId(groupId);
7055
+ if (normalizedGroupId && wechat.groups?.[normalizedGroupId]?.requireMention != null) {
7056
+ return wechat.groups[normalizedGroupId].requireMention;
6813
7057
  }
6814
- return true;
7058
+ return wechat.groups?.["*"]?.requireMention ?? true;
6815
7059
  }
6816
7060
  },
6817
7061
  // ---- Messaging adapter ----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-wechat/wechat",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "scripts": {
43
43
  "build": "esbuild index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --external:openclaw",
44
- "typecheck": "tsc --noEmit"
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "node --test --experimental-strip-types src/*.test.ts"
45
46
  }
46
47
  }