gewe-openclaw 2026.3.13 → 2026.3.23

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 (44) hide show
  1. package/README.md +455 -3
  2. package/index.ts +39 -1
  3. package/package.json +12 -1
  4. package/skills/gewe-agent-tools/SKILL.md +113 -0
  5. package/skills/gewe-channel-rules/SKILL.md +7 -0
  6. package/src/accounts.ts +51 -5
  7. package/src/api-tools.ts +1264 -0
  8. package/src/api.ts +37 -2
  9. package/src/binary-command.ts +65 -0
  10. package/src/channel-actions.ts +536 -0
  11. package/src/channel-allowlist.ts +150 -0
  12. package/src/channel-directory.ts +419 -0
  13. package/src/channel-status.ts +186 -0
  14. package/src/channel.ts +155 -58
  15. package/src/config-edit.ts +94 -0
  16. package/src/config-schema.ts +78 -3
  17. package/src/contacts-api.ts +113 -0
  18. package/src/delivery.ts +502 -62
  19. package/src/directory-cache.ts +164 -0
  20. package/src/gewe-account-api.ts +27 -0
  21. package/src/group-allowlist-tool.ts +242 -0
  22. package/src/group-binding-tool.ts +154 -0
  23. package/src/group-binding.ts +405 -0
  24. package/src/groups-api.ts +146 -0
  25. package/src/inbound-batch.ts +5 -2
  26. package/src/inbound.ts +248 -41
  27. package/src/media-server.ts +73 -93
  28. package/src/moments-api.ts +138 -0
  29. package/src/monitor.ts +81 -24
  30. package/src/onboarding.ts +9 -4
  31. package/src/openclaw-compat.ts +1070 -0
  32. package/src/pairing-store.ts +478 -0
  33. package/src/personal-api.ts +45 -0
  34. package/src/policy.ts +130 -22
  35. package/src/quote-context-cache.ts +97 -0
  36. package/src/reply-options.ts +101 -2
  37. package/src/s3.ts +1 -1
  38. package/src/send.ts +235 -16
  39. package/src/setup-wizard-types.ts +162 -0
  40. package/src/setup-wizard.ts +464 -0
  41. package/src/silk.ts +2 -1
  42. package/src/state-paths.ts +55 -14
  43. package/src/types.ts +66 -7
  44. package/src/xml.ts +158 -0
