@agent-wechat/wechat 0.8.0 → 0.8.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.
- package/README.md +2 -2
- package/dist/index.js +360 -69
- 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[] | `[]` |
|
|
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
|
|
1105
|
+
dmPolicy: normalizeDmPolicy(wechat.dmPolicy),
|
|
1100
1106
|
allowFrom: wechat.allowFrom ?? [],
|
|
1101
|
-
groupPolicy: wechat.groupPolicy
|
|
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,208 @@ 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
|
+
var INVISIBLE_TEXT_RE = /[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g;
|
|
5524
|
+
var MENTION_SEPARATOR_RE = /[\s\u2005]+/u;
|
|
5525
|
+
var WECHAT_MENTION_TOKEN_RE = /^[@@][^\s\u2005]+$/u;
|
|
5526
|
+
function unique(values) {
|
|
5527
|
+
return Array.from(new Set(values));
|
|
5528
|
+
}
|
|
5529
|
+
function normalizeDmPolicy2(policy) {
|
|
5530
|
+
if (policy === "open" || policy === "allowlist" || policy === "disabled") {
|
|
5531
|
+
return policy;
|
|
5532
|
+
}
|
|
5533
|
+
return "disabled";
|
|
5534
|
+
}
|
|
5535
|
+
function normalizeGroupPolicy2(policy) {
|
|
5536
|
+
if (policy === "open" || policy === "allowlist" || policy === "disabled") {
|
|
5537
|
+
return policy;
|
|
5538
|
+
}
|
|
5539
|
+
return void 0;
|
|
5540
|
+
}
|
|
5541
|
+
function firstDefined(...values) {
|
|
5542
|
+
for (const value of values) {
|
|
5543
|
+
if (value !== void 0) {
|
|
5544
|
+
return value;
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
return void 0;
|
|
5548
|
+
}
|
|
5549
|
+
function normalizeWeChatId(raw) {
|
|
5550
|
+
const trimmed = raw.trim();
|
|
5551
|
+
if (!trimmed) {
|
|
5552
|
+
return "";
|
|
5553
|
+
}
|
|
5554
|
+
return trimmed.replace(/^wechat:/i, "").trim();
|
|
5555
|
+
}
|
|
5556
|
+
function normalizeWeChatAllowFrom(values) {
|
|
5557
|
+
const normalized = (values ?? []).map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry === "*" ? "*" : normalizeWeChatId(entry)).filter(Boolean);
|
|
5558
|
+
return unique(normalized);
|
|
5559
|
+
}
|
|
5560
|
+
function findCommandTokenStart(input) {
|
|
5561
|
+
const match = /(?:^|\s)([/!][A-Za-z])/u.exec(input);
|
|
5562
|
+
if (!match) {
|
|
5563
|
+
return -1;
|
|
5564
|
+
}
|
|
5565
|
+
const whole = match[0] ?? "";
|
|
5566
|
+
const startsWithSpace = whole.startsWith(" ");
|
|
5567
|
+
return (match.index ?? 0) + (startsWithSpace ? 1 : 0);
|
|
5568
|
+
}
|
|
5569
|
+
function normalizeWeChatCommandBody(raw, params) {
|
|
5570
|
+
const trimmed = raw.replace(INVISIBLE_TEXT_RE, "").trim();
|
|
5571
|
+
if (!trimmed) {
|
|
5572
|
+
return "";
|
|
5573
|
+
}
|
|
5574
|
+
const isGroup = params?.isGroup === true;
|
|
5575
|
+
const wasMentioned = params?.wasMentioned === true;
|
|
5576
|
+
if (!isGroup || !wasMentioned) {
|
|
5577
|
+
return trimmed;
|
|
5578
|
+
}
|
|
5579
|
+
const commandStart = findCommandTokenStart(trimmed);
|
|
5580
|
+
if (commandStart < 0) {
|
|
5581
|
+
return trimmed;
|
|
5582
|
+
}
|
|
5583
|
+
const prefix = trimmed.slice(0, commandStart).trim();
|
|
5584
|
+
if (!prefix) {
|
|
5585
|
+
return trimmed.slice(commandStart).trimStart();
|
|
5586
|
+
}
|
|
5587
|
+
const prefixTokens = prefix.split(MENTION_SEPARATOR_RE).map((token) => token.trim()).filter(Boolean);
|
|
5588
|
+
if (prefixTokens.length > 0 && prefixTokens.every((token) => WECHAT_MENTION_TOKEN_RE.test(token))) {
|
|
5589
|
+
return trimmed.slice(commandStart).trimStart();
|
|
5590
|
+
}
|
|
5591
|
+
return trimmed;
|
|
5592
|
+
}
|
|
5593
|
+
function isWeChatSenderAllowed(senderId, allowFrom) {
|
|
5594
|
+
if (allowFrom.includes("*")) {
|
|
5595
|
+
return true;
|
|
5596
|
+
}
|
|
5597
|
+
const normalizedSender = senderId ? normalizeWeChatId(senderId) : "";
|
|
5598
|
+
if (!normalizedSender) {
|
|
5599
|
+
return false;
|
|
5600
|
+
}
|
|
5601
|
+
return allowFrom.includes(normalizedSender);
|
|
5602
|
+
}
|
|
5603
|
+
function resolveGroupEntry(params) {
|
|
5604
|
+
const groups = params.account.groups ?? {};
|
|
5605
|
+
const normalizedChatId = normalizeWeChatId(params.chatId);
|
|
5606
|
+
const keys = buildChannelKeyCandidates(params.chatId, normalizedChatId);
|
|
5607
|
+
const match = resolveChannelEntryMatchWithFallback({
|
|
5608
|
+
entries: groups,
|
|
5609
|
+
keys,
|
|
5610
|
+
wildcardKey: "*",
|
|
5611
|
+
normalizeKey: normalizeWeChatId
|
|
5612
|
+
});
|
|
5613
|
+
return {
|
|
5614
|
+
groupEntry: match.entry,
|
|
5615
|
+
wildcardEntry: match.wildcardEntry
|
|
5616
|
+
};
|
|
5617
|
+
}
|
|
5618
|
+
function resolveWeChatPolicyContext(params) {
|
|
5619
|
+
const dmPolicy = normalizeDmPolicy2(params.account.dmPolicy);
|
|
5620
|
+
const configuredAllowFrom = normalizeWeChatAllowFrom(params.account.allowFrom);
|
|
5621
|
+
const configuredGroupAllowFrom = normalizeWeChatAllowFrom(params.account.groupAllowFrom);
|
|
5622
|
+
const normalizedStoreAllowFrom = dmPolicy === "allowlist" ? [] : normalizeWeChatAllowFrom(params.storeAllowFrom);
|
|
5623
|
+
const effectiveAllowFrom = unique([...configuredAllowFrom, ...normalizedStoreAllowFrom]);
|
|
5624
|
+
const groupBase = configuredGroupAllowFrom.length > 0 ? configuredGroupAllowFrom : configuredAllowFrom;
|
|
5625
|
+
const effectiveGroupAllowFrom = unique([...groupBase, ...normalizedStoreAllowFrom]);
|
|
5626
|
+
const { groupEntry, wildcardEntry } = resolveGroupEntry({
|
|
5627
|
+
account: params.account,
|
|
5628
|
+
chatId: params.chatId
|
|
5629
|
+
});
|
|
5630
|
+
const groupEnabled = firstDefined(groupEntry?.enabled, wildcardEntry?.enabled, true) !== false;
|
|
5631
|
+
const requireMention = firstDefined(groupEntry?.requireMention, wildcardEntry?.requireMention, true) !== false;
|
|
5632
|
+
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg);
|
|
5633
|
+
const { groupPolicy: fallbackGroupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
5634
|
+
providerConfigPresent: params.cfg.channels?.wechat !== void 0,
|
|
5635
|
+
groupPolicy: normalizeGroupPolicy2(params.account.groupPolicy),
|
|
5636
|
+
defaultGroupPolicy: normalizeGroupPolicy2(defaultGroupPolicy)
|
|
5637
|
+
});
|
|
5638
|
+
const groupPolicy = normalizeGroupPolicy2(
|
|
5639
|
+
firstDefined(
|
|
5640
|
+
groupEntry?.groupPolicy,
|
|
5641
|
+
wildcardEntry?.groupPolicy,
|
|
5642
|
+
params.account.groupPolicy,
|
|
5643
|
+
defaultGroupPolicy
|
|
5644
|
+
)
|
|
5645
|
+
) ?? fallbackGroupPolicy;
|
|
5646
|
+
const groupAllowOverride = normalizeWeChatAllowFrom(
|
|
5647
|
+
firstDefined(groupEntry?.allowFrom, wildcardEntry?.allowFrom)
|
|
5648
|
+
);
|
|
5649
|
+
const groupAllowFrom = groupAllowOverride.length > 0 ? unique([...groupAllowOverride, ...normalizedStoreAllowFrom]) : effectiveGroupAllowFrom;
|
|
5650
|
+
return {
|
|
5651
|
+
dmPolicy,
|
|
5652
|
+
groupPolicy,
|
|
5653
|
+
requireMention,
|
|
5654
|
+
groupEnabled,
|
|
5655
|
+
effectiveAllowFrom,
|
|
5656
|
+
effectiveGroupAllowFrom: groupAllowFrom
|
|
5657
|
+
};
|
|
5658
|
+
}
|
|
5659
|
+
function resolveWeChatInboundAccessDecision(params) {
|
|
5660
|
+
if (params.isGroup) {
|
|
5661
|
+
if (!params.policy.groupEnabled) {
|
|
5662
|
+
return { allowed: false, reason: "group-config-disabled" };
|
|
5663
|
+
}
|
|
5664
|
+
if (params.policy.groupPolicy === "disabled") {
|
|
5665
|
+
return { allowed: false, reason: "groupPolicy=disabled" };
|
|
5666
|
+
}
|
|
5667
|
+
if (params.policy.groupPolicy === "allowlist") {
|
|
5668
|
+
if (params.policy.effectiveGroupAllowFrom.length === 0) {
|
|
5669
|
+
return { allowed: false, reason: "groupPolicy=allowlist (empty allowlist)" };
|
|
5670
|
+
}
|
|
5671
|
+
if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveGroupAllowFrom)) {
|
|
5672
|
+
return { allowed: false, reason: "groupPolicy=allowlist (sender not allowlisted)" };
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
return { allowed: true, reason: `groupPolicy=${params.policy.groupPolicy}` };
|
|
5676
|
+
}
|
|
5677
|
+
if (params.policy.dmPolicy === "disabled") {
|
|
5678
|
+
return { allowed: false, reason: "dmPolicy=disabled" };
|
|
5679
|
+
}
|
|
5680
|
+
if (params.policy.dmPolicy === "allowlist") {
|
|
5681
|
+
if (params.policy.effectiveAllowFrom.length === 0) {
|
|
5682
|
+
return { allowed: false, reason: "dmPolicy=allowlist (empty allowlist)" };
|
|
5683
|
+
}
|
|
5684
|
+
if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveAllowFrom)) {
|
|
5685
|
+
return { allowed: false, reason: "dmPolicy=allowlist (sender not allowlisted)" };
|
|
5686
|
+
}
|
|
5687
|
+
}
|
|
5688
|
+
return { allowed: true, reason: `dmPolicy=${params.policy.dmPolicy}` };
|
|
5689
|
+
}
|
|
5690
|
+
async function resolveWeChatCommandAuthorization(params) {
|
|
5691
|
+
const normalizedSenderId = normalizeWeChatId(params.senderId ?? "");
|
|
5692
|
+
const { commandAuthorized } = await resolveSenderCommandAuthorization({
|
|
5693
|
+
cfg: params.cfg,
|
|
5694
|
+
rawBody: params.rawBody,
|
|
5695
|
+
isGroup: params.isGroup,
|
|
5696
|
+
dmPolicy: params.dmPolicy,
|
|
5697
|
+
configuredAllowFrom: params.allowFromForCommands,
|
|
5698
|
+
senderId: normalizedSenderId,
|
|
5699
|
+
isSenderAllowed: (senderId, allowFrom) => isWeChatSenderAllowed(senderId, normalizeWeChatAllowFrom(allowFrom)),
|
|
5700
|
+
readAllowFromStore: async () => normalizeWeChatAllowFrom(await params.deps.readAllowFromStore()),
|
|
5701
|
+
shouldComputeCommandAuthorized: params.deps.shouldComputeCommandAuthorized,
|
|
5702
|
+
resolveCommandAuthorizedFromAuthorizers: params.deps.resolveCommandAuthorizedFromAuthorizers
|
|
5703
|
+
});
|
|
5704
|
+
return commandAuthorized;
|
|
5705
|
+
}
|
|
5706
|
+
function resolveWeChatMentionGate(params) {
|
|
5707
|
+
const implicitMention = params.implicitMention === true;
|
|
5708
|
+
const baseWasMentioned = params.wasMentioned || implicitMention;
|
|
5709
|
+
const shouldBypassMention = params.isGroup && params.requireMention && !baseWasMentioned && params.allowTextCommands && params.hasControlCommand && params.commandAuthorized;
|
|
5710
|
+
const effectiveWasMentioned = baseWasMentioned || shouldBypassMention;
|
|
5711
|
+
const shouldSkip = params.requireMention && params.canDetectMention && !effectiveWasMentioned;
|
|
5712
|
+
return { effectiveWasMentioned, shouldSkip, shouldBypassMention };
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
// src/monitor.ts
|
|
5508
5716
|
var MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34]);
|
|
5509
5717
|
var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
5510
5718
|
var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
@@ -5533,20 +5741,6 @@ async function pollMedia(client, chatId, localId, log, maxAttempts = 15, interva
|
|
|
5533
5741
|
}
|
|
5534
5742
|
return null;
|
|
5535
5743
|
}
|
|
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
5744
|
function enqueueWeChatSystemEvent(text, contextKey) {
|
|
5551
5745
|
try {
|
|
5552
5746
|
const core = getWeChatRuntime();
|
|
@@ -5684,7 +5878,7 @@ async function startWeChatMonitor(opts) {
|
|
|
5684
5878
|
connected: false
|
|
5685
5879
|
});
|
|
5686
5880
|
}
|
|
5687
|
-
async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
|
|
5881
|
+
async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, log) {
|
|
5688
5882
|
const core = getWeChatRuntime();
|
|
5689
5883
|
if (msg.isSelf) {
|
|
5690
5884
|
log?.info?.(`[wechat:${liveAccount.accountId}] Skipping self-sent msg ${msg.localId}`);
|
|
@@ -5693,8 +5887,16 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
|
|
|
5693
5887
|
const isGroup = chatId.includes("@chatroom");
|
|
5694
5888
|
const senderId = msg.sender ?? chatId;
|
|
5695
5889
|
const senderName = msg.senderName ?? msg.sender ?? chat.name;
|
|
5696
|
-
|
|
5697
|
-
|
|
5890
|
+
const wasMentioned = isGroup && msg.isMentioned === true;
|
|
5891
|
+
const access = resolveWeChatInboundAccessDecision({
|
|
5892
|
+
isGroup,
|
|
5893
|
+
senderId,
|
|
5894
|
+
policy
|
|
5895
|
+
});
|
|
5896
|
+
if (!access.allowed) {
|
|
5897
|
+
log?.info?.(
|
|
5898
|
+
`[wechat:${liveAccount.accountId}] Blocked by policy (${access.reason}) from ${senderId}`
|
|
5899
|
+
);
|
|
5698
5900
|
return null;
|
|
5699
5901
|
}
|
|
5700
5902
|
let mediaPath;
|
|
@@ -5772,6 +5974,10 @@ ${replyBlock}` : replyBlock;
|
|
|
5772
5974
|
return {
|
|
5773
5975
|
msg,
|
|
5774
5976
|
rawBody,
|
|
5977
|
+
commandBody: normalizeWeChatCommandBody(rawBody, {
|
|
5978
|
+
isGroup,
|
|
5979
|
+
wasMentioned
|
|
5980
|
+
}),
|
|
5775
5981
|
mediaPath,
|
|
5776
5982
|
mediaMime,
|
|
5777
5983
|
senderName,
|
|
@@ -5779,7 +5985,7 @@ ${replyBlock}` : replyBlock;
|
|
|
5779
5985
|
isGroup,
|
|
5780
5986
|
timestamp,
|
|
5781
5987
|
hasMedia,
|
|
5782
|
-
isMentioned:
|
|
5988
|
+
isMentioned: wasMentioned
|
|
5783
5989
|
};
|
|
5784
5990
|
}
|
|
5785
5991
|
function buildSegments(processed) {
|
|
@@ -5801,27 +6007,50 @@ function buildSegments(processed) {
|
|
|
5801
6007
|
}
|
|
5802
6008
|
return segments;
|
|
5803
6009
|
}
|
|
5804
|
-
async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg, log, remainingSegments
|
|
6010
|
+
async function dispatchSegment(segment, client, chatId, chat, liveAccount, policy, storeAllowFrom, allowTextCommands, cfg, log, remainingSegments) {
|
|
5805
6011
|
const core = getWeChatRuntime();
|
|
5806
6012
|
const lastMsg = segment[segment.length - 1];
|
|
5807
|
-
const { isGroup, senderId, senderName, timestamp, rawBody, msg } = lastMsg;
|
|
6013
|
+
const { isGroup, senderId, senderName, timestamp, rawBody, commandBody, msg } = lastMsg;
|
|
5808
6014
|
const mediaMsg = segment.find((pm) => pm.mediaPath);
|
|
5809
6015
|
const mediaPath = mediaMsg?.mediaPath;
|
|
5810
6016
|
const mediaMime = mediaMsg?.mediaMime;
|
|
5811
6017
|
log?.info?.(
|
|
5812
6018
|
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? ` media=${mediaPath}` : ""}`
|
|
5813
6019
|
);
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
6020
|
+
const hasControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(commandBody, cfg);
|
|
6021
|
+
const commandAuthorized = await resolveWeChatCommandAuthorization({
|
|
6022
|
+
cfg,
|
|
6023
|
+
rawBody: commandBody,
|
|
6024
|
+
isGroup,
|
|
6025
|
+
senderId,
|
|
6026
|
+
dmPolicy: policy.dmPolicy,
|
|
6027
|
+
allowFromForCommands: isGroup ? policy.effectiveGroupAllowFrom : policy.effectiveAllowFrom,
|
|
6028
|
+
deps: {
|
|
6029
|
+
shouldComputeCommandAuthorized: (raw, loadedCfg) => core.channel.commands.shouldComputeCommandAuthorized(raw, loadedCfg),
|
|
6030
|
+
resolveCommandAuthorizedFromAuthorizers: (params) => core.channel.commands.resolveCommandAuthorizedFromAuthorizers(params),
|
|
6031
|
+
readAllowFromStore: async () => storeAllowFrom
|
|
5824
6032
|
}
|
|
6033
|
+
});
|
|
6034
|
+
if (isGroup && allowTextCommands && hasControlCommand && commandAuthorized !== true) {
|
|
6035
|
+
log?.info?.(
|
|
6036
|
+
`[wechat:${liveAccount.accountId}] Dropping unauthorized group control command from ${senderId} in ${chatId}`
|
|
6037
|
+
);
|
|
6038
|
+
return false;
|
|
6039
|
+
}
|
|
6040
|
+
const mentionGate = resolveWeChatMentionGate({
|
|
6041
|
+
isGroup,
|
|
6042
|
+
requireMention: policy.requireMention,
|
|
6043
|
+
canDetectMention: true,
|
|
6044
|
+
wasMentioned: segment.some((pm) => pm.isMentioned),
|
|
6045
|
+
allowTextCommands,
|
|
6046
|
+
hasControlCommand,
|
|
6047
|
+
commandAuthorized: commandAuthorized === true
|
|
6048
|
+
});
|
|
6049
|
+
if (isGroup && mentionGate.shouldSkip) {
|
|
6050
|
+
log?.info?.(
|
|
6051
|
+
`[wechat:${liveAccount.accountId}] Skipping group segment (mention required) in ${chatId}`
|
|
6052
|
+
);
|
|
6053
|
+
return false;
|
|
5825
6054
|
}
|
|
5826
6055
|
try {
|
|
5827
6056
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -5896,7 +6125,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
5896
6125
|
Body: body,
|
|
5897
6126
|
BodyForAgent: rawBody,
|
|
5898
6127
|
RawBody: rawBody,
|
|
5899
|
-
CommandBody:
|
|
6128
|
+
CommandBody: commandBody,
|
|
5900
6129
|
InboundHistory: inboundHistory,
|
|
5901
6130
|
From: isGroup ? `wechat:group:${chatId}` : `wechat:${senderId}`,
|
|
5902
6131
|
To: `wechat:${chatId}`,
|
|
@@ -5909,7 +6138,8 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
5909
6138
|
Provider: "wechat",
|
|
5910
6139
|
Surface: "wechat",
|
|
5911
6140
|
MessageSid: `wechat:${chatId}:${msg.localId}`,
|
|
5912
|
-
WasMentioned: isGroup ?
|
|
6141
|
+
WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : void 0,
|
|
6142
|
+
CommandAuthorized: commandAuthorized,
|
|
5913
6143
|
OriginatingChannel: "wechat",
|
|
5914
6144
|
OriginatingTo: `wechat:${chatId}`,
|
|
5915
6145
|
...mediaPath ? { MediaPath: mediaPath, MediaUrl: mediaPath, MediaType: mediaMime } : {},
|
|
@@ -6012,13 +6242,12 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
6012
6242
|
direction: "inbound",
|
|
6013
6243
|
at: timestamp
|
|
6014
6244
|
});
|
|
6015
|
-
|
|
6016
|
-
groupHistory.set(chatId, []);
|
|
6017
|
-
}
|
|
6245
|
+
return true;
|
|
6018
6246
|
} catch (err) {
|
|
6019
6247
|
log?.error?.(
|
|
6020
6248
|
`[wechat:${liveAccount.accountId}] Failed to dispatch segment (last msg ${msg.localId}): ${err}`
|
|
6021
6249
|
);
|
|
6250
|
+
return false;
|
|
6022
6251
|
}
|
|
6023
6252
|
}
|
|
6024
6253
|
function bufferGroupHistory(groupHistory, chatId, pm, limit) {
|
|
@@ -6035,8 +6264,20 @@ function bufferGroupHistory(groupHistory, chatId, pm, limit) {
|
|
|
6035
6264
|
}
|
|
6036
6265
|
}
|
|
6037
6266
|
async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, skipOpen, groupHistory, groupHistoryLimit) {
|
|
6267
|
+
const core = getWeChatRuntime();
|
|
6038
6268
|
const liveAccount = resolveWeChatAccount(cfg, account.accountId) ?? account;
|
|
6039
6269
|
const chatId = chat.username ?? chat.id;
|
|
6270
|
+
const storeAllowFrom = await core.channel.pairing.readAllowFromStore("wechat", process.env, liveAccount.accountId).catch(() => []);
|
|
6271
|
+
const policy = resolveWeChatPolicyContext({
|
|
6272
|
+
account: liveAccount,
|
|
6273
|
+
cfg,
|
|
6274
|
+
chatId,
|
|
6275
|
+
storeAllowFrom
|
|
6276
|
+
});
|
|
6277
|
+
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
6278
|
+
cfg,
|
|
6279
|
+
surface: "wechat"
|
|
6280
|
+
});
|
|
6040
6281
|
if (!skipOpen) {
|
|
6041
6282
|
log?.info?.(`[wechat:${liveAccount.accountId}] Opening chat ${chatId}...`);
|
|
6042
6283
|
try {
|
|
@@ -6094,20 +6335,18 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6094
6335
|
log?.info?.(
|
|
6095
6336
|
`[wechat:${liveAccount.accountId}] Processing msg ${msg.localId}: type=${msg.type}, sender=${msg.sender}, isSelf=${msg.isSelf}, content=${(msg.content || "").slice(0, 50)}`
|
|
6096
6337
|
);
|
|
6097
|
-
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, log);
|
|
6338
|
+
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, policy, log);
|
|
6098
6339
|
if (pm) {
|
|
6099
6340
|
processed.push(pm);
|
|
6100
6341
|
}
|
|
6101
6342
|
}
|
|
6102
6343
|
const isGroup = chatId.includes("@chatroom");
|
|
6344
|
+
let clearBufferedHistory = false;
|
|
6345
|
+
const hasControlCommandInWindow = allowTextCommands && processed.some((pm) => core.channel.commands.isControlCommandMessage(pm.commandBody, cfg));
|
|
6103
6346
|
if (isGroup && groupHistory) {
|
|
6104
|
-
|
|
6105
|
-
const groupEntry = wechatCfg?.groups?.[chatId];
|
|
6106
|
-
const defaultEntry = wechatCfg?.groups?.["*"];
|
|
6107
|
-
const requireMention = groupEntry?.requireMention ?? defaultEntry?.requireMention ?? true;
|
|
6108
|
-
if (requireMention) {
|
|
6347
|
+
if (policy.requireMention) {
|
|
6109
6348
|
const hasMention = processed.some((pm) => pm.isMentioned);
|
|
6110
|
-
if (!hasMention) {
|
|
6349
|
+
if (!hasMention && !hasControlCommandInWindow) {
|
|
6111
6350
|
const limit = groupHistoryLimit ?? 50;
|
|
6112
6351
|
for (const pm of processed) {
|
|
6113
6352
|
bufferGroupHistory(groupHistory, chatId, pm, limit);
|
|
@@ -6117,37 +6356,67 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6117
6356
|
lastSeenId.set(chatId, maxId2);
|
|
6118
6357
|
return;
|
|
6119
6358
|
}
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6359
|
+
if (hasMention) {
|
|
6360
|
+
clearBufferedHistory = true;
|
|
6361
|
+
const buffered = groupHistory.get(chatId) ?? [];
|
|
6362
|
+
if (buffered.length > 0) {
|
|
6363
|
+
for (const pm of buffered) {
|
|
6364
|
+
pm.isMentioned = true;
|
|
6365
|
+
}
|
|
6366
|
+
processed.unshift(...buffered);
|
|
6367
|
+
log?.info?.(
|
|
6368
|
+
`[wechat:${liveAccount.accountId}] Injected ${buffered.length} buffered msg(s) as history in ${chatId}`
|
|
6369
|
+
);
|
|
6124
6370
|
}
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
if (processed[i].mediaPath) {
|
|
6132
|
-
latestMediaIdx = i;
|
|
6133
|
-
break;
|
|
6371
|
+
let latestMediaIdx = -1;
|
|
6372
|
+
for (let i = processed.length - 1; i >= 0; i--) {
|
|
6373
|
+
if (processed[i].mediaPath) {
|
|
6374
|
+
latestMediaIdx = i;
|
|
6375
|
+
break;
|
|
6376
|
+
}
|
|
6134
6377
|
}
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6378
|
+
for (let i = 0; i < processed.length; i++) {
|
|
6379
|
+
if (processed[i].mediaPath && i !== latestMediaIdx) {
|
|
6380
|
+
processed[i] = {
|
|
6381
|
+
...processed[i],
|
|
6382
|
+
mediaPath: void 0,
|
|
6383
|
+
mediaMime: void 0,
|
|
6384
|
+
hasMedia: false
|
|
6385
|
+
};
|
|
6386
|
+
}
|
|
6139
6387
|
}
|
|
6140
6388
|
}
|
|
6389
|
+
} else {
|
|
6390
|
+
clearBufferedHistory = true;
|
|
6141
6391
|
}
|
|
6142
6392
|
}
|
|
6143
6393
|
if (processed.length > 0) {
|
|
6144
|
-
const segments = buildSegments(processed);
|
|
6394
|
+
const segments = hasControlCommandInWindow ? processed.map((pm) => [pm]) : buildSegments(processed);
|
|
6145
6395
|
log?.info?.(
|
|
6146
6396
|
`[wechat:${liveAccount.accountId}] ${chatId}: ${processed.length} dispatchable msg(s) in ${segments.length} segment(s)`
|
|
6147
6397
|
);
|
|
6398
|
+
let allDispatched = true;
|
|
6148
6399
|
for (let i = 0; i < segments.length; i++) {
|
|
6149
6400
|
const remaining = segments.length - i - 1;
|
|
6150
|
-
await dispatchSegment(
|
|
6401
|
+
const dispatched = await dispatchSegment(
|
|
6402
|
+
segments[i],
|
|
6403
|
+
client,
|
|
6404
|
+
chatId,
|
|
6405
|
+
chat,
|
|
6406
|
+
liveAccount,
|
|
6407
|
+
policy,
|
|
6408
|
+
storeAllowFrom,
|
|
6409
|
+
allowTextCommands,
|
|
6410
|
+
cfg,
|
|
6411
|
+
log,
|
|
6412
|
+
hasControlCommandInWindow ? void 0 : remaining
|
|
6413
|
+
);
|
|
6414
|
+
if (!dispatched) {
|
|
6415
|
+
allDispatched = false;
|
|
6416
|
+
}
|
|
6417
|
+
}
|
|
6418
|
+
if (clearBufferedHistory && allDispatched && groupHistory) {
|
|
6419
|
+
groupHistory.set(chatId, []);
|
|
6151
6420
|
}
|
|
6152
6421
|
}
|
|
6153
6422
|
const maxId = Math.max(...newMessages.map((m) => m.localId));
|
|
@@ -6521,7 +6790,7 @@ var wechatOnboardingAdapter = {
|
|
|
6521
6790
|
wechatCfg.groupPolicy = groupPolicy;
|
|
6522
6791
|
if (groupPolicy === "allowlist") {
|
|
6523
6792
|
const raw = await prompter.text({
|
|
6524
|
-
message: "Allowed group IDs (comma-separated
|
|
6793
|
+
message: "Allowed group sender IDs (comma-separated wxid_xxx values; use * to allow any sender)"
|
|
6525
6794
|
});
|
|
6526
6795
|
wechatCfg.groupAllowFrom = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6527
6796
|
}
|
|
@@ -6755,7 +7024,13 @@ var wechatPlugin = {
|
|
|
6755
7024
|
additionalProperties: {
|
|
6756
7025
|
type: "object",
|
|
6757
7026
|
properties: {
|
|
6758
|
-
|
|
7027
|
+
enabled: { type: "boolean" },
|
|
7028
|
+
requireMention: { type: "boolean" },
|
|
7029
|
+
groupPolicy: {
|
|
7030
|
+
type: "string",
|
|
7031
|
+
enum: ["open", "allowlist", "disabled"]
|
|
7032
|
+
},
|
|
7033
|
+
allowFrom: { type: "array", items: { type: "string" } }
|
|
6759
7034
|
}
|
|
6760
7035
|
}
|
|
6761
7036
|
},
|
|
@@ -6800,7 +7075,8 @@ var wechatPlugin = {
|
|
|
6800
7075
|
allowFrom: account.allowFrom ?? [],
|
|
6801
7076
|
allowFromPath: "channels.wechat.allowFrom",
|
|
6802
7077
|
policyPath: "channels.wechat.dmPolicy",
|
|
6803
|
-
approveHint: "Add the wxid to channels.wechat.allowFrom"
|
|
7078
|
+
approveHint: "Add the wxid to channels.wechat.allowFrom",
|
|
7079
|
+
normalizeEntry: (raw) => raw.replace(/^wechat:/i, "").trim()
|
|
6804
7080
|
})
|
|
6805
7081
|
},
|
|
6806
7082
|
// ---- Groups adapter ----
|
|
@@ -6808,12 +7084,27 @@ var wechatPlugin = {
|
|
|
6808
7084
|
resolveRequireMention: ({ cfg, groupId }) => {
|
|
6809
7085
|
const wechat = cfg?.channels?.wechat;
|
|
6810
7086
|
if (!wechat) return true;
|
|
6811
|
-
if (groupId
|
|
6812
|
-
return wechat.groups[
|
|
7087
|
+
if (!groupId) {
|
|
7088
|
+
return wechat.groups?.["*"]?.requireMention ?? true;
|
|
7089
|
+
}
|
|
7090
|
+
const exact = wechat.groups?.[groupId];
|
|
7091
|
+
if (exact?.requireMention != null) {
|
|
7092
|
+
return exact.requireMention;
|
|
6813
7093
|
}
|
|
6814
|
-
|
|
7094
|
+
const normalizedGroupId = normalizeWeChatId(groupId);
|
|
7095
|
+
if (normalizedGroupId && wechat.groups?.[normalizedGroupId]?.requireMention != null) {
|
|
7096
|
+
return wechat.groups[normalizedGroupId].requireMention;
|
|
7097
|
+
}
|
|
7098
|
+
return wechat.groups?.["*"]?.requireMention ?? true;
|
|
6815
7099
|
}
|
|
6816
7100
|
},
|
|
7101
|
+
// ---- Mention adapter ----
|
|
7102
|
+
mentions: {
|
|
7103
|
+
stripMentions: ({ text, ctx }) => normalizeWeChatCommandBody(text, {
|
|
7104
|
+
isGroup: ctx.ChatType === "group",
|
|
7105
|
+
wasMentioned: ctx.WasMentioned === true
|
|
7106
|
+
})
|
|
7107
|
+
},
|
|
6817
7108
|
// ---- Messaging adapter ----
|
|
6818
7109
|
messaging: {
|
|
6819
7110
|
normalizeTarget: (raw) => raw.replace(/^wechat:/i, "").trim() || void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-wechat/wechat",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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
|
}
|