gewe-openclaw 2026.1.30 → 2026.2.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/src/inbound.ts CHANGED
@@ -9,6 +9,7 @@ import type { GeweDownloadQueue } from "./download-queue.js";
9
9
  import { downloadGeweFile, downloadGeweImage, downloadGeweVideo, downloadGeweVoice } from "./download.js";
10
10
  import { deliverGewePayload } from "./delivery.js";
11
11
  import { getGeweRuntime } from "./runtime.js";
12
+ import { ensureRustSilkBinary } from "./silk.js";
12
13
  import {
13
14
  normalizeGeweAllowlist,
14
15
  resolveGeweAllowlistMatch,
@@ -19,8 +20,7 @@ import {
19
20
  } from "./policy.js";
20
21
  import type { CoreConfig, GeweInboundMessage, ResolvedGeweAccount } from "./types.js";
21
22
  import { extractAppMsgType, extractFileName, extractLinkDetails } from "./xml.js";
22
-
23
- const CHANNEL_ID = "gewe" as const;
23
+ import { CHANNEL_ID } from "./constants.js";
24
24
 
25
25
  type PreparedInbound = {
26
26
  rawBody: string;
@@ -103,7 +103,7 @@ async function decodeSilkVoice(params: {
103
103
  fileName?: string | null;
104
104
  }): Promise<DecodedVoice | null> {
105
105
  const core = getGeweRuntime();
106
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "voice" });
106
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "voice" });
107
107
  const decodeOutput = params.account.config.voiceDecodeOutput ?? "pcm";
108
108
  const sampleRate = resolveVoiceDecodeSampleRate(params.account);
109
109
  const ffmpegPath = params.account.config.voiceFfmpegPath?.trim() || "ffmpeg";
