@agent-wechat/agent-wechat 0.15.5 → 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.
Files changed (3) hide show
  1. package/README.md +8 -2
  2. package/dist/index.js +116 -109
  3. package/package.json +2 -2
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[] | `[]` | wxid allowlist for DMs (when policy is `allowlist`) |
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[] | `[]` | Global allowlist of group sender IDs (`wxid_...`) |
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
@@ -1083,7 +1083,7 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core";
1083
1083
 
1084
1084
  // src/channel.ts
1085
1085
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID2 } from "openclaw/plugin-sdk/account-id";
1086
- import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-message";
1086
+ import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
1087
1087
 
1088
1088
  // src/types.ts
1089
1089
  function normalizeDmPolicy(policy) {
@@ -5538,7 +5538,7 @@ var agentConfigSchema = external_exports.object({
5538
5538
  });
5539
5539
 
5540
5540
  // src/monitor.ts
5541
- import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
5541
+ import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
5542
5542
 
5543
5543
  // src/media-delivery.ts
5544
5544
  var MIME_BY_FORMAT = {
@@ -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,
@@ -5945,8 +5948,7 @@ import {
5945
5948
  import {
5946
5949
  resolveAllowlistProviderRuntimeGroupPolicy,
5947
5950
  resolveDefaultGroupPolicy
5948
- } from "openclaw/plugin-sdk/config-runtime";
5949
- import { resolveSenderCommandAuthorization } from "openclaw/plugin-sdk/command-auth";
5951
+ } from "openclaw/plugin-sdk/runtime-group-policy";
5950
5952
  var INVISIBLE_TEXT_RE = /[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g;
5951
5953
  var WECHAT_MENTION_START_RE = /^[@@]/u;
5952
5954
  function unique(values) {
@@ -6022,16 +6024,6 @@ function normalizeWeChatCommandBody(raw, params) {
6022
6024
  }
6023
6025
  return trimmed;
6024
6026
  }
6025
- function isWeChatSenderAllowed(senderId, allowFrom) {
6026
- if (allowFrom.includes("*")) {
6027
- return true;
6028
- }
6029
- const normalizedSender = senderId ? normalizeWeChatId(senderId) : "";
6030
- if (!normalizedSender) {
6031
- return false;
6032
- }
6033
- return allowFrom.includes(normalizedSender);
6034
- }
6035
6027
  function resolveGroupEntry(params) {
6036
6028
  const groups = params.account.groups ?? {};
6037
6029
  const normalizedChatId = normalizeWeChatId(params.chatId);
@@ -6048,13 +6040,6 @@ function resolveGroupEntry(params) {
6048
6040
  };
6049
6041
  }
6050
6042
  function resolveWeChatPolicyContext(params) {
6051
- const dmPolicy = normalizeDmPolicy2(params.account.dmPolicy);
6052
- const configuredAllowFrom = normalizeWeChatAllowFrom(params.account.allowFrom);
6053
- const configuredGroupAllowFrom = normalizeWeChatAllowFrom(params.account.groupAllowFrom);
6054
- const normalizedStoreAllowFrom = dmPolicy === "allowlist" ? [] : normalizeWeChatAllowFrom(params.storeAllowFrom);
6055
- const effectiveAllowFrom = unique([...configuredAllowFrom, ...normalizedStoreAllowFrom]);
6056
- const groupBase = configuredGroupAllowFrom.length > 0 ? configuredGroupAllowFrom : configuredAllowFrom;
6057
- const effectiveGroupAllowFrom = unique([...groupBase, ...normalizedStoreAllowFrom]);
6058
6043
  const { groupEntry, wildcardEntry } = resolveGroupEntry({
6059
6044
  account: params.account,
6060
6045
  chatId: params.chatId
@@ -6075,76 +6060,86 @@ function resolveWeChatPolicyContext(params) {
6075
6060
  defaultGroupPolicy
6076
6061
  )
6077
6062
  ) ?? fallbackGroupPolicy;
6078
- const groupAllowOverride = normalizeWeChatAllowFrom(
6063
+ const groupSenderAllowFrom = normalizeWeChatAllowFrom(
6079
6064
  firstDefined(groupEntry?.allowFrom, wildcardEntry?.allowFrom)
6080
6065
  );
6081
- const groupAllowFrom = groupAllowOverride.length > 0 ? unique([...groupAllowOverride, ...normalizedStoreAllowFrom]) : effectiveGroupAllowFrom;
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 };
6082
6077
  return {
6083
6078
  dmPolicy,
6084
6079
  groupPolicy,
6085
6080
  requireMention,
6086
6081
  groupEnabled,
6087
- effectiveAllowFrom,
6088
- effectiveGroupAllowFrom: groupAllowFrom
6082
+ ...scoped,
6083
+ ...groupSenderAllowFrom.length > 0 ? { groupSenderAllowFrom } : {}
6089
6084
  };
6090
6085
  }
6091
- function resolveWeChatInboundAccessDecision(params) {
6092
- if (params.isGroup) {
6093
- if (!params.policy.groupEnabled) {
6094
- return { allowed: false, reason: "group-config-disabled" };
6095
- }
6096
- if (params.policy.groupPolicy === "disabled") {
6097
- return { allowed: false, reason: "groupPolicy=disabled" };
6098
- }
6099
- if (params.policy.groupPolicy === "allowlist") {
6100
- if (params.policy.effectiveGroupAllowFrom.length === 0) {
6101
- return { allowed: false, reason: "groupPolicy=allowlist (empty allowlist)" };
6102
- }
6103
- if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveGroupAllowFrom)) {
6104
- return { allowed: false, reason: "groupPolicy=allowlist (sender not allowlisted)" };
6105
- }
6106
- }
6107
- return { allowed: true, reason: `groupPolicy=${params.policy.groupPolicy}` };
6108
- }
6109
- if (params.policy.dmPolicy === "disabled") {
6110
- return { allowed: false, reason: "dmPolicy=disabled" };
6111
- }
6112
- if (params.policy.dmPolicy === "allowlist") {
6113
- if (params.policy.effectiveAllowFrom.length === 0) {
6114
- return { allowed: false, reason: "dmPolicy=allowlist (empty allowlist)" };
6115
- }
6116
- if (!isWeChatSenderAllowed(params.senderId, params.policy.effectiveAllowFrom)) {
6117
- return { allowed: false, reason: "dmPolicy=allowlist (sender not allowlisted)" };
6118
- }
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
+ });
6119
6105
  }
6120
- return { allowed: true, reason: `dmPolicy=${params.policy.dmPolicy}` };
6121
- }
6122
- async function resolveWeChatCommandAuthorization(params) {
6123
- const normalizedSenderId = normalizeWeChatId(params.senderId ?? "");
6124
- const { commandAuthorized } = await resolveSenderCommandAuthorization({
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,
6125
6113
  cfg: params.cfg,
6126
- rawBody: params.rawBody,
6127
- isGroup: params.isGroup,
6128
- dmPolicy: params.dmPolicy,
6129
- configuredAllowFrom: params.allowFromForCommands,
6130
- senderId: normalizedSenderId,
6131
- isSenderAllowed: (senderId, allowFrom) => isWeChatSenderAllowed(senderId, normalizeWeChatAllowFrom(allowFrom)),
6132
- readAllowFromStore: async () => normalizeWeChatAllowFrom(await params.deps.readAllowFromStore()),
6133
- shouldComputeCommandAuthorized: params.deps.shouldComputeCommandAuthorized,
6134
- resolveCommandAuthorizedFromAuthorizers: params.deps.resolveCommandAuthorizedFromAuthorizers
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
6135
6134
  });
6136
- return commandAuthorized;
6137
- }
6138
- function resolveWeChatMentionGate(params) {
6139
- const implicitMention = params.implicitMention === true;
6140
- const baseWasMentioned = params.wasMentioned || implicitMention;
6141
- const shouldBypassMention = params.isGroup && params.requireMention && !baseWasMentioned && params.allowTextCommands && params.hasControlCommand && params.commandAuthorized;
6142
- const effectiveWasMentioned = baseWasMentioned || shouldBypassMention;
6143
- const shouldSkip = params.requireMention && params.canDetectMention && !effectiveWasMentioned;
6144
- return { effectiveWasMentioned, shouldSkip, shouldBypassMention };
6145
6135
  }