@@ -0,0 +1,138 @@
1
+ import {
2
+ createGeweAccountMethod,
3
+ type GeweApiList,
4
+ type GeweApiObject,
5
+ } from "./gewe-account-api.js";
6
+
7
+ type GeweTagId = string | number;
8
+
9
+ type GeweSnsVisibilityParams = {
10
+ allowWxIds?: string[];
11
+ atWxIds?: string[];
12
+ disableWxIds?: string[];
13
+ privacy?: number;
14
+ allowTagIds?: GeweTagId[];
15
+ disableTagIds?: GeweTagId[];
16
+ };
17
+
18
+ export type GeweSnsImageInfo = GeweApiObject;
19
+ export type GeweSnsVideoInfo = GeweApiObject;
20
+
21
+ export const uploadSnsImageGewe = createGeweAccountMethod<{
22
+ imgUrls: string[];
23
+ }>("/gewe/v2/api/sns/uploadSnsImage");
24
+
25
+ export const uploadSnsVideoGewe = createGeweAccountMethod<{
26
+ thumbUrl: string;
27
+ videoUrl: string;
28
+ }>("/gewe/v2/api/sns/uploadSnsVideo");
29
+
30
+ export const downloadSnsVideoGewe = createGeweAccountMethod<{
31
+ snsXml: string;
32
+ }>("/gewe/v2/api/sns/downloadSnsVideo");
33
+
34
+ export const delSnsGewe = createGeweAccountMethod<{
35
+ snsId: string;
36
+ }>("/gewe/v2/api/sns/delSns");
37
+
38
+ export const sendImgSnsGewe = createGeweAccountMethod<
39
+ GeweSnsVisibilityParams & {
40
+ content?: string;
41
+ imgInfos: GeweSnsImageInfo[];
42
+ }
43
+ >("/gewe/v2/api/sns/sendImgSns");
44
+
45
+ export const sendTextSnsGewe = createGeweAccountMethod<
46
+ GeweSnsVisibilityParams & {
47
+ content: string;
48
+ }
49
+ >("/gewe/v2/api/sns/sendTextSns");
50
+
51
+ export const sendVideoSnsGewe = createGeweAccountMethod<
52
+ GeweSnsVisibilityParams & {
53
+ content?: string;
54
+ videoInfo: GeweSnsVideoInfo;
55
+ }
56
+ >("/gewe/v2/api/sns/sendVideoSns");
57
+
58
+ export const sendUrlSnsGewe = createGeweAccountMethod<
59
+ GeweSnsVisibilityParams & {
60
+ content?: string;
61
+ thumbUrl: string;
62
+ linkUrl: string;
63
+ title: string;
64
+ description: string;
65
+ }
66
+ >("/gewe/v2/api/sns/sendUrlSns");
67
+
68
+ export const strangerVisibilityEnabledGewe = createGeweAccountMethod<{
69
+ enabled: boolean;
70
+ }>("/gewe/v2/api/sns/strangerVisibilityEnabled");
71
+
72
+ export const snsDetailsGewe = createGeweAccountMethod<{
73
+ snsId: string;
74
+ }>("/gewe/v2/api/sns/snsDetails");
75
+
76
+ export const likeSnsGewe = createGeweAccountMethod<{
77
+ snsId: string;
78
+ operType: number;
79
+ wxid: string;
80
+ }>("/gewe/v2/api/sns/likeSns");
81
+
82
+ export const contactsSnsListGewe = createGeweAccountMethod<{
83
+ wxid: string;
84
+ maxId?: string | number;
85
+ decrypt?: boolean;
86
+ firstPageMd5?: string;
87
+ }>("/gewe/v2/api/sns/contactsSnsList");
88
+
89
+ export const snsListGewe = createGeweAccountMethod<{
90
+ maxId?: string | number;
91
+ decrypt?: boolean;
92
+ firstPageMd5?: string;
93
+ }>("/gewe/v2/api/sns/snsList");
94
+
95
+ export const snsVisibleScopeGewe = createGeweAccountMethod<{
96
+ option: number;
97
+ }>("/gewe/v2/api/sns/snsVisibleScope");
98
+
99
+ export const snsSetPrivacyGewe = createGeweAccountMethod<{
100
+ snsId: string;
101
+ open: boolean;
102
+ }>("/gewe/v2/api/sns/snsSetPrivacy");
103
+
104
+ export const commentSnsGewe = createGeweAccountMethod<{
105
+ snsId: string;
106
+ operType: number;
107
+ wxid: string;
108
+ commentId?: string | number;
109
+ content?: string;
110
+ }>("/gewe/v2/api/sns/commentSns");
111
+
112
+ export const forwardSnsGewe = createGeweAccountMethod<
113
+ Pick<GeweSnsVisibilityParams, "allowWxIds" | "atWxIds" | "disableWxIds" | "privacy"> & {
114
+ snsXml: string;
115
+ }
116
+ >("/gewe/v2/api/sns/forwardSns");
117
+
118
+ export const geweMomentsApi = {
119
+ uploadSnsImage: uploadSnsImageGewe,
120
+ uploadSnsVideo: uploadSnsVideoGewe,
121
+ downloadSnsVideo: downloadSnsVideoGewe,
122
+ delSns: delSnsGewe,
123
+ sendImgSns: sendImgSnsGewe,
124
+ sendTextSns: sendTextSnsGewe,
125
+ sendVideoSns: sendVideoSnsGewe,
126
+ sendUrlSns: sendUrlSnsGewe,
127
+ strangerVisibilityEnabled: strangerVisibilityEnabledGewe,
128
+ snsDetails: snsDetailsGewe,
129
+ likeSns: likeSnsGewe,
130
+ contactsSnsList: contactsSnsListGewe,
131
+ snsList: snsListGewe,
132
+ snsVisibleScope: snsVisibleScopeGewe,
133
+ snsSetPrivacy: snsSetPrivacyGewe,
134
+ commentSns: commentSnsGewe,
135
+ forwardSns: forwardSnsGewe,
136
+ };
137
+
138
+ export type GeweSnsList = GeweApiList;
package/src/monitor.ts CHANGED
@@ -1,12 +1,20 @@
1
1
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
2
 
