@agent-wechat/wechat 0.7.10 → 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.
- package/README.md +2 -2
- package/dist/index.js +319 -68
- 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,
|
|
@@ -1204,6 +1210,13 @@ var WeChatClient = class {
|
|
|
1204
1210
|
async openChat(chatId, clearUnreads) {
|
|
1205
1211
|
return this.post(`/api/chats/${encodeURIComponent(chatId)}/open${qs({ clearUnreads })}`);
|
|
1206
1212
|
}
|
|
1213
|
+
// ---- Contacts ----
|
|
1214
|
+
async listContacts(limit, offset) {
|
|
1215
|
+
return this.get(`/api/contacts${qs({ limit, offset })}`);
|
|
1216
|
+
}
|
|
1217
|
+
async findContacts(name) {
|
|
1218
|
+
return this.get(`/api/contacts/find${qs({ name })}`);
|
|
1219
|
+
}
|
|
1207
1220
|
// ---- Messages ----
|
|
1208
1221
|
async listMessages(chatId, limit, offset) {
|
|
1209
1222
|
return this.get(`/api/messages/${encodeURIComponent(chatId)}${qs({ limit, offset })}`);
|
|
@@ -5498,6 +5511,172 @@ var agentConfigSchema = external_exports.object({
|
|
|
5498
5511
|
|
|
5499
5512
|
// src/monitor.ts
|
|
5500
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
|
|
5501
5680
|
var MEDIA_TYPES = /* @__PURE__ */ new Set([3, 34]);
|
|
5502
5681
|
var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
5503
5682
|
var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
@@ -5526,20 +5705,6 @@ async function pollMedia(client, chatId, localId, log, maxAttempts = 15, interva
|
|
|
5526
5705
|
}
|
|
5527
5706
|
return null;
|
|
5528
5707
|
}
|
|
5529
|
-
function isMessageAllowed(account, isGroup, senderId) {
|
|
5530
|
-
if (isGroup) {
|
|
5531
|
-
if (account.groupPolicy === "disabled") return false;
|
|
5532
|
-
if (account.groupPolicy === "allowlist") {
|
|
5533
|
-
return account.groupAllowFrom.includes(senderId);
|
|
5534
|
-
}
|
|
5535
|
-
return true;
|
|
5536
|
-
}
|
|
5537
|
-
if (account.dmPolicy === "disabled") return false;
|
|
5538
|
-
if (account.dmPolicy === "allowlist") {
|
|
5539
|
-
return account.allowFrom.includes(senderId);
|
|
5540
|
-
}
|
|
5541
|
-
return true;
|
|
5542
|
-
}
|
|
5543
5708
|
function enqueueWeChatSystemEvent(text, contextKey) {
|
|
5544
5709
|
try {
|
|
5545
5710
|
const core = getWeChatRuntime();
|
|
@@ -5677,7 +5842,7 @@ async function startWeChatMonitor(opts) {
|
|
|
5677
5842
|
connected: false
|
|
5678
5843
|
});
|
|
5679
5844
|
}
|
|
5680
|
-
async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
|
|
5845
|
+
async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, log) {
|
|
5681
5846
|
const core = getWeChatRuntime();
|
|
5682
5847
|
if (msg.isSelf) {
|
|
5683
5848
|
log?.info?.(`[wechat:${liveAccount.accountId}] Skipping self-sent msg ${msg.localId}`);
|
|
@@ -5686,8 +5851,15 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, log) {
|
|
|
5686
5851
|
const isGroup = chatId.includes("@chatroom");
|
|
5687
5852
|
const senderId = msg.sender ?? chatId;
|
|
5688
5853
|
const senderName = msg.senderName ?? msg.sender ?? chat.name;
|
|
5689
|
-
|
|
5690
|
-
|
|
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
|
+
);
|
|
5691
5863
|
return null;
|
|
5692
5864
|
}
|
|
5693
5865
|
let mediaPath;
|
|
@@ -5765,6 +5937,7 @@ ${replyBlock}` : replyBlock;
|
|
|
5765
5937
|
return {
|
|
5766
5938
|
msg,
|
|
5767
5939
|
rawBody,
|
|
5940
|
+
commandBody: rawBody,
|
|
5768
5941
|
mediaPath,
|
|
5769
5942
|
mediaMime,
|
|
5770
5943
|
senderName,
|
|
@@ -5794,27 +5967,50 @@ function buildSegments(processed) {
|
|
|
5794
5967
|
}
|
|
5795
5968
|
return segments;
|
|
5796
5969
|
}
|
|
5797
|
-
async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg, log, remainingSegments
|
|
5970
|
+
async function dispatchSegment(segment, client, chatId, chat, liveAccount, policy, storeAllowFrom, allowTextCommands, cfg, log, remainingSegments) {
|
|
5798
5971
|
const core = getWeChatRuntime();
|
|
5799
5972
|
const lastMsg = segment[segment.length - 1];
|
|
5800
|
-
const { isGroup, senderId, senderName, timestamp, rawBody, msg } = lastMsg;
|
|
5973
|
+
const { isGroup, senderId, senderName, timestamp, rawBody, commandBody, msg } = lastMsg;
|
|
5801
5974
|
const mediaMsg = segment.find((pm) => pm.mediaPath);
|
|
5802
5975
|
const mediaPath = mediaMsg?.mediaPath;
|
|
5803
5976
|
const mediaMime = mediaMsg?.mediaMime;
|
|
5804
5977
|
log?.info?.(
|
|
5805
5978
|
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? ` media=${mediaPath}` : ""}`
|
|
5806
5979
|
);
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
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
|
|
5817
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;
|
|
5818
6014
|
}
|
|
5819
6015
|
try {
|
|
5820
6016
|
const route = core.channel.routing.resolveAgentRoute({
|
|
@@ -5889,7 +6085,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
5889
6085
|
Body: body,
|
|
5890
6086
|
BodyForAgent: rawBody,
|
|
5891
6087
|
RawBody: rawBody,
|
|
5892
|
-
CommandBody:
|
|
6088
|
+
CommandBody: commandBody,
|
|
5893
6089
|
InboundHistory: inboundHistory,
|
|
5894
6090
|
From: isGroup ? `wechat:group:${chatId}` : `wechat:${senderId}`,
|
|
5895
6091
|
To: `wechat:${chatId}`,
|
|
@@ -5902,7 +6098,8 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
5902
6098
|
Provider: "wechat",
|
|
5903
6099
|
Surface: "wechat",
|
|
5904
6100
|
MessageSid: `wechat:${chatId}:${msg.localId}`,
|
|
5905
|
-
WasMentioned: isGroup ?
|
|
6101
|
+
WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : void 0,
|
|
6102
|
+
CommandAuthorized: commandAuthorized,
|
|
5906
6103
|
OriginatingChannel: "wechat",
|
|
5907
6104
|
OriginatingTo: `wechat:${chatId}`,
|
|
5908
6105
|
...mediaPath ? { MediaPath: mediaPath, MediaUrl: mediaPath, MediaType: mediaMime } : {},
|
|
@@ -6005,13 +6202,12 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, cfg,
|
|
|
6005
6202
|
direction: "inbound",
|
|
6006
6203
|
at: timestamp
|
|
6007
6204
|
});
|
|
6008
|
-
|
|
6009
|
-
groupHistory.set(chatId, []);
|
|
6010
|
-
}
|
|
6205
|
+
return true;
|
|
6011
6206
|
} catch (err) {
|
|
6012
6207
|
log?.error?.(
|
|
6013
6208
|
`[wechat:${liveAccount.accountId}] Failed to dispatch segment (last msg ${msg.localId}): ${err}`
|
|
6014
6209
|
);
|
|
6210
|
+
return false;
|
|
6015
6211
|
}
|
|
6016
6212
|
}
|
|
6017
6213
|
function bufferGroupHistory(groupHistory, chatId, pm, limit) {
|
|
@@ -6028,8 +6224,20 @@ function bufferGroupHistory(groupHistory, chatId, pm, limit) {
|
|
|
6028
6224
|
}
|
|
6029
6225
|
}
|
|
6030
6226
|
async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, skipOpen, groupHistory, groupHistoryLimit) {
|
|
6227
|
+
const core = getWeChatRuntime();
|
|
6031
6228
|
const liveAccount = resolveWeChatAccount(cfg, account.accountId) ?? account;
|
|
6032
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
|
+
});
|
|
6033
6241
|
if (!skipOpen) {
|
|
6034
6242
|
log?.info?.(`[wechat:${liveAccount.accountId}] Opening chat ${chatId}...`);
|
|
6035
6243
|
try {
|
|
@@ -6087,20 +6295,18 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6087
6295
|
log?.info?.(
|
|
6088
6296
|
`[wechat:${liveAccount.accountId}] Processing msg ${msg.localId}: type=${msg.type}, sender=${msg.sender}, isSelf=${msg.isSelf}, content=${(msg.content || "").slice(0, 50)}`
|
|
6089
6297
|
);
|
|
6090
|
-
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, log);
|
|
6298
|
+
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, policy, log);
|
|
6091
6299
|
if (pm) {
|
|
6092
6300
|
processed.push(pm);
|
|
6093
6301
|
}
|
|
6094
6302
|
}
|
|
6095
6303
|
const isGroup = chatId.includes("@chatroom");
|
|
6304
|
+
let clearBufferedHistory = false;
|
|
6305
|
+
const hasControlCommandInWindow = allowTextCommands && processed.some((pm) => core.channel.text.hasControlCommand(pm.commandBody, cfg));
|
|
6096
6306
|
if (isGroup && groupHistory) {
|
|
6097
|
-
|
|
6098
|
-
const groupEntry = wechatCfg?.groups?.[chatId];
|
|
6099
|
-
const defaultEntry = wechatCfg?.groups?.["*"];
|
|
6100
|
-
const requireMention = groupEntry?.requireMention ?? defaultEntry?.requireMention ?? true;
|
|
6101
|
-
if (requireMention) {
|
|
6307
|
+
if (policy.requireMention) {
|
|
6102
6308
|
const hasMention = processed.some((pm) => pm.isMentioned);
|
|
6103
|
-
if (!hasMention) {
|
|
6309
|
+
if (!hasMention && !hasControlCommandInWindow) {
|
|
6104
6310
|
const limit = groupHistoryLimit ?? 50;
|
|
6105
6311
|
for (const pm of processed) {
|
|
6106
6312
|
bufferGroupHistory(groupHistory, chatId, pm, limit);
|
|
@@ -6110,37 +6316,67 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6110
6316
|
lastSeenId.set(chatId, maxId2);
|
|
6111
6317
|
return;
|
|
6112
6318
|
}
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
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
|
+
);
|
|
6117
6330
|
}
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
if (processed[i].mediaPath) {
|
|
6125
|
-
latestMediaIdx = i;
|
|
6126
|
-
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
|
+
}
|
|
6127
6337
|
}
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
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
|
+
}
|
|
6132
6347
|
}
|
|
6133
6348
|
}
|
|
6349
|
+
} else {
|
|
6350
|
+
clearBufferedHistory = true;
|
|
6134
6351
|
}
|
|
6135
6352
|
}
|
|
6136
6353
|
if (processed.length > 0) {
|
|
6137
|
-
const segments = buildSegments(processed);
|
|
6354
|
+
const segments = hasControlCommandInWindow ? processed.map((pm) => [pm]) : buildSegments(processed);
|
|
6138
6355
|
log?.info?.(
|
|
6139
6356
|
`[wechat:${liveAccount.accountId}] ${chatId}: ${processed.length} dispatchable msg(s) in ${segments.length} segment(s)`
|
|
6140
6357
|
);
|
|
6358
|
+
let allDispatched = true;
|
|
6141
6359
|
for (let i = 0; i < segments.length; i++) {
|
|
6142
6360
|
const remaining = segments.length - i - 1;
|
|
6143
|
-
await dispatchSegment(
|
|
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, []);
|
|
6144
6380
|
}
|
|
6145
6381
|
}
|
|
6146
6382
|
const maxId = Math.max(...newMessages.map((m) => m.localId));
|
|
@@ -6514,7 +6750,7 @@ var wechatOnboardingAdapter = {
|
|
|
6514
6750
|
wechatCfg.groupPolicy = groupPolicy;
|
|
6515
6751
|
if (groupPolicy === "allowlist") {
|
|
6516
6752
|
const raw = await prompter.text({
|
|
6517
|
-
message: "Allowed group IDs (comma-separated
|
|
6753
|
+
message: "Allowed group sender IDs (comma-separated wxid_xxx values; use * to allow any sender)"
|
|
6518
6754
|
});
|
|
6519
6755
|
wechatCfg.groupAllowFrom = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6520
6756
|
}
|
|
@@ -6748,7 +6984,13 @@ var wechatPlugin = {
|
|
|
6748
6984
|
additionalProperties: {
|
|
6749
6985
|
type: "object",
|
|
6750
6986
|
properties: {
|
|
6751
|
-
|
|
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" } }
|
|
6752
6994
|
}
|
|
6753
6995
|
}
|
|
6754
6996
|
},
|
|
@@ -6793,7 +7035,8 @@ var wechatPlugin = {
|
|
|
6793
7035
|
allowFrom: account.allowFrom ?? [],
|
|
6794
7036
|
allowFromPath: "channels.wechat.allowFrom",
|
|
6795
7037
|
policyPath: "channels.wechat.dmPolicy",
|
|
6796
|
-
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()
|
|
6797
7040
|
})
|
|
6798
7041
|
},
|
|
6799
7042
|
// ---- Groups adapter ----
|
|
@@ -6801,10 +7044,18 @@ var wechatPlugin = {
|
|
|
6801
7044
|
resolveRequireMention: ({ cfg, groupId }) => {
|
|
6802
7045
|
const wechat = cfg?.channels?.wechat;
|
|
6803
7046
|
if (!wechat) return true;
|
|
6804
|
-
if (groupId
|
|
6805
|
-
return wechat.groups[
|
|
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;
|
|
6806
7057
|
}
|
|
6807
|
-
return true;
|
|
7058
|
+
return wechat.groups?.["*"]?.requireMention ?? true;
|
|
6808
7059
|
}
|
|
6809
7060
|
},
|
|
6810
7061
|
// ---- Messaging adapter ----
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-wechat/wechat",
|
|
3
|
-
"version": "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
|
}
|