gewe-openclaw 2026.3.23 → 2026.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gewe-openclaw",
3
- "version": "2026.3.23",
3
+ "version": "2026.3.24",
4
4
  "type": "module",
5
5
  "description": "OpenClaw GeWe channel plugin",
6
6
  "license": "MIT",
package/src/inbound.ts CHANGED
@@ -113,6 +113,25 @@ function resolveAppMsgPlaceholder(appType?: number): string {
113
113
  return typeof appType === "number" ? `<appmsg:${appType}>` : "<appmsg>";
114
114
  }
115
115
 
116
+ function summarizeUnsupportedInboundMessage(message: GeweInboundMessage): string {
117
+ const preview = message.text.replace(/\s+/g, " ").trim().slice(0, 120);
118
+ const parts = [
119
+ `msgType=${message.msgType}`,
120
+ `from=${message.fromId}`,
121
+ `to=${message.toId}`,
122
+ `sender=${message.senderId}`,
123
+ `messageId=${message.messageId}`,
124
+ `newMessageId=${message.newMessageId}`,
125
+ preview ? `text=${JSON.stringify(preview)}` : undefined,
126
+ ];
127
+ return parts.filter(Boolean).join(" ");
128
+ }
129
+
130
+ function summarizeTextPreview(text: string): string | undefined {
131
+ const preview = text.replace(/\s+/g, " ").trim().slice(0, 160);
132
+ return preview ? JSON.stringify(preview) : undefined;
133
+ }
134
+
116
135
  function resolveGewePairCodeCandidate(rawBody: string): string | null {
117
136
  const trimmed = rawBody.trim();
118
137
  if (!trimmed) return null;
@@ -342,7 +361,7 @@ function normalizeInboundEntry(params: {
342
361
  const { message, runtime } = params;
343
362
  const msgType = message.msgType;
344
363
  if (![1, 3, 34, 43, 49].includes(msgType)) {
345
- runtime.log?.(`gewe: skip unsupported msgType ${msgType}`);
364
+ runtime.log?.(`gewe: skip unsupported ${summarizeUnsupportedInboundMessage(message)}`);
346
365
  return null;
347
366
  }
348
367
 
@@ -834,10 +853,33 @@ export async function handleGeweInboundBatch(params: {
834
853
  return;
835
854
  }
836
855
 
837
- const mentionRegexes = core.channel.mentions.buildMentionRegexes(config as OpenClawConfig);
838
- const wasAtTriggered = mentionRegexes.length
856
+ const route = core.channel.routing.resolveAgentRoute({
857
+ cfg: config as OpenClawConfig,
858
+ channel: CHANNEL_ID,
859
+ accountId: account.accountId,
860
+ peer: {
861
+ kind: isGroup ? "group" : "direct",
862
+ id: isGroup ? groupId ?? "" : senderId,
863
+ },
864
+ });
865
+ const mentionRegexes = core.channel.mentions.buildMentionRegexes(
866
+ config as OpenClawConfig,
867
+ route.agentId,
868
+ );
869
+ const nativeAtWxids = Array.from(
870
+ new Set(
871
+ entries
872
+ .flatMap((entry) => entry.message.atWxids ?? [])
873
+ .map((wxid) => wxid.trim())
874
+ .filter(Boolean),
875
+ ),
876
+ );
877
+ const nativeAtAll = entries.some((entry) => entry.message.atAll === true);
878
+ const nativeAtTriggered = nativeAtWxids.includes(lastMessage.botWxid.trim());
879
+ const regexAtTriggered = mentionRegexes.length
839
880
  ? core.channel.mentions.matchesMentionPatterns(rawBodyCandidate, mentionRegexes)
840
881
  : false;
882
+ const wasAtTriggered = nativeAtTriggered || regexAtTriggered;
841
883
  const latestQuote = entries.at(-1)?.quoteDetails;
842
884
  const wasQuoteTriggered = Boolean(
843
885
  latestQuote &&
@@ -862,23 +904,32 @@ export async function handleGeweInboundBatch(params: {
862
904
  commandAuthorized,
863
905
  });
864
906
  if (triggerGate.shouldSkip) {
907
+ const detail =
908
+ triggerMode === "at"
909
+ ? [
910
+ `agent=${route.agentId ?? "default"}`,
911
+ `wasAtTriggered=${String(wasAtTriggered)}`,
912
+ `nativeAtTriggered=${String(nativeAtTriggered)}`,
913
+ `nativeAtAll=${String(nativeAtAll)}`,
914
+ `regexAtTriggered=${String(regexAtTriggered)}`,
915
+ `wasQuoteTriggered=${String(wasQuoteTriggered)}`,
916
+ `mentionRegexes=${JSON.stringify(mentionRegexes.map((regex) => regex.source))}`,
917
+ `nativeAtWxids=${JSON.stringify(nativeAtWxids)}`,
918
+ summarizeTextPreview(rawBodyCandidate)
919
+ ? `rawBody=${summarizeTextPreview(rawBodyCandidate)}`
920
+ : undefined,
921
+ ]
922
+ .filter(Boolean)
923
+ .join(" ")
924
+ : undefined;
865
925
  runtime.log?.(
866
926
  isGroup
867
- ? `gewe: drop group ${groupId} (trigger=${triggerMode})`
868
- : `gewe: drop DM sender ${senderId} (trigger=${triggerMode})`,
927
+ ? `gewe: drop group ${groupId} (trigger=${triggerMode})${detail ? ` ${detail}` : ""}`
928
+ : `gewe: drop DM sender ${senderId} (trigger=${triggerMode})${detail ? ` ${detail}` : ""}`,
869
929
  );
870
930
  return;
871
931
  }
872
932
 
873
- const route = core.channel.routing.resolveAgentRoute({
874
- cfg: config as OpenClawConfig,
875
- channel: CHANNEL_ID,
876
- accountId: account.accountId,
877
- peer: {
878
- kind: isGroup ? "group" : "direct",
879
- id: isGroup ? groupId ?? "" : senderId,
880
- },
881
- });
882
933
  const storePath = core.channel.session.resolveStorePath(config.session?.store, {
883
934
  agentId: route.agentId,
884
935
  });
package/src/monitor.ts CHANGED
@@ -23,6 +23,7 @@ import type {
23
23
  GeweWebhookServerOptions,
24
24
  ResolvedGeweAccount,
25
25
  } from "./types.js";
26
+ import { extractAtUserList } from "./xml.js";
26
27
 
27
28
  const DEFAULT_WEBHOOK_PORT = 4399;
28
29
  const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
@@ -155,6 +156,8 @@ function payloadToInboundMessage(payload: GeweCallbackPayload): GeweInboundMessa
155
156
  const groupParsed = isGroupChat ? splitGroupContent(content) : { body: content };
156
157
  const senderId = (isGroupChat ? groupParsed.senderId : fromId) ?? fromId;
157
158
  const text = groupParsed.body?.trim() ?? "";
159
+ const atWxids = extractAtUserList(data.MsgSource);
160
+ const atAll = atWxids.includes("notify@all");
158
161
 
159
162
  return {
160
163
  messageId: String(msgId),
@@ -166,6 +169,8 @@ function payloadToInboundMessage(payload: GeweCallbackPayload): GeweInboundMessa
166
169
  senderId,
167
170
  senderName: resolveSenderName(data.PushContent),
168
171
  text,
172
+ atWxids: atWxids.length ? atWxids : undefined,
173
+ atAll,
169
174
  msgType,
170
175
  xml: text,
171
176
  timestamp,
@@ -178,7 +183,7 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
178
183
  start: () => Promise<void>;
179
184
  stop: () => void;
180
185
  } {
181
- const { port, host, path, mediaPath, secret, onMessage, onError, abortSignal } = opts;
186
+ const { port, host, path, mediaPath, secret, onRawPayload, onMessage, onError, abortSignal } = opts;
182
187
 
183
188
  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
184
189
  if (req.url === HEALTH_PATH) {
@@ -230,6 +235,7 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
230
235
  return;
231
236
  }
232
237
 
238
+ onRawPayload?.(bodyResult.raw);
233
239
  const payload = parseWebhookPayload(bodyResult.raw);
234
240
  if (!payload) {
235
241
  res.writeHead(400, { "Content-Type": "application/json" });
@@ -357,6 +363,7 @@ export async function monitorGeweProvider(
357
363
  path,
358
364
  mediaPath: shouldStartMedia ? mediaPath : undefined,
359
365
  secret,
366
+ onRawPayload: (raw) => runtime.log?.(`[${account.accountId}] GeWe webhook raw: ${raw}`),
360
367
  onMessage: async (message) => {
361
368
  const isSelf = message.fromId === message.botWxid || message.senderId === message.botWxid;
362
369
  if (isSelf) return;
package/src/types.ts CHANGED
@@ -175,6 +175,7 @@ export type GeweCallbackPayload = {
175
175
  ToUserName?: { string?: string };
176
176
  MsgType?: number;
177
177
  Content?: { string?: string };
178
+ MsgSource?: string;
178
179
  CreateTime?: number;
179
180
  PushContent?: string;
180
181
  };
@@ -190,6 +191,8 @@ export type GeweInboundMessage = {
190
191
  senderId: string;
191
192
  senderName?: string;
192
193
  text: string;
194
+ atWxids?: string[];
195
+ atAll?: boolean;
193
196
  msgType: number;
194
197
  xml?: string;
195
198
  timestamp: number;
@@ -202,6 +205,7 @@ export type GeweWebhookServerOptions = {
202
205
  path: string;
203
206
  mediaPath?: string;
204
207
  secret?: string;
208
+ onRawPayload?: (raw: string) => void;
205
209
  onMessage: (message: GeweInboundMessage) => void | Promise<void>;
206
210
  onError?: (error: Error) => void;
207
211
  abortSignal?: AbortSignal;
package/src/xml.ts CHANGED
@@ -120,6 +120,24 @@ export function extractXmlTag(xml: string, tag: string): string | undefined {
120
120
  return decodeEntities(raw);
121
121
  }
122
122
 
123
+ export function extractAtUserList(xml?: string): string[] {
124
+ const atUserList = xml?.trim() ? extractXmlTag(xml, "atuserlist") : undefined;
125
+ if (!atUserList) return [];
126
+
127
+ const seen = new Set<string>();
128
+ const values = atUserList
129
+ .split(/[,\uFF0C;\s]+/)
130
+ .map((value) => value.trim())
131
+ .filter(Boolean)
132
+ .filter((value) => {
133
+ if (seen.has(value)) return false;
134
+ seen.add(value);
135
+ return true;
136
+ });
137
+
138
+ return values;
139
+ }
140
+
123
141
  export function extractAppMsgType(xml: string): number | undefined {
124
142
  const match = /<appmsg[\s\S]*?<type>(\d+)<\/type>/i.exec(xml);
125
143
  if (!match?.[1]) return undefined;