6146
6136
 
6147
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
+ }
6148
6143
  var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
6149
6144
  var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
6150
6145
  var voiceTranscriptCache = /* @__PURE__ */ new Map();
@@ -6371,7 +6366,7 @@ async function startWeChatMonitor(opts) {
6371
6366
  connected: false
6372
6367
  });
6373
6368
  }
6374
- async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, log) {
6369
+ async function prepareMessage(client, msg, chatId, chat, liveAccount, admitSender, log) {
6375
6370
  if (msg.isSelf) {
6376
6371
  log?.info?.(`[wechat:${liveAccount.accountId}] Skipping self-sent msg ${msg.localId}`);
6377
6372
  return null;
@@ -6380,11 +6375,7 @@ async function prepareMessage(client, msg, chatId, chat, liveAccount, policy, lo
6380
6375
  const senderId = msg.sender ?? chatId;
6381
6376
  const senderName = msg.senderName ?? msg.sender ?? chat.name;
6382
6377
  const wasMentioned = isGroup && msg.isMentioned === true;
6383
- const access = resolveWeChatInboundAccessDecision({
6384
- isGroup,
6385
- senderId,
6386
- policy
6387
- });
6378
+ const access = await admitSender({ isGroup, senderId });
6388
6379
  if (!access.allowed) {
6389
6380
  log?.info?.(
6390
6381
  `[wechat:${liveAccount.accountId}] Blocked by policy (${access.reason}) from ${senderId}`
@@ -6475,40 +6466,34 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6475
6466
  `[wechat:${liveAccount.accountId}] Dispatching segment: ${segment.length} msg(s), last=${msg.localId}${mediaPath ? " with model media" : ""} attachments=${attachmentPaths.length}`
6476
6467
  );
6477
6468
  const hasControlCommand = allowTextCommands && core.channel.commands.isControlCommandMessage(commandBody, cfg);
6478
- const commandAuthorized = await resolveWeChatCommandAuthorization({
6469
+ const wasMentioned = segment.some((pm) => pm.isMentioned);
6470
+ const ingress = await resolveWeChatIngress({
6471
+ resolve: getIngressResolver(core),
6479
6472
  cfg,
6480
- rawBody: commandBody,
6473
+ accountId: liveAccount.accountId,
6474
+ policy,
6481
6475
  isGroup,
6476
+ chatId,
6482
6477
  senderId,
6483
- dmPolicy: policy.dmPolicy,
6484
- allowFromForCommands: isGroup ? policy.effectiveGroupAllowFrom : policy.effectiveAllowFrom,
6485
- deps: {
6486
- shouldComputeCommandAuthorized: (raw, loadedCfg) => core.channel.commands.shouldComputeCommandAuthorized(raw, loadedCfg),
6487
- resolveCommandAuthorizedFromAuthorizers: (params) => core.channel.commands.resolveCommandAuthorizedFromAuthorizers(params),
6488
- readAllowFromStore: async () => storeAllowFrom
6489
- }
6478
+ readStoreAllowFrom: async () => storeAllowFrom,
6479
+ command: { allowTextCommands, hasControlCommand },
6480
+ mention: { wasMentioned, allowTextCommands }
6490
6481
  });
6491
- if (isGroup && allowTextCommands && hasControlCommand && commandAuthorized !== true) {
6482
+ if (ingress.ingress.admission === "skip") {
6492
6483
  log?.info?.(
6493
- `[wechat:${liveAccount.accountId}] Dropping unauthorized group control command from ${senderId} in ${chatId}`
6484
+ `[wechat:${liveAccount.accountId}] Skipping group segment (${ingress.ingress.reasonCode}) in ${chatId}`
6494
6485
  );
6495
6486
  return "ignored";
6496
6487
  }
6497
- const mentionGate = resolveWeChatMentionGate({
6498
- isGroup,
6499
- requireMention: policy.requireMention,
6500
- canDetectMention: true,
6501
- wasMentioned: segment.some((pm) => pm.isMentioned),
6502
- allowTextCommands,
6503
- hasControlCommand,
6504
- commandAuthorized: commandAuthorized === true
6505
- });
6506
- if (isGroup && mentionGate.shouldSkip) {
6488
+ const dmUnauthorizedCommand = !isGroup && ingress.ingress.reasonCode === "control_command_unauthorized";
6489
+ if (ingress.ingress.admission !== "dispatch" && !dmUnauthorizedCommand) {
6507
6490
  log?.info?.(
6508
- `[wechat:${liveAccount.accountId}] Skipping group segment (mention required) in ${chatId}`
6491
+ `[wechat:${liveAccount.accountId}] Dropping segment (${ingress.ingress.reasonCode}) from ${senderId} in ${chatId}`
6509
6492
  );
6510
6493
  return "ignored";
6511
6494
  }
6495
+ const commandAuthorized = core.channel.commands.shouldComputeCommandAuthorized(commandBody, cfg) ? ingress.commandAccess.authorized : void 0;
6496
+ const effectiveWasMentioned = ingress.activationAccess.effectiveWasMentioned ?? wasMentioned;
6512
6497
  try {
6513
6498
  for (const item of segment) {
6514
6499
  const attachment = item.attachment;
@@ -6625,7 +6610,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6625
6610
  Provider: "agent-wechat",
6626
6611
  Surface: "agent-wechat",
6627
6612
  MessageSid: `wechat:${chatId}:${msg.localId}`,
6628
- WasMentioned: isGroup ? mentionGate.effectiveWasMentioned : void 0,
6613
+ WasMentioned: isGroup ? effectiveWasMentioned : void 0,
6629
6614
  CommandAuthorized: commandAuthorized,
6630
6615
  OriginatingChannel: "agent-wechat",
6631
6616
  OriginatingTo: `wechat:${chatId}`,
@@ -6646,7 +6631,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6646
6631
  );
6647
6632
  }
6648
6633
  });
6649
- const { onModelSelected, ...prefixOptions } = createChannelReplyPipeline({
6634
+ const { onModelSelected, ...prefixOptions } = createChannelMessageReplyPipeline({
6650
6635
  cfg,
6651
6636
  agentId: route.agentId,
6652
6637
  channel: "agent-wechat",
@@ -6732,9 +6717,31 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
6732
6717
  const policy = resolveWeChatPolicyContext({
6733
6718
  account: liveAccount,
6734
6719
  cfg,
6735
- chatId,
6736
- storeAllowFrom
6720
+ chatId
6737
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
+ };
6738
6745
  const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
6739
6746
  cfg,
6740
6747
  surface: "agent-wechat"
@@ -6801,7 +6808,7 @@ async function processUnreadChat(client, chat, lastSeenId, account, cfg, log, sk
6801
6808
  log?.info?.(
6802
6809
  `[wechat:${liveAccount.accountId}] Processing msg ${msg.localId}: type=${msg.type}, sender=${msg.sender}, isSelf=${msg.isSelf}`
6803
6810
  );
6804
- const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, policy, log);
6811
+ const pm = await prepareMessage(client, msg, chatId, chat, liveAccount, admitSender, log);
6805
6812
  if (pm) {
6806
6813
  processed.push(pm);
6807
6814
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-wechat/agent-wechat",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -9,7 +9,7 @@
9
9
  "devDependencies": {
10
10
  "@types/node": "^22",
11
11
  "esbuild": "^0.25.0",
12
- "openclaw": "^2026.8.1",
12
+ "openclaw": "^2026.10.2",
13
13
  "typescript": "^5.4.5",
14
14
  "@agent-wechat/shared": "0.3.1"
15
15
  },