@agent-wechat/agent-wechat 0.15.6 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/index.js +113 -113
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,6 +87,7 @@ Or edit `~/.openclaw/openclaw.json` directly:
|
|
|
87
87
|
"enabled": true,
|
|
88
88
|
"serverUrl": "http://localhost:6174",
|
|
89
89
|
"dmPolicy": "open",
|
|
90
|
+
"allowFrom": ["*"],
|
|
90
91
|
"groupPolicy": "open"
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -147,13 +148,18 @@ All config lives under `channels.agent-wechat` in OpenClaw's config file:
|
|
|
147
148
|
| `serverUrl` | string | — | agent-wechat REST API URL |
|
|
148
149
|
| `token` | string | — | Auth token (auto-read from `~/.config/agent-wechat/token` for local setups) |
|
|
149
150
|
| `dmPolicy` | `"open" \| "allowlist" \| "disabled"` | `"disabled"` | Who can DM the bot |
|
|
150
|
-
| `allowFrom` | string[] | `[]` |
|
|
151
|
+
| `allowFrom` | string[] | `[]` | DM senders and command owners: `wxid_...`, `"*"` for everyone, or `accessGroup:<name>`. With `dmPolicy: "open"`, use `["*"]` |
|
|
151
152
|
| `groupPolicy` | `"open" \| "allowlist" \| "disabled"` | `"disabled"` | Group message policy |
|
|
152
|
-
| `groupAllowFrom` | string[] | `[]` |
|
|
153
|
+
| `groupAllowFrom` | string[] | `[]` | Group senders who pass `allowlist` and may run commands in groups; falls back to `allowFrom` when empty |
|
|
153
154
|
| `groups` | object | `{}` | Per-group overrides (e.g. `{ "id@chatroom": { "requireMention": false, "enabled": true, "groupPolicy": "allowlist", "allowFrom": ["wxid_..."] } }`) |
|
|
154
155
|
| `pollIntervalMs` | integer | `1000` | Message polling interval |
|
|
155
156
|
| `authPollIntervalMs` | integer | `30000` | Auth status check interval |
|
|
156
157
|
|
|
158
|
+
Access decisions (DM and group senders, per-group overrides, control commands, @mention
|
|
159
|
+
gating) are made by OpenClaw's channel ingress policy, so `accessGroup:<name>` entries from
|
|
160
|
+
the top-level `accessGroups` config work in every allowlist. The pairing store is not
|
|
161
|
+
used: WeChat has no pairing flow.
|
|
162
|
+
|
|
157
163
|
## Development
|
|
158
164
|
|
|
159
165
|
### Build from source
|
package/dist/index.js
CHANGED
|
@@ -5937,6 +5937,9 @@ async function pollMedia(client, chatId, localId, quality, log, maxAttempts = 15
|
|
|
5937
5937
|
return lastResult;
|
|
5938
5938
|
}
|
|
5939
5939
|
|
|
5940
|
+
// src/monitor.ts
|
|
5941
|
+
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
5942
|
+
|
|
5940
5943
|
// src/access-control.ts
|
|
5941
5944
|
import {
|
|
5942
5945
|
buildChannelKeyCandidates,
|
|
@@ -6021,16 +6024,6 @@ function normalizeWeChatCommandBody(raw, params) {
|
|
|
6021
6024
|
}
|
|
6022
6025
|
return trimmed;
|
|
6023
6026
|
}
|
|
6024
|
-
function isWeChatSenderAllowed(senderId, allowFrom) {
|
|
6025
|
-
if (allowFrom.includes("*")) {
|
|
6026
|
-
return true;
|
|
6027
|
-
}
|
|
6028
|
-
const normalizedSender = senderId ? normalizeWeChatId(senderId) : "";
|
|
6029
|
-
if (!normalizedSender) {
|
|
6030
|
-
return false;
|
|
6031
|
-
}
|
|
6032
|
-
return allowFrom.includes(normalizedSender);
|
|
6033
|
-
}
|
|
6034
6027
|
function resolveGroupEntry(params) {
|
|
6035
6028
|
const groups = params.account.groups ?? {};
|
|
6036
6029
|
const normalizedChatId = normalizeWeChatId(params.chatId);
|
|
@@ -6047,13 +6040,6 @@ function resolveGroupEntry(params) {
|
|
|
6047
6040
|
};
|
|
6048
6041
|
}
|
|
6049
6042
|
function resolveWeChatPolicyContext(params) {
|
|
6050
|
-
const dmPolicy = normalizeDmPolicy2(params.account.dmPolicy);
|
|
6051
|
-
const configuredAllowFrom = normalizeWeChatAllowFrom(params.account.allowFrom);
|
|
6052
|
-
const configuredGroupAllowFrom = normalizeWeChatAllowFrom(params.account.groupAllowFrom);
|
|
6053
|
-
const normalizedStoreAllowFrom = dmPolicy === "allowlist" ? [] : normalizeWeChatAllowFrom(params.storeAllowFrom);
|
|
6054
|
-
const effectiveAllowFrom = unique([...configuredAllowFrom, ...normalizedStoreAllowFrom]);
|
|
6055
|
-
const groupBase = configuredGroupAllowFrom.length > 0 ? configuredGroupAllowFrom : configuredAllowFrom;
|
|
6056
|
-
const effectiveGroupAllowFrom = unique([...groupBase, ...normalizedStoreAllowFrom]);
|
|
6057
6043
|
const { groupEntry, wildcardEntry } = resolveGroupEntry({
|
|
6058
6044
|
account: params.account,
|
|
6059
6045
|
chatId: params.chatId
|
|
@@ -6074,84 +6060,86 @@ function resolveWeChatPolicyContext(params) {
|
|
|
6074
6060
|
defaultGroupPolicy
|
|
6075
6061
|
)
|
|
6076
6062
|
) ?? fallbackGroupPolicy;
|
|
6077
|
-
const
|
|
6063
|
+
const groupSenderAllowFrom = normalizeWeChatAllowFrom(
|
|
6078
6064
|
firstDefined(groupEntry?.allowFrom, wildcardEntry?.allowFrom)
|
|
6079
6065
|
);
|
|
6080
|
-
const
|
|
6066
|
+
const dmPolicy = normalizeDmPolicy2(params.account.dmPolicy);
|
|
6067
|
+
const allowFrom = normalizeWeChatAllowFrom(params.account.allowFrom);
|
|
6068
|
+
const groupAllowFrom = normalizeWeChatAllowFrom(params.account.groupAllowFrom);
|
|
6069
|
+
const impliedOpenDmWildcard = dmPolicy === "open" && !allowFrom.includes("*");
|
|
6070
|
+
const scoped = impliedOpenDmWildcard ? {
|
|
6071
|
+
allowFrom: ["*", ...allowFrom],
|
|
6072
|
+
impliedOpenDmWildcard,
|
|
6073
|
+
groupAllowFrom: groupAllowFrom.length > 0 ? groupAllowFrom : allowFrom,
|
|
6074
|
+
groupAllowFromFallbackToAllowFrom: false,
|
|
6075
|
+
commandOwnerAllowFrom: allowFrom
|
|
6076
|
+
} : { allowFrom, groupAllowFrom, groupAllowFromFallbackToAllowFrom: true };
|
|
6081
6077
|
return {
|
|
6082
6078
|
dmPolicy,
|
|
6083
6079
|
groupPolicy,
|
|
6084
6080
|
requireMention,
|
|
6085
6081
|
groupEnabled,
|
|
6086
|
-
|
|
6087
|
-
|
|
6082
|
+
...scoped,
|
|
6083
|
+
...groupSenderAllowFrom.length > 0 ? { groupSenderAllowFrom } : {}
|
|
6088
6084
|
};
|
|
6089
6085
|
}
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
return { allowed: false, reason: "dmPolicy=disabled" };
|
|
6110
|
-
}
|
|
6111
|
-
if (params.policy.dmPolicy === "allowlist") {
|
|
6112
|
-
if (params.policy.effectiveAllowFrom.length === 0) {
|
|
6113
|
-
return { allowed: false, reason: "dmPolicy=allowlist (empty allowlist)" };
|
|
6114
|
-
}
|
|
6115
|
-
if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveAllowFrom)) {
|
|
6116
|
-
return { allowed: false, reason: "dmPolicy=allowlist (sender not allowlisted)" };
|
|
6117
|
-
}
|
|
6118
|
-
}
|
|
6119
|
-
return { allowed: true, reason: `dmPolicy=${params.policy.dmPolicy}` };
|
|
6120
|
-
}
|
|
6121
|
-
async function resolveWeChatCommandAuthorization(params) {
|
|
6122
|
-
if (!params.deps.shouldComputeCommandAuthorized(params.rawBody, params.cfg)) {
|
|
6123
|
-
return void 0;
|
|
6086
|
+
var WECHAT_INGRESS_IDENTITY = {
|
|
6087
|
+
key: "wechat-id",
|
|
6088
|
+
entryIdPrefix: "wechat",
|
|
6089
|
+
sensitivity: "pii",
|
|
6090
|
+
normalize: (value) => normalizeWeChatId(value) || null
|
|
6091
|
+
};
|
|
6092
|
+
function resolveWeChatIngress(params) {
|
|
6093
|
+
const { policy, isGroup } = params;
|
|
6094
|
+
const routes = [];
|
|
6095
|
+
if (isGroup && !policy.groupEnabled) {
|
|
6096
|
+
routes.push({
|
|
6097
|
+
id: "wechat-group",
|
|
6098
|
+
kind: "route",
|
|
6099
|
+
configured: true,
|
|
6100
|
+
matched: true,
|
|
6101
|
+
enabled: true,
|
|
6102
|
+
allowed: false,
|
|
6103
|
+
blockReason: "group_disabled"
|
|
6104
|
+
});
|
|
6124
6105
|
}
|
|
6125
|
-
const
|
|
6126
|
-
const
|
|
6127
|
-
const
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6106
|
+
const groupAllowFrom = isGroup && policy.groupSenderAllowFrom || policy.groupAllowFrom;
|
|
6107
|
+
const mention = isGroup ? params.mention : void 0;
|
|
6108
|
+
const readStoreAllowFrom = params.readStoreAllowFrom;
|
|
6109
|
+
return params.resolve({
|
|
6110
|
+
channelId: "agent-wechat",
|
|
6111
|
+
accountId: params.accountId,
|
|
6112
|
+
identity: WECHAT_INGRESS_IDENTITY,
|
|
6113
|
+
cfg: params.cfg,
|
|
6114
|
+
subject: { stableId: normalizeWeChatId(params.senderId ?? "") || null },
|
|
6115
|
+
conversation: { kind: isGroup ? "group" : "direct", id: params.chatId },
|
|
6116
|
+
event: { kind: "message", authMode: "inbound", mayPair: false },
|
|
6117
|
+
dmPolicy: policy.dmPolicy,
|
|
6118
|
+
groupPolicy: policy.groupPolicy,
|
|
6119
|
+
policy: {
|
|
6120
|
+
groupAllowFromFallbackToAllowFrom: policy.groupAllowFromFallbackToAllowFrom,
|
|
6121
|
+
...mention ? { activation: { requireMention: policy.requireMention, allowTextCommands: mention.allowTextCommands } } : {}
|
|
6122
|
+
},
|
|
6123
|
+
allowFrom: policy.allowFrom,
|
|
6124
|
+
groupAllowFrom,
|
|
6125
|
+
...routes.length > 0 ? { route: routes } : {},
|
|
6126
|
+
...readStoreAllowFrom ? { readStoreAllowFrom: async () => readStoreAllowFrom() } : {},
|
|
6127
|
+
...mention ? { mentionFacts: { canDetectMention: true, wasMentioned: mention.wasMentioned } } : {},
|
|
6128
|
+
command: params.command ? {
|
|
6129
|
+
...params.command,
|
|
6130
|
+
// Group commands are authorized by the group's sender allowlist only, as before.
|
|
6131
|
+
groupOwnerAllowFrom: "none",
|
|
6132
|
+
...policy.commandOwnerAllowFrom && !isGroup ? { commandOwnerAllowFrom: policy.commandOwnerAllowFrom, commandGroupAllowFromFallbackToAllowFrom: false } : {}
|
|
6133
|
+
} : false
|
|
6143
6134
|
});
|
|
6144
6135
|
}
|
|
6145
|
-
function resolveWeChatMentionGate(params) {
|
|
6146
|
-
const implicitMention = params.implicitMention === true;
|
|
6147
|
-
const baseWasMentioned = params.wasMentioned || implicitMention;
|
|
6148
|
-
const shouldBypassMention = params.isGroup && params.requireMention && !baseWasMentioned && params.allowTextCommands && params.hasControlCommand && params.commandAuthorized;
|
|
6149
|
-
const effectiveWasMentioned = baseWasMentioned || shouldBypassMention;
|
|
6150
|
-
const shouldSkip = params.requireMention && params.canDetectMention && !effectiveWasMentioned;
|
|
6151
|
-
return { effectiveWasMentioned, shouldSkip, shouldBypassMention };
|
|
6152
|
-
}
|
|
6153
6136
|
|
|
6154
6137
|
// src/monitor.ts
|
|
6138
|
+
var warnedOpenDmWildcard = /* @__PURE__ */ new Set();
|
|
6139
|
+
function getIngressResolver(core) {
|
|
6140
|
+
const runtimeResolve = core.channel.inbound?.ingress?.resolveStable;
|
|
6141
|
+
return runtimeResolve ?? resolveStableChannelMessageIngress;
|
|
6142
|
+
}
|
|
6155
6143
|
var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
|
|
6156
6144
|
var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
|
|
6157
6145
|
var voiceTranscriptCache = /* @__PURE__ */ new Map();
|
|
@@ -6378,7 +6366,7 @@ async function startWeChatMonitor(opts) {
|
|
|
6378
6366
|
connected: false
|
|
6379
6367
|
});
|
|
6380
6368
|
}
|
|
6381
|
-
async function prepareMessage(client, msg, chatId, chat, liveAccount,
|
|
6369
|
+
async function prepareMessage(client, msg, chatId, chat, liveAccount, admitSender, log) {
|
|
6382
6370
|
if (msg.isSelf) {
|
|
6383
6371
|
log?.info?.(`[wechat:${liveAccount.accountId}] Skipping self-sent msg ${msg.localId}`);
|
|
6384
6372
|
return null;
|
|
@@ -6387,11 +6375,7 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, lo
|
|
|
6387
6375
|
const senderId = msg.sender ?? chatId;
|
|
6388
6376
|
const senderName = msg.senderName ?? msg.sender ?? chat.name;
|
|
6389
6377
|
const wasMentioned = isGroup && msg.isMentioned === true;
|
|
6390
|
-
const access =
|
|
6391
|
-
isGroup,
|
|
6392
|
-
senderId,
|
|
6393
|
-
policy
|
|
6394
|
-
});
|
|
6378
|
+
const access = await admitSender({ isGroup, senderId });
|
|
6395
6379
|
if (!access.allowed) {
|
|
6396
6380
|
log?.info?.(
|
|
6397
6381
|
`[wechat:${liveAccount.accountId}] Blocked by policy (${access.reason}) from ${senderId}`
|
|
@@ -6482,40 +6466,34 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6482
6466
|
`[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? " with model media" : ""} attachments=${attachmentPaths.length}`
|
|
6483
6467
|
);
|
|
6484
6468
|
const hasControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(commandBody, cfg);
|
|
6485
|
-
const
|
|
6469
|
+
const wasMentioned = segment.some((pm) => pm.isMentioned);
|
|
6470
|
+
const ingress = await resolveWeChatIngress({
|
|
6471
|
+
resolve: getIngressResolver(core),
|
|
6486
6472
|
cfg,
|
|
6487
|
-
|
|
6473
|
+
accountId: liveAccount.accountId,
|
|
6474
|
+
policy,
|
|
6488
6475
|
isGroup,
|
|
6476
|
+
chatId,
|
|
6489
6477
|
senderId,
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
shouldComputeCommandAuthorized: (raw, loadedCfg) => core.channel.commands.shouldComputeCommandAuthorized(raw, loadedCfg),
|
|
6494
|
-
resolveCommandAuthorizedFromAuthorizers: (params) => core.channel.commands.resolveCommandAuthorizedFromAuthorizers(params),
|
|
6495
|
-
readAllowFromStore: async () => storeAllowFrom
|
|
6496
|
-
}
|
|
6478
|
+
readStoreAllowFrom: async () => storeAllowFrom,
|
|
6479
|
+
command: { allowTextCommands, hasControlCommand },
|
|
6480
|
+
mention: { wasMentioned, allowTextCommands }
|
|
6497
6481
|
});
|
|
6498
|
-
if (
|
|
6482
|
+
if (ingress.ingress.admission === "skip") {
|
|
6499
6483
|
log?.info?.(
|
|
6500
|
-
`[wechat:${liveAccount.accountId}]
|
|
6484
|
+
`[wechat:${liveAccount.accountId}] Skipping group segment (${ingress.ingress.reasonCode}) in ${chatId}`
|
|
6501
6485
|
);
|
|
6502
6486
|
return "ignored";
|
|
6503
6487
|
}
|
|
6504
|
-
const
|
|
6505
|
-
|
|
6506
|
-
requireMention: policy.requireMention,
|
|
6507
|
-
canDetectMention: true,
|
|
6508
|
-
wasMentioned: segment.some((pm) => pm.isMentioned),
|
|
6509
|
-
allowTextCommands,
|
|
6510
|
-
hasControlCommand,
|
|
6511
|
-
commandAuthorized: commandAuthorized === true
|
|
6512
|
-
});
|
|
6513
|
-
if (isGroup && mentionGate.shouldSkip) {
|
|
6488
|
+
const dmUnauthorizedCommand = !isGroup && ingress.ingress.reasonCode === "control_command_unauthorized";
|
|
6489
|
+
if (ingress.ingress.admission !== "dispatch" && !dmUnauthorizedCommand) {
|
|
6514
6490
|
log?.info?.(
|
|
6515
|
-
`[wechat:${liveAccount.accountId}]
|
|
6491
|
+
`[wechat:${liveAccount.accountId}] Dropping segment (${ingress.ingress.reasonCode}) from ${senderId} in ${chatId}`
|
|
6516
6492
|
);
|
|
6517
6493
|
return "ignored";
|
|
6518
6494
|
}
|
|
6495
|
+
const commandAuthorized = core.channel.commands.shouldComputeCommandAuthorized(commandBody, cfg) ? ingress.commandAccess.authorized : void 0;
|
|
6496
|
+
const effectiveWasMentioned = ingress.activationAccess.effectiveWasMentioned ?? wasMentioned;
|
|
6519
6497
|
try {
|
|
6520
6498
|
for (const item of segment) {
|
|
6521
6499
|
const attachment = item.attachment;
|
|
@@ -6632,7 +6610,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
|
|
|
6632
6610
|
Provider: "agent-wechat",
|
|
6633
6611
|
Surface: "agent-wechat",
|
|
6634
6612
|
MessageSid: `wechat:${chatId}:${msg.localId}`,
|
|
6635
|
-
WasMentioned: isGroup ?
|
|
6613
|
+
WasMentioned: isGroup ? effectiveWasMentioned : void 0,
|
|
6636
6614
|
CommandAuthorized: commandAuthorized,
|
|
6637
6615
|
OriginatingChannel: "agent-wechat",
|
|
6638
6616
|
OriginatingTo: `wechat:${chatId}`,
|
|
@@ -6739,9 +6717,31 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6739
6717
|
const policy = resolveWeChatPolicyContext({
|
|
6740
6718
|
account: liveAccount,
|
|
6741
6719
|
cfg,
|
|
6742
|
-
chatId
|
|
6743
|
-
storeAllowFrom
|
|
6720
|
+
chatId
|
|
6744
6721
|
});
|
|
6722
|
+
if (policy.impliedOpenDmWildcard && !warnedOpenDmWildcard.has(liveAccount.accountId)) {
|
|
6723
|
+
warnedOpenDmWildcard.add(liveAccount.accountId);
|
|
6724
|
+
log?.info?.(
|
|
6725
|
+
`[wechat:${liveAccount.accountId}] dmPolicy is "open" but allowFrom has no "*"; treating it as ["*"]. Add "*" to channels.agent-wechat.allowFrom to make this explicit.`
|
|
6726
|
+
);
|
|
6727
|
+
}
|
|
6728
|
+
const resolveIngress = getIngressResolver(core);
|
|
6729
|
+
const admitSender = async ({ isGroup: isGroup2, senderId }) => {
|
|
6730
|
+
const result = await resolveWeChatIngress({
|
|
6731
|
+
resolve: resolveIngress,
|
|
6732
|
+
cfg,
|
|
6733
|
+
accountId: liveAccount.accountId,
|
|
6734
|
+
policy,
|
|
6735
|
+
isGroup: isGroup2,
|
|
6736
|
+
chatId,
|
|
6737
|
+
senderId,
|
|
6738
|
+
readStoreAllowFrom: async () => storeAllowFrom
|
|
6739
|
+
});
|
|
6740
|
+
return {
|
|
6741
|
+
allowed: result.ingress.admission === "dispatch",
|
|
6742
|
+
reason: result.ingress.reasonCode
|
|
6743
|
+
};
|
|
6744
|
+
};
|
|
6745
6745
|
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
6746
6746
|
cfg,
|
|
6747
6747
|
surface: "agent-wechat"
|
|
@@ -6808,7 +6808,7 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
|
|
|
6808
6808
|
log?.info?.(
|
|
6809
6809
|
`[wechat:${liveAccount.accountId}] Processing msg ${msg.localId}: type=${msg.type}, sender=${msg.sender}, isSelf=${msg.isSelf}`
|
|
6810
6810
|
);
|
|
6811
|
-
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount,
|
|
6811
|
+
const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, admitSender, log);
|
|
6812
6812
|
if (pm) {
|
|
6813
6813
|
processed.push(pm);
|
|
6814
6814
|
}
|