3
- import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
3
+ import {
4
+ DEFAULT_WEBHOOK_BODY_TIMEOUT_MS,
5
+ DEFAULT_WEBHOOK_MAX_BODY_BYTES,
6
+ readJsonBodyWithLimit,
7
+ type OpenClawConfig,
8
+ type RuntimeEnv,
9
+ } from "./openclaw-compat.js";
4
10
 
5
11
  import { resolveGeweAccount } from "./accounts.js";
12
+ import { parseGeweJsonText } from "./api.js";
6
13
  import { GeweDownloadQueue } from "./download-queue.js";
7
14
  import { createGeweInboundDebouncer } from "./inbound-batch.js";
8
15
  import { handleGeweInboundBatch } from "./inbound.js";
9
16
  import { createGeweMediaServer, DEFAULT_MEDIA_HOST, DEFAULT_MEDIA_PATH, DEFAULT_MEDIA_PORT } from "./media-server.js";
17
+ import { maybeHandleGeweMediaRequest } from "./media-server.js";
10
18
  import { getGeweRuntime } from "./runtime.js";
11
19
  import type {
12
20
  CoreConfig,
@@ -40,20 +48,18 @@ function isDuplicate(key: string): boolean {
40
48
  return false;
41
49
  }
42
50
 
51
+ export function buildGeweInboundDedupeKey(params: {
52
+ accountId: string;
53
+ message: Pick<GeweInboundMessage, "appId" | "newMessageId">;
54
+ }): string {
55
+ return `${params.accountId}:${params.message.appId}:${params.message.newMessageId}`;
56
+ }
57
+
43
58
  function formatError(err: unknown): string {
44
59
  if (err instanceof Error) return err.message;
45
60
  return typeof err === "string" ? err : JSON.stringify(err);
46
61
  }
47
62
 
48
- function readBody(req: IncomingMessage): Promise<string> {
49
- return new Promise((resolve, reject) => {
50
- const chunks: Buffer[] = [];
51
- req.on("data", (chunk: Buffer) => chunks.push(chunk));
52
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
53
- req.on("error", reject);
54
- });
55
- }
56
-
57
63
  function resolveWebhookToken(req: IncomingMessage): string | undefined {
58
64
  const headers = req.headers as Record<string, string | string[] | undefined>;
59
65
  const candidates = [
@@ -122,7 +128,7 @@ function resolveSenderName(pushContent?: string): string | undefined {
122
128
 
123
129
  function parseWebhookPayload(body: string): GeweCallbackPayload | null {
124
130
  try {
125
- const data = JSON.parse(body);
131
+ const data = parseGeweJsonText<GeweCallbackPayload>(body);
126
132
  return data as GeweCallbackPayload;
127
133
  } catch {
128
134
  return null;
@@ -172,7 +178,7 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
172
178
  start: () => Promise<void>;
173
179
  stop: () => void;
174
180
  } {
175
- const { port, host, path, secret, onMessage, onError, abortSignal } = opts;
181
+ const { port, host, path, mediaPath, secret, onMessage, onError, abortSignal } = opts;
176
182
 
177
183
  const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
178
184
  if (req.url === HEALTH_PATH) {
@@ -181,6 +187,17 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
181
187
  return;
182
188
  }
183
189
 
190
+ if (
191
+ mediaPath &&
192
+ (await maybeHandleGeweMediaRequest({
193
+ req,
194
+ res,
195
+ path: mediaPath,
196
+ }))
197
+ ) {
198
+ return;
199
+ }
200
+
184
201
  if (req.url?.split("?")[0] !== path || req.method !== "POST") {
185
202
  res.writeHead(404);
186
203
  res.end();
@@ -195,8 +212,25 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
195
212
  }
196
213
 
197
214
  try {
198
- const body = await readBody(req);
199
- const payload = parseWebhookPayload(body);
215
+ const bodyResult = await readJsonBodyWithLimit(req, {
216
+ maxBytes: DEFAULT_WEBHOOK_MAX_BODY_BYTES,
217
+ timeoutMs: DEFAULT_WEBHOOK_BODY_TIMEOUT_MS,
218
+ emptyObjectOnEmpty: false,
219
+ });
220
+ if (!bodyResult.ok) {
221
+ res.writeHead(
222
+ bodyResult.code === "PAYLOAD_TOO_LARGE"
223
+ ? 413
224
+ : bodyResult.code === "REQUEST_BODY_TIMEOUT"
225
+ ? 408
226
+ : 400,
227
+ { "Content-Type": "application/json" },
228
+ );
229
+ res.end(JSON.stringify({ error: bodyResult.error || "Invalid JSON payload" }));
230
+ return;
231
+ }
232
+
233
+ const payload = parseWebhookPayload(bodyResult.raw);
200
234
  if (!payload) {
201
235
  res.writeHead(400, { "Content-Type": "application/json" });
202
236
  res.end(JSON.stringify({ error: "Invalid JSON payload" }));
@@ -228,8 +262,20 @@ export function createGeweWebhookServer(opts: GeweWebhookServerOptions): {
228
262
  });
229
263
 
230
264
  const start = (): Promise<void> => {
231
- return new Promise((resolve) => {
232
- server.listen(port, host, () => resolve());
265
+ return new Promise((resolve, reject) => {
266
+ const onError = (err: Error) => {
267
+ reject(err);
268
+ };
269
+ server.once("error", onError);
270
+ try {
271
+ server.listen(port, host, () => {
272
+ server.off("error", onError);
273
+ resolve();
274
+ });
275
+ } catch (err) {
276
+ server.off("error", onError);
277
+ reject(err);
278
+ }
233
279
  });
234
280
  };
235
281
 
@@ -259,9 +305,12 @@ export async function monitorGeweProvider(
259
305
  const core = getGeweRuntime();
260
306
  const cfg = opts.config ?? (core.config.loadConfig() as CoreConfig);
261
307
  const account = opts.account ?? resolveGeweAccount({ cfg, accountId: opts.accountId });
308
+ const fallbackLogger = core.logging.getChildLogger();
309
+ const formatRuntimeArgs = (args: unknown[]) =>
310
+ args.map((arg) => (typeof arg === "string" ? arg : String(arg))).join(" ");
262
311
  const runtime: RuntimeEnv = opts.runtime ?? {
263
- log: (message: string) => core.logging.getChildLogger().info(message),
264
- error: (message: string) => core.logging.getChildLogger().error(message),
312
+ log: (...args: unknown[]) => fallbackLogger.info(formatRuntimeArgs(args)),
313
+ error: (...args: unknown[]) => fallbackLogger.error(formatRuntimeArgs(args)),
265
314
  exit: () => {
266
315
  throw new Error("Runtime exit not available");
267
316
  },
@@ -276,6 +325,10 @@ export async function monitorGeweProvider(
276
325
  const rawPath = account.config.webhookPath?.trim() || DEFAULT_WEBHOOK_PATH;
277
326
  const path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
278
327
  const secret = account.config.webhookSecret?.trim() || undefined;
328
+ const shouldStartMedia =
329
+ Boolean(account.config.mediaPublicUrl) ||
330
+ Boolean(account.config.mediaPort || account.config.mediaHost || account.config.mediaPath);
331
+ const mediaPath = account.config.mediaPath ?? DEFAULT_MEDIA_PATH;
279
332
 
280
333
  const downloadQueue = new GeweDownloadQueue({
281
334
  minDelayMs: account.config.downloadMinDelayMs,
@@ -302,12 +355,16 @@ export async function monitorGeweProvider(
302
355
  port,
303
356
  host,
304
357
  path,
358
+ mediaPath: shouldStartMedia ? mediaPath : undefined,
305
359
  secret,
306
360
  onMessage: async (message) => {
307
361
  const isSelf = message.fromId === message.botWxid || message.senderId === message.botWxid;
308
362
  if (isSelf) return;
309
363
 
310
- const dedupeKey = `${message.appId}:${message.newMessageId}`;
364
+ const dedupeKey = buildGeweInboundDedupeKey({
365
+ accountId: account.accountId,
366
+ message,
367
+ });
311
368
  if (isDuplicate(dedupeKey)) return;
312
369
  opts.statusSink?.({ lastInboundAt: Date.now() });
313
370
 
@@ -318,18 +375,18 @@ export async function monitorGeweProvider(
318
375
  });
319
376
 
320
377
  await webhookServer.start();
321
- runtime.log?.(`[${account.accountId}] GeWe webhook server listening on ${host}:${port}${path}`);
378
+ const webhookPublicUrl =
379
+ account.config.webhookPublicUrl?.trim() ||
380
+ `http://${host === "0.0.0.0" ? "localhost" : host}:${port}${path}`;
381
+ runtime.log?.(`[${account.accountId}] GeWe webhook server listening on ${webhookPublicUrl}`);
322
382
 
323
383
  let mediaStop: (() => void) | undefined;
324
- const shouldStartMedia =
325
- Boolean(account.config.mediaPublicUrl) ||
326
- Boolean(account.config.mediaPort || account.config.mediaHost || account.config.mediaPath);
327
384
 
328
385
  if (shouldStartMedia) {
329
386
  const mediaServer = createGeweMediaServer({
330
387
  host: account.config.mediaHost ?? DEFAULT_MEDIA_HOST,
331
388
  port: account.config.mediaPort ?? DEFAULT_MEDIA_PORT,
332
- path: account.config.mediaPath ?? DEFAULT_MEDIA_PATH,
389
+ path: mediaPath,
333
390
  abortSignal: opts.abortSignal,
334
391
  });
335
392
  await mediaServer.start();
package/src/onboarding.ts CHANGED
@@ -1,5 +1,10 @@
1
- import type { ChannelPlugin, OpenClawConfig, WizardPrompter } from "openclaw/plugin-sdk";
2
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
1
+ import {
2
+ DEFAULT_ACCOUNT_ID,
3
+ normalizeAccountId,
4
+ type ChannelPlugin,
5
+ type OpenClawConfig,
6
+ type WizardPrompter,
7
+ } from "./openclaw-compat.js";
3
8
 
4
9
  import type { CoreConfig, GeweAccountConfig, ResolvedGeweAccount } from "./types.js";
5
10
  import { resolveGeweAccount, resolveDefaultGeweAccountId, listGeweAccountIds } from "./accounts.js";
@@ -265,14 +270,14 @@ export const geweOnboarding: GeweOnboardingAdapter = {
265
270
  placeholder: "gewe-openclaw/outbound",
266
271
  initialValue: existing.s3KeyPrefix,
267
272
  });
268
- const s3UrlMode = await ctx.prompter.select({
273
+ const s3UrlMode = (await ctx.prompter.select({
269
274
  message: "S3 URL mode",
270
275
  options: [
271
276
  { value: "public", label: "public (default)" },
272
277
  { value: "presigned", label: "presigned" },
273
278
  ],
274
279
  initialValue: existing.s3UrlMode ?? "public",
275
- });
280
+ })) as NonNullable<GeweAccountConfig["s3UrlMode"]>;
276
281
  const s3PublicBaseUrl =
277
282
  s3UrlMode === "public"
278
283
  ? await ctx.prompter.text({