@@ -117,10 +117,28 @@ async function decodeSilkVoice(params: {
117
117
  ["{input}", "-o", "{output}"],
118
118
  ["-i", "{input}", "{output}"],
119
119
  ];
120
- const argTemplates = customArgs.length ? customArgs : fallbackArgs;
120
+ const rustArgs = [
121
+ "decode",
122
+ "-i",
123
+ "{input}",
124
+ "-o",
125
+ "{output}",
126
+ "--sample-rate",
127
+ "{sampleRate}",
128
+ "--quiet",
129
+ ];
130
+ if (decodeOutput === "wav") rustArgs.push("--wav");
131
+ const rustSilk = customPath ? null : await ensureRustSilkBinary(params.account);
132
+ const argTemplates = customArgs.length
133
+ ? customArgs
134
+ : rustSilk
135
+ ? [rustArgs]
136
+ : fallbackArgs;
121
137
  const candidates = customPath
122
138
  ? [customPath]
123
- : ["silk-decoder", "silk-v3-decoder", "decoder"];
139
+ : rustSilk
140
+ ? [rustSilk]
141
+ : ["silk-decoder", "silk-v3-decoder", "decoder"];
124
142
 
125
143
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gewe-voice-in-"));
126
144
  const silkPath = path.join(tmpDir, "voice.silk");
@@ -261,8 +279,10 @@ async function dispatchGeweInbound(params: {
261
279
  Body: body,
262
280
  RawBody: prepared.rawBody,
263
281
  CommandBody: prepared.rawBody,
264
- From: prepared.groupId ? `gewe:group:${prepared.groupId}` : `gewe:${prepared.senderId}`,
265
- To: `gewe:${prepared.toWxid}`,
282
+ From: prepared.groupId
283
+ ? `${CHANNEL_ID}:group:${prepared.groupId}`
284
+ : `${CHANNEL_ID}:${prepared.senderId}`,
285
+ To: `${CHANNEL_ID}:${prepared.toWxid}`,
266
286
  SessionKey: prepared.route.sessionKey,
267
287
  AccountId: prepared.route.accountId,
268
288
  ChatType: prepared.isGroup ? "group" : "direct",
@@ -272,16 +292,16 @@ async function dispatchGeweInbound(params: {
272
292
  SenderName: prepared.senderName || undefined,
273
293
  SenderId: prepared.senderId,
274
294
  CommandAuthorized: prepared.commandAuthorized,
275
- Provider: "gewe",
276
- Surface: "gewe",
295
+ Provider: CHANNEL_ID,
296
+ Surface: CHANNEL_ID,
277
297
  MessageSid: prepared.messageSid,
278
298
  MessageSidFull: prepared.messageSid,
279
299
  MediaPath: media?.path,
280
300
  MediaType: media?.contentType,
281
301
  MediaUrl: media?.path,
282
302
  GroupSystemPrompt: prepared.groupSystemPrompt,
283
- OriginatingChannel: "gewe",
284
- OriginatingTo: `gewe:${prepared.toWxid}`,
303
+ OriginatingChannel: CHANNEL_ID,
304
+ OriginatingTo: `${CHANNEL_ID}:${prepared.toWxid}`,
285
305
  });
286
306
 
287
307
  await core.channel.session.recordInboundSession({
@@ -523,7 +543,7 @@ export async function handleGeweInbound(params: {
523
543
  };
524
544
 
525
545
  core.channel.activity.record({
526
- channel: "gewe",
546
+ channel: CHANNEL_ID,
527
547
  accountId: account.accountId,
528
548
  direction: "inbound",
529
549
  });
package/src/monitor.ts CHANGED
@@ -15,9 +15,9 @@ import type {
15
15
  ResolvedGeweAccount,
16
16
  } from "./types.js";
17
17
 
18
- const DEFAULT_WEBHOOK_PORT = 18786;
18
+ const DEFAULT_WEBHOOK_PORT = 4399;
19
19
  const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
20
- const DEFAULT_WEBHOOK_PATH = "/gewe-webhook";
20
+ const DEFAULT_WEBHOOK_PATH = "/webhook";
21
21
  const HEALTH_PATH = "/healthz";
22
22
  const DEDUPE_TTL_MS = 12 * 60 * 60 * 1000;
23
23
 
package/src/normalize.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  export function normalizeGeweMessagingTarget(target: string): string | null {
2
2
  const trimmed = target.trim();
3
3
  if (!trimmed) return null;
4
+ const prefix = /^(gewe-openclaw|gewe|wechat|wx):(group:|user:)?/i;
4
5
  return trimmed
5
- .replace(/^gewe:(group:|user:)?/i, "")
6
- .replace(/^wechat:(group:|user:)?/i, "")
7
- .replace(/^wx:/i, "")
6
+ .replace(prefix, "")
8
7
  .trim();
9
8
  }
10
9
 
11
10
  export function looksLikeGeweTargetId(id: string): boolean {
12
11
  const trimmed = id?.trim();
13
12
  if (!trimmed) return false;
14
- if (/^gewe:/i.test(trimmed)) return true;
13
+ if (/^(gewe-openclaw|gewe):/i.test(trimmed)) return true;
15
14
  if (/@chatroom$/i.test(trimmed)) return true;
16
15
  if (/^wxid_/i.test(trimmed)) return true;
17
16
  if (/^gh_/i.test(trimmed)) return true;
@@ -0,0 +1,265 @@
1
+ import type { ChannelPlugin, OpenClawConfig, WizardPrompter } from "openclaw/plugin-sdk";
2
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
3
+
4
+ import type { CoreConfig, GeweAccountConfig, ResolvedGeweAccount } from "./types.js";
5
+ import { resolveGeweAccount, resolveDefaultGeweAccountId, listGeweAccountIds } from "./accounts.js";
6
+ import { CHANNEL_CONFIG_KEY, CHANNEL_ID, stripChannelPrefix } from "./constants.js";
7
+
8
+ const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
9
+ const DEFAULT_WEBHOOK_PORT = 4399;
10
+ const DEFAULT_WEBHOOK_PATH = "/webhook";
11
+ const DEFAULT_MEDIA_HOST = "0.0.0.0";
12
+ const DEFAULT_MEDIA_PORT = 4400;
13
+ const DEFAULT_MEDIA_PATH = "/gewe-media";
14
+ const DEFAULT_API_BASE_URL = "https://www.geweapi.com";
15
+
16
+ type GeweOnboardingAdapter = NonNullable<
17
+ ChannelPlugin<ResolvedGeweAccount>["onboarding"]
18
+ >;
19
+
20
+ type AccountSelection = {
21
+ accountId: string;
22
+ label: string;
23
+ };
24
+
25
+ function listAccountChoices(cfg: OpenClawConfig): AccountSelection[] {
26
+ const ids = listGeweAccountIds(cfg as CoreConfig);
27
+ return ids.map((accountId) => ({
28
+ accountId,
29
+ label: accountId === DEFAULT_ACCOUNT_ID ? "default (primary)" : accountId,
30
+ }));
31
+ }
32
+
33
+ async function promptAccountId(params: {
34
+ cfg: OpenClawConfig;
35
+ prompter: WizardPrompter;
36
+ currentId?: string;
37
+ }): Promise<string> {
38
+ const choices = listAccountChoices(params.cfg);
39
+ const defaultId = resolveDefaultGeweAccountId(params.cfg as CoreConfig);
40
+ const initial = params.currentId?.trim() || defaultId || DEFAULT_ACCOUNT_ID;
41
+ const selection = await params.prompter.select({
42
+ message: "GeWe account",
43
+ options: [
44
+ ...choices.map((item) => ({ value: item.accountId, label: item.label })),
45
+ { value: "__new__", label: "Add a new account" },
46
+ ],
47
+ initialValue: initial,
48
+ });
49
+
50
+ if (selection !== "__new__") {
51
+ return normalizeAccountId(selection) ?? DEFAULT_ACCOUNT_ID;
52
+ }
53
+
54
+ const entered = await params.prompter.text({
55
+ message: "New GeWe account id",
56
+ validate: (value) => (value?.trim() ? undefined : "Required"),
57
+ });
58
+ const normalized = normalizeAccountId(String(entered));
59
+ if (String(entered).trim() !== normalized) {
60
+ await params.prompter.note(`Normalized account id to "${normalized}".`, "GeWe account");
61
+ }
62
+ return normalized;
63
+ }
64
+
65
+ function parseAllowFrom(raw: string): string[] {
66
+ return raw
67
+ .split(/[\n,;]+/g)
68
+ .map((entry) => stripChannelPrefix(entry.trim()))
69
+ .filter(Boolean);
70
+ }
71
+
72
+ async function promptAllowFrom(params: {
73
+ prompter: WizardPrompter;
74
+ existing?: Array<string | number>;
75
+ required?: boolean;
76
+ }): Promise<string[]> {
77
+ const initial = (params.existing ?? []).map((entry) => String(entry)).join(", ");
78
+ const value = await params.prompter.text({
79
+ message: "Allowlist wxid (comma or newline separated)",
80
+ placeholder: "wxid_xxx, wxid_yyy",
81
+ initialValue: initial || undefined,
82
+ validate: params.required
83
+ ? (input) => (parseAllowFrom(input).length > 0 ? undefined : "Required")
84
+ : undefined,
85
+ });
86
+ return parseAllowFrom(String(value));
87
+ }
88
+
89
+ function applyAccountPatch(
90
+ cfg: OpenClawConfig,
91
+ accountId: string,
92
+ patch: GeweAccountConfig,
93
+ ): OpenClawConfig {
94
+ const existing = (cfg.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as GeweAccountConfig & {
95
+ accounts?: Record<string, GeweAccountConfig>;
96
+ };
97
+ if (accountId === DEFAULT_ACCOUNT_ID) {
98
+ return {
99
+ ...cfg,
100
+ channels: {
101
+ ...cfg.channels,
102
+ [CHANNEL_CONFIG_KEY]: {
103
+ ...existing,
104
+ ...patch,
105
+ enabled: patch.enabled ?? existing.enabled ?? true,
106
+ },
107
+ },
108
+ };
109
+ }
110
+
111
+ return {
112
+ ...cfg,
113
+ channels: {
114
+ ...cfg.channels,
115
+ [CHANNEL_CONFIG_KEY]: {
116
+ ...existing,
117
+ accounts: {
118
+ ...(existing.accounts ?? {}),
119
+ [accountId]: {
120
+ ...(existing.accounts?.[accountId] ?? {}),
121
+ ...patch,
122
+ enabled:
123
+ patch.enabled ??
124
+ existing.accounts?.[accountId]?.enabled ??
125
+ existing.enabled ??
126
+ true,
127
+ },
128
+ },
129
+ },
130
+ },
131
+ };
132
+ }
133
+
134
+ function readAccountConfig(cfg: OpenClawConfig, accountId: string): GeweAccountConfig {
135
+ const channelCfg = (cfg.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as GeweAccountConfig & {
136
+ accounts?: Record<string, GeweAccountConfig>;
137
+ };
138
+ if (accountId === DEFAULT_ACCOUNT_ID) {
139
+ return channelCfg;
140
+ }
141
+ return channelCfg.accounts?.[accountId] ?? {};
142
+ }
143
+
144
+ export const geweOnboarding: GeweOnboardingAdapter = {
145
+ channel: CHANNEL_ID,
146
+ async getStatus(ctx) {
147
+ const accountId =
148
+ ctx.accountOverrides?.[CHANNEL_ID] ??
149
+ resolveDefaultGeweAccountId(ctx.cfg as CoreConfig);
150
+ const account = resolveGeweAccount({ cfg: ctx.cfg as CoreConfig, accountId });
151
+ const configured = Boolean(account.token?.trim() && account.appId?.trim());
152
+ const label = configured ? "configured" : "not configured";
153
+ const status = `GeWe (${accountId}): ${label}`;
154
+ return {
155
+ channel: CHANNEL_ID,
156
+ configured,
157
+ statusLines: [status],
158
+ selectionHint: label,
159
+ quickstartScore: configured ? 2 : 0,
160
+ };
161
+ },
162
+ async configure(ctx) {
163
+ const accountId = ctx.shouldPromptAccountIds
164
+ ? await promptAccountId({ cfg: ctx.cfg, prompter: ctx.prompter })
165
+ : resolveDefaultGeweAccountId(ctx.cfg as CoreConfig);
166
+ const resolved = resolveGeweAccount({ cfg: ctx.cfg as CoreConfig, accountId });
167
+ const existing = readAccountConfig(ctx.cfg, accountId);
168
+
169
+ await ctx.prompter.note(
170
+ [
171
+ "You will need:",
172
+ "- GeWe token + appId",
173
+ "- Public webhook endpoint (FRP or reverse proxy)",
174
+ "- Public media base URL (for sending voice/media)",
175
+ ].join("\n"),
176
+ "GeWe setup",
177
+ );
178
+
179
+ const token = await ctx.prompter.text({
180
+ message: "GeWe token",
181
+ initialValue: resolved.tokenSource !== "none" ? resolved.token : existing.token,
182
+ validate: (value) => (value.trim() ? undefined : "Required"),
183
+ });
184
+ const appId = await ctx.prompter.text({
185
+ message: "GeWe appId",
186
+ initialValue: resolved.appIdSource !== "none" ? resolved.appId : existing.appId,
187
+ validate: (value) => (value.trim() ? undefined : "Required"),
188
+ });
189
+
190
+ const apiBaseUrl = await ctx.prompter.text({
191
+ message: "GeWe API base URL",
192
+ initialValue: existing.apiBaseUrl ?? DEFAULT_API_BASE_URL,
193
+ validate: (value) => (value.trim() ? undefined : "Required"),
194
+ });
195
+
196
+ const webhookHost = await ctx.prompter.text({
197
+ message: "Webhook host",
198
+ initialValue: existing.webhookHost ?? DEFAULT_WEBHOOK_HOST,
199
+ validate: (value) => (value.trim() ? undefined : "Required"),
200
+ });
201
+ const webhookPortRaw = await ctx.prompter.text({
202
+ message: "Webhook port",
203
+ initialValue: String(existing.webhookPort ?? DEFAULT_WEBHOOK_PORT),
204
+ validate: (value) => {
205
+ const parsed = Number(value);
206
+ if (!Number.isInteger(parsed) || parsed <= 0) return "Must be a positive integer";
207
+ return undefined;
208
+ },
209
+ });
210
+ const webhookPath = await ctx.prompter.text({
211
+ message: "Webhook path",
212
+ initialValue: existing.webhookPath ?? DEFAULT_WEBHOOK_PATH,
213
+ validate: (value) => (value.trim() ? undefined : "Required"),
214
+ });
215
+
216
+ const mediaPublicUrl = await ctx.prompter.text({
217
+ message: "Media public URL (prefix)",
218
+ placeholder: "https://your-domain/gewe-media",
219
+ initialValue: existing.mediaPublicUrl,
220
+ validate: (value) => (value.trim() ? undefined : "Required"),
221
+ });
222
+
223
+ let allowFrom = existing.allowFrom;
224
+ let dmPolicy: GeweAccountConfig["dmPolicy"] | undefined;
225
+ if (ctx.forceAllowFrom) {
226
+ allowFrom = await promptAllowFrom({
227
+ prompter: ctx.prompter,
228
+ existing: existing.allowFrom,
229
+ required: true,
230
+ });
231
+ dmPolicy = "allowlist";
232
+ } else {
233
+ const wantsAllowlist = await ctx.prompter.confirm({
234
+ message: "Set a DM allowlist now? (optional)",
235
+ initialValue: false,
236
+ });
237
+ if (wantsAllowlist) {
238
+ allowFrom = await promptAllowFrom({
239
+ prompter: ctx.prompter,
240
+ existing: existing.allowFrom,
241
+ required: true,
242
+ });
243
+ dmPolicy = "allowlist";
244
+ }
245
+ }
246
+
247
+ let nextCfg = applyAccountPatch(ctx.cfg, accountId, {
248
+ enabled: true,
249
+ token: token.trim(),
250
+ appId: appId.trim(),
251
+ apiBaseUrl: apiBaseUrl.trim().replace(/\/$/, ""),
252
+ webhookHost: webhookHost.trim(),
253
+ webhookPort: Number(webhookPortRaw),
254
+ webhookPath: webhookPath.trim(),
255
+ mediaHost: existing.mediaHost ?? DEFAULT_MEDIA_HOST,
256
+ mediaPort: existing.mediaPort ?? DEFAULT_MEDIA_PORT,
257
+ mediaPath: existing.mediaPath ?? DEFAULT_MEDIA_PATH,
258
+ mediaPublicUrl: mediaPublicUrl.trim(),
259
+ ...(allowFrom ? { allowFrom } : {}),
260
+ ...(dmPolicy ? { dmPolicy } : {}),
261
+ });
262
+
263
+ return { cfg: nextCfg, accountId };
264
+ },
265
+ };
package/src/policy.ts CHANGED
@@ -7,10 +7,11 @@ import {
7
7
  resolveNestedAllowlistDecision,
8
8
  } from "openclaw/plugin-sdk";
9
9
 
10
+ import { CHANNEL_CONFIG_KEY, CHANNEL_PREFIX_REGEX } from "./constants.js";
10
11
  import type { GeweGroupConfig } from "./types.js";
11
12
 
12
13
  function normalizeAllowEntry(raw: string): string {
13
- return raw.trim().toLowerCase().replace(/^(gewe|wechat|wx):/i, "");
14
+ return raw.trim().toLowerCase().replace(CHANNEL_PREFIX_REGEX, "");
14
15
  }
15
16
 
16
17
  export function normalizeGeweAllowlist(values: Array<string | number> | undefined): string[] {
@@ -89,7 +90,7 @@ export function resolveGeweGroupToolPolicy(
89
90
  ): GeweGroupConfig["tools"] | undefined {
90
91
  const cfg = params.cfg as {
91
92
  channels?: {
92
- gewe?: {
93
+ "gewe-openclaw"?: {
93
94
  groups?: Record<string, GeweGroupConfig>;
94
95
  accounts?: Record<string, { groups?: Record<string, GeweGroupConfig> }>;
95
96
  };
@@ -99,10 +100,10 @@ export function resolveGeweGroupToolPolicy(
99
100
  if (!groupId) return undefined;
100
101
  const groupName = params.groupChannel?.trim() || undefined;
101
102
  const accountGroups =
102
- params.accountId && cfg.channels?.gewe?.accounts?.[params.accountId]?.groups
103
- ? cfg.channels?.gewe?.accounts?.[params.accountId]?.groups
103
+ params.accountId && cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[params.accountId]?.groups
104
+ ? cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[params.accountId]?.groups
104
105
  : undefined;
105
- const groups = accountGroups ?? cfg.channels?.gewe?.groups;
106
+ const groups = accountGroups ?? cfg.channels?.[CHANNEL_CONFIG_KEY]?.groups;
106
107
  const match = resolveGeweGroupMatch({
107
108
  groups,
108
109
  groupId,
package/src/send.ts CHANGED
@@ -8,7 +8,7 @@ type GeweSendContext = {
8
8
  };
9
9
 
10
10
  function buildContext(account: ResolvedGeweAccount): GeweSendContext {
11
- const baseUrl = account.config.apiBaseUrl?.trim() || "http://api.geweapi.com";
11
+ const baseUrl = account.config.apiBaseUrl?.trim() || "https://www.geweapi.com";
12
12
  return { baseUrl, token: account.token, appId: account.appId };
13
13
  }
14
14