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/README.md CHANGED
@@ -34,7 +34,7 @@ openclaw plugins install ./gewe-openclaw.tgz
34
34
 
35
35
  ## 配置
36
36
 
37
- 插件配置放在 `~/.openclaw/openclaw.json` 的 `channels.gewe`,并确保插件开启:
37
+ 插件配置放在 `~/.openclaw/openclaw.json` 的 `channels.gewe-openclaw`,并确保插件开启:
38
38
 
39
39
  ```json5
40
40
  {
@@ -44,7 +44,7 @@ openclaw plugins install ./gewe-openclaw.tgz
44
44
  }
45
45
  },
46
46
  "channels": {
47
- "gewe": {
47
+ "gewe-openclaw": {
48
48
  "enabled": true,
49
49
  "apiBaseUrl": "https://www.geweapi.com",
50
50
  "token": "<gewe-token>",
@@ -56,7 +56,12 @@ openclaw plugins install ./gewe-openclaw.tgz
56
56
  "mediaPort": 4400,
57
57
  "mediaPath": "/gewe-media",
58
58
  "mediaPublicUrl": "https://your-public-domain/gewe-media",
59
- "allowFrom": ["wxid_xxx"]
59
+ "allowFrom": ["wxid_xxx"],
60
+ "silkAutoDownload": true,
61
+ "silkVersion": "latest",
62
+ "silkBaseUrl": "https://github.com/Wangnov/rust-silk/releases/download",
63
+ "silkInstallDir": "~/.openclaw/tools/rust-silk",
64
+ "silkAllowUnverified": false
60
65
  }
61
66
  }
62
67
  }
@@ -64,8 +69,15 @@ openclaw plugins install ./gewe-openclaw.tgz
64
69
 
65
70
  说明:
66
71
  - `webhookHost/webhookPort/webhookPath`:GeWe 回调入口(需公网可达,常配合 FRP)。
67
- - `mediaPublicUrl`:公网访问地址,供微信拉取媒体。
72
+ - `mediaPath`:本地媒体服务的路由前缀(默认 `/gewe-media`)。
73
+ - `mediaPublicUrl`:公网访问地址的“基础前缀”,会自动拼接媒体 ID。通常应与 `mediaPath` 对齐,例如 `mediaPath="/gewe-media"` 时,`mediaPublicUrl` 也应包含 `/gewe-media`。
68
74
  - `allowFrom`:允许私聊触发的微信 ID(或在群里走 allowlist 规则)。
75
+ - `silkAutoDownload`:自动下载 `rust-silk`(默认开启;可关闭后自行配置 `voiceSilkPath` / `voiceDecodePath`)。
76
+ - `silkVersion`:自动下载的 `rust-silk` 版本(`latest` 会自动清理旧版本)。
77
+ - `silkBaseUrl`:自定义下载源(默认 GitHub Releases)。
78
+ - `silkInstallDir`:自定义安装目录(默认 `~/.openclaw/tools/rust-silk/<version>`)。
79
+ - `silkAllowUnverified`:校验文件缺失时是否允许继续(默认 `false`)。
80
+ - `silkSha256`:手动指定下载包 SHA256(用于私有源或校验文件缺失场景)。
69
81
 
70
82
  > 配置变更后需重启 Gateway。
71
83
 
@@ -86,14 +98,14 @@ OpenClaw 支持外部插件目录(catalog)。放置到以下路径即可被
86
98
  "name": "gewe-openclaw",
87
99
  "openclaw": {
88
100
  "channel": {
89
- "id": "gewe",
101
+ "id": "gewe-openclaw",
90
102
  "label": "GeWe",
91
103
  "selectionLabel": "WeChat (GeWe)",
92
104
  "detailLabel": "WeChat (GeWe)",
93
- "docsPath": "/channels/gewe",
94
- "docsLabel": "gewe",
105
+ "docsPath": "/channels/gewe-openclaw",
106
+ "docsLabel": "gewe-openclaw",
95
107
  "blurb": "WeChat channel via GeWe API and webhook callbacks.",
96
- "aliases": ["wechat", "wx", "gewe"],
108
+ "aliases": ["gewe-openclaw", "gewe", "wechat", "wx"],
97
109
  "order": 72,
98
110
  "quickstartAllowFrom": true
99
111
  },
@@ -107,6 +119,8 @@ OpenClaw 支持外部插件目录(catalog)。放置到以下路径即可被
107
119
  }
108
120
  ```
109
121
 
122
+ > 现在插件已支持 onboarding:选择 GeWe 通道后会提示填写 token/appId/webhook/mediaPublicUrl 等配置。
123
+
110
124
  ## 依赖
111
125
 
112
126
  ### npm 依赖
@@ -120,8 +134,8 @@ OpenClaw 支持外部插件目录(catalog)。放置到以下路径即可被
120
134
  ### 系统级工具
121
135
 
122
136
  - `ffmpeg` / `ffprobe`(用于视频缩略图与时长)
123
- - `silk-encoder`(出站语音转 silk
124
- - `silk-decoder`(入站语音解码)
137
+ - `rust-silk`(出站语音转 silk + 入站语音解码;支持自动下载)
138
+ - 或者自行安装 `silk-encoder` / `silk-decoder` 并在配置中指定路径
125
139
 
126
140
  ### 网络/服务依赖
127
141
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "gewe-openclaw",
3
3
  "channels": [
4
- "gewe"
4
+ "gewe-openclaw"
5
5
  ],
6
6
  "configSchema": {
7
7
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gewe-openclaw",
3
- "version": "2026.1.30",
3
+ "version": "2026.2.1",
4
4
  "type": "module",
5
5
  "description": "OpenClaw GeWe channel plugin",
6
6
  "license": "MIT",
@@ -9,17 +9,18 @@
9
9
  "./index.ts"
10
10
  ],
11
11
  "channel": {
12
- "id": "gewe",
12
+ "id": "gewe-openclaw",
13
13
  "label": "GeWe",
14
14
  "selectionLabel": "WeChat (GeWe)",
15
15
  "detailLabel": "WeChat (GeWe)",
16
- "docsPath": "/channels/gewe",
17
- "docsLabel": "gewe",
16
+ "docsPath": "/channels/gewe-openclaw",
17
+ "docsLabel": "gewe-openclaw",
18
18
  "blurb": "WeChat channel via GeWe API and webhook callbacks.",
19
19
  "aliases": [
20
+ "gewe-openclaw",
21
+ "gewe",
20
22
  "wechat",
21
- "wx",
22
- "gewe"
23
+ "wx"
23
24
  ],
24
25
  "order": 72,
25
26
  "quickstartAllowFrom": true
package/src/accounts.ts CHANGED
@@ -2,9 +2,10 @@ import { readFileSync } from "node:fs";
2
2
 
3
3
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
4
4
 
5
+ import { CHANNEL_CONFIG_KEY } from "./constants.js";
5
6
  import type { CoreConfig, GeweAccountConfig, GeweAppIdSource, GeweTokenSource } from "./types.js";
6
7
 
7
- const DEFAULT_API_BASE_URL = "http://api.geweapi.com";
8
+ const DEFAULT_API_BASE_URL = "https://www.geweapi.com";
8
9
 
9
10
  export type ResolvedGeweAccount = {
10
11
  accountId: string;
@@ -18,7 +19,7 @@ export type ResolvedGeweAccount = {
18
19
  };
19
20
 
20
21
  function listConfiguredAccountIds(cfg: CoreConfig): string[] {
21
- const accounts = cfg.channels?.gewe?.accounts;
22
+ const accounts = cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts;
22
23
  if (!accounts || typeof accounts !== "object") return [];
23
24
  const ids = new Set<string>();
24
25
  for (const key of Object.keys(accounts)) {
@@ -41,7 +42,7 @@ export function resolveDefaultGeweAccountId(cfg: CoreConfig): string {
41
42
  }
42
43
 
43
44
  function resolveAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountConfig | undefined {
44
- const accounts = cfg.channels?.gewe?.accounts;
45
+ const accounts = cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts;
45
46
  if (!accounts || typeof accounts !== "object") return undefined;
46
47
  const direct = accounts[accountId] as GeweAccountConfig | undefined;
47
48
  if (direct) return direct;
@@ -51,7 +52,7 @@ function resolveAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountCo
51
52
  }
52
53
 
53
54
  function mergeGeweAccountConfig(cfg: CoreConfig, accountId: string): GeweAccountConfig {
54
- const { accounts: _ignored, ...base } = (cfg.channels?.gewe ?? {}) as GeweAccountConfig & {
55
+ const { accounts: _ignored, ...base } = (cfg.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as GeweAccountConfig & {
55
56
  accounts?: unknown;
56
57
  };
57
58
  const account = resolveAccountConfig(cfg, accountId) ?? {};
@@ -117,7 +118,7 @@ export function resolveGeweAccount(params: {
117
118
  accountId?: string | null;
118
119
  }): ResolvedGeweAccount {
119
120
  const hasExplicitAccountId = Boolean(params.accountId?.trim());
120
- const baseEnabled = params.cfg.channels?.gewe?.enabled !== false;
121
+ const baseEnabled = params.cfg.channels?.[CHANNEL_CONFIG_KEY]?.enabled !== false;
121
122
 
122
123
  const resolve = (accountId: string): ResolvedGeweAccount => {
123
124
  const merged = mergeGeweAccountConfig(params.cfg, accountId);
@@ -161,4 +162,3 @@ export function listEnabledGeweAccounts(cfg: CoreConfig): ResolvedGeweAccount[]
161
162
  .map((accountId) => resolveGeweAccount({ cfg, accountId }))
162
163
  .filter((account) => account.enabled);
163
164
  }
164
-
package/src/channel.ts CHANGED
@@ -15,6 +15,14 @@ import {
15
15
 
16
16
  import { resolveGeweAccount, resolveDefaultGeweAccountId, listGeweAccountIds } from "./accounts.js";
17
17
  import { GeweConfigSchema } from "./config-schema.js";
18
+ import {
19
+ CHANNEL_ALIASES,
20
+ CHANNEL_CONFIG_KEY,
21
+ CHANNEL_DOCS_LABEL,
22
+ CHANNEL_DOCS_PATH,
23
+ CHANNEL_ID,
24
+ stripChannelPrefix,
25
+ } from "./constants.js";
18
26
  import { deliverGewePayload } from "./delivery.js";
19
27
  import { monitorGeweProvider } from "./monitor.js";
20
28
  import { looksLikeGeweTargetId, normalizeGeweMessagingTarget } from "./normalize.js";
@@ -22,16 +30,17 @@ import { resolveGeweGroupToolPolicy, resolveGeweRequireMention } from "./policy.
22
30
  import { getGeweRuntime } from "./runtime.js";
23
31
  import { sendTextGewe } from "./send.js";
24
32
  import type { CoreConfig, ResolvedGeweAccount } from "./types.js";
33
+ import { geweOnboarding } from "./onboarding.js";
25
34
 
26
35
  const meta = {
27
- id: "gewe",
36
+ id: CHANNEL_ID,
28
37
  label: "GeWe",
29
38
  selectionLabel: "WeChat (GeWe)",
30
39
  detailLabel: "WeChat (GeWe)",
31
- docsPath: "/channels/gewe",
32
- docsLabel: "gewe",
40
+ docsPath: CHANNEL_DOCS_PATH,
41
+ docsLabel: CHANNEL_DOCS_LABEL,
33
42
  blurb: "WeChat channel via GeWe API and webhook callbacks.",
34
- aliases: ["wechat", "wx", "gewe"],
43
+ aliases: [...CHANNEL_ALIASES],
35
44
  order: 72,
36
45
  quickstartAllowFrom: true,
37
46
  };
@@ -45,11 +54,12 @@ type GeweSetupInput = ChannelSetupInput & {
45
54
  };
46
55
 
47
56
  export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
48
- id: "gewe",
57
+ id: CHANNEL_ID,
49
58
  meta,
59
+ onboarding: geweOnboarding,
50
60
  pairing: {
51
61
  idLabel: "wechatUserId",
52
- normalizeAllowEntry: (entry) => entry.replace(/^(gewe|wechat|wx):/i, ""),
62
+ normalizeAllowEntry: (entry) => stripChannelPrefix(entry),
53
63
  notifyApproval: async ({ cfg, id }) => {
54
64
  const account = resolveGeweAccount({ cfg: cfg as CoreConfig });
55
65
  if (!account.token || !account.appId) {
@@ -70,7 +80,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
70
80
  nativeCommands: false,
71
81
  blockStreaming: true,
72
82
  },
73
- reload: { configPrefixes: ["channels.gewe"] },
83
+ reload: { configPrefixes: [`channels.${CHANNEL_CONFIG_KEY}`] },
74
84
  configSchema: buildChannelConfigSchema(GeweConfigSchema),
75
85
  config: {
76
86
  listAccountIds: (cfg) => listGeweAccountIds(cfg as CoreConfig),
@@ -79,7 +89,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
79
89
  setAccountEnabled: ({ cfg, accountId, enabled }) =>
80
90
  setAccountEnabledInConfigSection({
81
91
  cfg,
82
- sectionKey: "gewe",
92
+ sectionKey: CHANNEL_CONFIG_KEY,
83
93
  accountId,
84
94
  enabled,
85
95
  allowTopLevel: true,
@@ -87,7 +97,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
87
97
  deleteAccount: ({ cfg, accountId }) =>
88
98
  deleteAccountFromConfigSection({
89
99
  cfg,
90
- sectionKey: "gewe",
100
+ sectionKey: CHANNEL_CONFIG_KEY,
91
101
  accountId,
92
102
  clearBaseFields: ["token", "tokenFile", "appId", "appIdFile", "name"],
93
103
  }),
@@ -108,24 +118,24 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
108
118
  allowFrom
109
119
  .map((entry) => String(entry).trim())
110
120
  .filter(Boolean)
111
- .map((entry) => entry.replace(/^(gewe|wechat|wx):/i, "")),
121
+ .map((entry) => stripChannelPrefix(entry)),
112
122
  },
113
123
  security: {
114
124
  resolveDmPolicy: ({ cfg, accountId, account }) => {
115
125
  const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
116
126
  const useAccountPath = Boolean(
117
- cfg.channels?.gewe?.accounts?.[resolvedAccountId],
127
+ cfg.channels?.[CHANNEL_CONFIG_KEY]?.accounts?.[resolvedAccountId],
118
128
  );
119
129
  const basePath = useAccountPath
120
- ? `channels.gewe.accounts.${resolvedAccountId}.`
121
- : "channels.gewe.";
130
+ ? `channels.${CHANNEL_CONFIG_KEY}.accounts.${resolvedAccountId}.`
131
+ : `channels.${CHANNEL_CONFIG_KEY}.`;
122
132
  return {
123
133
  policy: account.config.dmPolicy ?? "pairing",
124
134
  allowFrom: account.config.allowFrom ?? [],
125
135
  policyPath: `${basePath}dmPolicy`,
126
136
  allowFromPath: basePath,
127
- approveHint: formatPairingApproveHint("gewe"),
128
- normalizeEntry: (raw) => raw.replace(/^(gewe|wechat|wx):/i, ""),
137
+ approveHint: formatPairingApproveHint(CHANNEL_ID),
138
+ normalizeEntry: (raw) => stripChannelPrefix(raw),
129
139
  };
130
140
  },
131
141
  collectWarnings: ({ account, cfg }) => {
@@ -136,11 +146,11 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
136
146
  account.config.groups && Object.keys(account.config.groups).length > 0;
137
147
  if (groupAllowlistConfigured) {
138
148
  return [
139
- `- GeWe groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.gewe.groupPolicy="allowlist" + channels.gewe.groupAllowFrom to restrict senders.`,
149
+ `- GeWe groups: groupPolicy="open" allows any member in allowed groups to trigger (mention-gated). Set channels.${CHANNEL_CONFIG_KEY}.groupPolicy="allowlist" + channels.${CHANNEL_CONFIG_KEY}.groupAllowFrom to restrict senders.`,
140
150
  ];
141
151
  }
142
152
  return [
143
- `- GeWe groups: groupPolicy="open" with no channels.gewe.groups allowlist; any group can add + ping (mention-gated). Set channels.gewe.groupPolicy="allowlist" + channels.gewe.groupAllowFrom or configure channels.gewe.groups.`,
153
+ `- GeWe groups: groupPolicy="open" with no channels.${CHANNEL_CONFIG_KEY}.groups allowlist; any group can add + ping (mention-gated). Set channels.${CHANNEL_CONFIG_KEY}.groupPolicy="allowlist" + channels.${CHANNEL_CONFIG_KEY}.groupAllowFrom or configure channels.${CHANNEL_CONFIG_KEY}.groups.`,
144
154
  ];
145
155
  },
146
156
  },
@@ -187,7 +197,10 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
187
197
  }
188
198
  return {
189
199
  ok: false,
190
- error: missingTargetError("GeWe", "<wxid|@chatroom> or channels.gewe.allowFrom[0]"),
200
+ error: missingTargetError(
201
+ "GeWe",
202
+ `<wxid|@chatroom> or channels.${CHANNEL_CONFIG_KEY}.allowFrom[0]`,
203
+ ),
191
204
  };
192
205
  }
193
206
  return { ok: true, to: normalized };
@@ -198,7 +211,10 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
198
211
  }
199
212
  return {
200
213
  ok: false,
201
- error: missingTargetError("GeWe", "<wxid|@chatroom> or channels.gewe.allowFrom[0]"),
214
+ error: missingTargetError(
215
+ "GeWe",
216
+ `<wxid|@chatroom> or channels.${CHANNEL_CONFIG_KEY}.allowFrom[0]`,
217
+ ),
202
218
  };
203
219
  },
204
220
  sendPayload: async ({ payload, cfg, to, accountId }) => {
@@ -210,7 +226,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
210
226
  toWxid: to,
211
227
  });
212
228
  return {
213
- channel: "gewe",
229
+ channel: CHANNEL_ID,
214
230
  messageId: result?.messageId ?? "ok",
215
231
  timestamp: result?.timestamp,
216
232
  meta: { newMessageId: result?.newMessageId },
@@ -225,7 +241,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
225
241
  toWxid: to,
226
242
  });
227
243
  return {
228
- channel: "gewe",
244
+ channel: CHANNEL_ID,
229
245
  messageId: result?.messageId ?? "ok",
230
246
  timestamp: result?.timestamp,
231
247
  meta: { newMessageId: result?.newMessageId },
@@ -240,7 +256,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
240
256
  toWxid: to,
241
257
  });
242
258
  return {
243
- channel: "gewe",
259
+ channel: CHANNEL_ID,
244
260
  messageId: result?.messageId ?? "ok",
245
261
  timestamp: result?.timestamp,
246
262
  meta: { newMessageId: result?.newMessageId },
@@ -307,7 +323,9 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
307
323
  },
308
324
  logoutAccount: async ({ accountId, cfg }) => {
309
325
  const nextCfg = { ...cfg } as OpenClawConfig;
310
- const nextSection = cfg.channels?.gewe ? { ...cfg.channels.gewe } : undefined;
326
+ const nextSection = cfg.channels?.[CHANNEL_CONFIG_KEY]
327
+ ? { ...(cfg.channels?.[CHANNEL_CONFIG_KEY] as Record<string, unknown>) }
328
+ : undefined;
311
329
  let cleared = false;
312
330
  let changed = false;
313
331
 
@@ -377,7 +395,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
377
395
  if (changed) {
378
396
  nextCfg.channels = {
379
397
  ...nextCfg.channels,
380
- gewe: nextSection,
398
+ [CHANNEL_CONFIG_KEY]: nextSection,
381
399
  };
382
400
  }
383
401
  }
@@ -390,7 +408,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
390
408
  applyAccountName: ({ cfg, accountId, name }) =>
391
409
  applyAccountNameToChannelSection({
392
410
  cfg: cfg as OpenClawConfig,
393
- channelKey: "gewe",
411
+ channelKey: CHANNEL_CONFIG_KEY,
394
412
  accountId,
395
413
  name,
396
414
  }),
@@ -411,11 +429,14 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
411
429
  const setupInput = input as GeweSetupInput;
412
430
  const namedConfig = applyAccountNameToChannelSection({
413
431
  cfg: cfg as OpenClawConfig,
414
- channelKey: "gewe",
432
+ channelKey: CHANNEL_CONFIG_KEY,
415
433
  accountId,
416
434
  name: setupInput.name,
417
435
  });
418
- const section = (namedConfig.channels?.gewe ?? {}) as Record<string, unknown>;
436
+ const section = (namedConfig.channels?.[CHANNEL_CONFIG_KEY] ?? {}) as Record<
437
+ string,
438
+ unknown
439
+ >;
419
440
  const useAccountPath = accountId !== DEFAULT_ACCOUNT_ID;
420
441
  const base = useAccountPath
421
442
  ? (section.accounts?.[accountId] as Record<string, unknown> | undefined) ?? {}
@@ -443,7 +464,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
443
464
  ...namedConfig,
444
465
  channels: {
445
466
  ...namedConfig.channels,
446
- gewe: nextEntry,
467
+ [CHANNEL_CONFIG_KEY]: nextEntry,
447
468
  },
448
469
  };
449
470
  }
@@ -451,7 +472,7 @@ export const gewePlugin: ChannelPlugin<ResolvedGeweAccount> = {
451
472
  ...namedConfig,
452
473
  channels: {
453
474
  ...namedConfig.channels,
454
- gewe: {
475
+ [CHANNEL_CONFIG_KEY]: {
455
476
  ...section,
456
477
  accounts: {
457
478
  ...(section.accounts as Record<string, unknown> | undefined),
@@ -49,6 +49,12 @@ export const GeweAccountSchemaBase = z
49
49
  voiceDecodeArgs: z.array(z.string()).optional(),
50
50
  voiceDecodeSampleRate: z.number().int().positive().optional(),
51
51
  voiceDecodeOutput: z.enum(["pcm", "wav"]).optional(),
52
+ silkAutoDownload: z.boolean().optional(),
53
+ silkVersion: z.string().optional(),
54
+ silkBaseUrl: z.string().optional(),
55
+ silkSha256: z.string().optional(),
56
+ silkAllowUnverified: z.boolean().optional(),
57
+ silkInstallDir: z.string().optional(),
52
58
  videoFfmpegPath: z.string().optional(),
53
59
  videoFfprobePath: z.string().optional(),
54
60
  videoThumbUrl: z.string().optional(),
@@ -81,25 +87,27 @@ export const GeweAccountSchemaBase = z
81
87
  });
82
88
 
83
89
  export const GeweAccountSchema = GeweAccountSchemaBase.superRefine((value, ctx) => {
90
+ const pathHint = "channels.gewe-openclaw";
84
91
  requireOpenAllowFrom({
85
92
  policy: value.dmPolicy,
86
93
  allowFrom: value.allowFrom,
87
94
  ctx,
88
95
  path: ["allowFrom"],
89
96
  message:
90
- 'channels.gewe.dmPolicy="open" requires channels.gewe.allowFrom to include "*"',
97
+ `${pathHint}.dmPolicy="open" requires ${pathHint}.allowFrom to include "*"`,
91
98
  });
92
99
  });
93
100
 
94
101
  export const GeweConfigSchema = GeweAccountSchemaBase.extend({
95
102
  accounts: z.record(z.string(), GeweAccountSchema.optional()).optional(),
96
103
  }).superRefine((value, ctx) => {
104
+ const pathHint = "channels.gewe-openclaw";
97
105
  requireOpenAllowFrom({
98
106
  policy: value.dmPolicy,
99
107
  allowFrom: value.allowFrom,
100
108
  ctx,
101
109
  path: ["allowFrom"],
102
110
  message:
103
- 'channels.gewe.dmPolicy="open" requires channels.gewe.allowFrom to include "*"',
111
+ `${pathHint}.dmPolicy="open" requires ${pathHint}.allowFrom to include "*"`,
104
112
  });
105
113
  });
@@ -0,0 +1,10 @@
1
+ export const CHANNEL_ID = "gewe-openclaw" as const;
2
+ export const CHANNEL_CONFIG_KEY = "gewe-openclaw" as const;
3
+ export const CHANNEL_DOCS_PATH = "/channels/gewe-openclaw" as const;
4
+ export const CHANNEL_DOCS_LABEL = "gewe-openclaw" as const;
5
+ export const CHANNEL_PREFIX_REGEX = /^(gewe-openclaw|gewe|wechat|wx):/i;
6
+ export const CHANNEL_ALIASES = ["gewe-openclaw", "gewe", "wechat", "wx"] as const;
7
+
8
+ export function stripChannelPrefix(value: string): string {
9
+ return value.replace(CHANNEL_PREFIX_REGEX, "");
10
+ }
package/src/delivery.ts CHANGED
@@ -5,7 +5,9 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  import type { OpenClawConfig, ReplyPayload } from "openclaw/plugin-sdk";
7
7
  import { extractOriginalFilename, extensionForMime } from "openclaw/plugin-sdk";
8
+ import { CHANNEL_ID } from "./constants.js";
8
9
  import { getGeweRuntime } from "./runtime.js";
10
+ import { ensureRustSilkBinary } from "./silk.js";
9
11
  import {
10
12
  sendFileGewe,
11
13
  sendImageGewe,
@@ -97,8 +99,10 @@ function resolveMediaMaxBytes(account: ResolvedGeweAccount): number {
97
99
  }
98
100
 
99
101
  function resolveGeweData(payload: ReplyPayload): GeweChannelData | undefined {
100
- const data = payload.channelData as { gewe?: GeweChannelData } | undefined;
101
- return data?.gewe;
102
+ const data = payload.channelData as
103
+ | { "gewe-openclaw"?: GeweChannelData; gewe?: GeweChannelData }
104
+ | undefined;
105
+ return data?.[CHANNEL_ID] ?? data?.gewe;
102
106
  }
103
107
 
104
108
  function isSilkAudio(opts: { contentType?: string; fileName?: string }): boolean {
@@ -145,7 +149,7 @@ async function probeVideoDurationSeconds(params: {
145
149
  sourcePath: string;
146
150
  }): Promise<number | null> {
147
151
  const core = getGeweRuntime();
148
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "video" });
152
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
149
153
  const ffmpegPath = resolveVideoFfmpegPath(params.account);
150
154
  const ffprobePath = resolveVideoFfprobePath(params.account, ffmpegPath);
151
155
  const args = [
@@ -180,7 +184,7 @@ async function generateVideoThumbBuffer(params: {
180
184
  sourcePath: string;
181
185
  }): Promise<Buffer | null> {
182
186
  const core = getGeweRuntime();
183
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "video" });
187
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
184
188
  const ffmpegPath = resolveVideoFfmpegPath(params.account);
185
189
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-gewe-video-"));
186
190
  const thumbPath = path.join(tmpDir, "thumb.png");
@@ -247,21 +251,40 @@ async function convertAudioToSilk(params: {
247
251
  sourcePath: string;
248
252
  }): Promise<{ buffer: Buffer; durationMs: number } | null> {
249
253
  const core = getGeweRuntime();
250
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "voice" });
254
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "voice" });
251
255
  if (!params.account.config.voiceAutoConvert) return null;
252
256
 
253
257
  const sampleRate = resolveVoiceSampleRate(params.account);
254
258
  const ffmpegPath = params.account.config.voiceFfmpegPath?.trim() || DEFAULT_VOICE_FFMPEG;
255
- const silkPath = params.account.config.voiceSilkPath?.trim() || DEFAULT_VOICE_SILK;
256
- const customArgs =
257
- params.account.config.voiceSilkArgs?.length ? [params.account.config.voiceSilkArgs] : [];
258
259
  const fallbackArgs = [
259
260
  ["-i", "{input}", "-o", "{output}", "-rate", "{sampleRate}"],
260
261
  ["{input}", "{output}", "-rate", "{sampleRate}"],
261
262
  ["{input}", "{output}", "{sampleRate}"],
262
263
  ["{input}", "{output}"],
263
264
  ];
264
- const argTemplates = customArgs.length ? customArgs : fallbackArgs;
265
+ const rustArgs = [
266
+ "encode",
267
+ "-i",
268
+ "{input}",
269
+ "-o",
270
+ "{output}",
271
+ "--sample-rate",
272
+ "{sampleRate}",
273
+ "--tencent",
274
+ "--quiet",
275
+ ];
276
+ const customPath = params.account.config.voiceSilkPath?.trim();
277
+ const customArgs =
278
+ params.account.config.voiceSilkArgs?.length ? [params.account.config.voiceSilkArgs] : [];
279
+ let silkPath = customPath || DEFAULT_VOICE_SILK;
280
+ let argTemplates = customArgs.length ? customArgs : fallbackArgs;
281
+ if (!customPath) {
282
+ const rustSilk = await ensureRustSilkBinary(params.account);
283
+ if (rustSilk) {
284
+ silkPath = rustSilk;
285
+ argTemplates = [rustArgs];
286
+ }
287
+ }
265
288
 
266
289
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-gewe-voice-"));
267
290
  const pcmPath = path.join(tmpDir, "voice.pcm");
@@ -441,7 +464,7 @@ async function resolveLinkThumbUrl(params: {
441
464
  thumbUrl?: string;
442
465
  }): Promise<string> {
443
466
  const core = getGeweRuntime();
444
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "thumb" });
467
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "thumb" });
445
468
  const fallbackBuffer = await fs.readFile(DEFAULT_LINK_THUMB_PATH);
446
469
  const fallbackUrl = await stageThumbBuffer({
447
470
  account: params.account,
@@ -591,7 +614,7 @@ export async function deliverGewePayload(params: {
591
614
  thumbUrl,
592
615
  });
593
616
  core.channel.activity.record({
594
- channel: "gewe",
617
+ channel: CHANNEL_ID,
595
618
  accountId: account.accountId,
596
619
  direction: "outbound",
597
620
  });
@@ -625,7 +648,7 @@ export async function deliverGewePayload(params: {
625
648
  voiceDuration: declaredDuration,
626
649
  });
627
650
  core.channel.activity.record({
628
- channel: "gewe",
651
+ channel: CHANNEL_ID,
629
652
  accountId: account.accountId,
630
653
  direction: "outbound",
631
654
  });
@@ -657,7 +680,7 @@ export async function deliverGewePayload(params: {
657
680
  voiceDuration,
658
681
  });
659
682
  core.channel.activity.record({
660
- channel: "gewe",
683
+ channel: CHANNEL_ID,
661
684
  accountId: account.accountId,
662
685
  direction: "outbound",
663
686
  });
@@ -674,7 +697,7 @@ export async function deliverGewePayload(params: {
674
697
  imgUrl: staged.publicUrl,
675
698
  });
676
699
  core.channel.activity.record({
677
- channel: "gewe",
700
+ channel: CHANNEL_ID,
678
701
  accountId: account.accountId,
679
702
  direction: "outbound",
680
703
  });
@@ -683,7 +706,7 @@ export async function deliverGewePayload(params: {
683
706
  }
684
707
 
685
708
  if (!forceFile && kind === "video") {
686
- const logger = core.logging.getChildLogger({ channel: "gewe", module: "video" });
709
+ const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "video" });
687
710
  const video = geweData?.video;
688
711
  let thumbUrl = video?.thumbUrl;
689
712
  const fallbackThumbUrl = account.config.videoThumbUrl?.trim() || undefined;
@@ -755,7 +778,7 @@ export async function deliverGewePayload(params: {
755
778
  videoDuration: Math.floor(videoDuration),
756
779
  });
757
780
  core.channel.activity.record({
758
- channel: "gewe",
781
+ channel: CHANNEL_ID,
759
782
  accountId: account.accountId,
760
783
  direction: "outbound",
761
784
  });
@@ -780,7 +803,7 @@ export async function deliverGewePayload(params: {
780
803
  videoDuration: Math.floor(videoDuration),
781
804
  });
782
805
  core.channel.activity.record({
783
- channel: "gewe",
806
+ channel: CHANNEL_ID,
784
807
  accountId: account.accountId,
785
808
  direction: "outbound",
786
809
  });
@@ -803,7 +826,7 @@ export async function deliverGewePayload(params: {
803
826
  fileName: fallbackName,
804
827
  });
805
828
  core.channel.activity.record({
806
- channel: "gewe",
829
+ channel: CHANNEL_ID,
807
830
  accountId: account.accountId,
808
831
  direction: "outbound",
809
832
  });
@@ -814,7 +837,7 @@ export async function deliverGewePayload(params: {
814
837
  if (trimmedText) {
815
838
  const tableMode = core.channel.text.resolveMarkdownTableMode({
816
839
  cfg,
817
- channel: "gewe",
840
+ channel: CHANNEL_ID,
818
841
  accountId: account.accountId,
819
842
  });
820
843
  const content = core.channel.text.convertMarkdownTables(trimmedText, tableMode);
@@ -825,7 +848,7 @@ export async function deliverGewePayload(params: {
825
848
  ats: geweData?.ats,
826
849
  });
827
850
  core.channel.activity.record({
828
- channel: "gewe",
851
+ channel: CHANNEL_ID,
829
852
  accountId: account.accountId,
830
853
  direction: "outbound",
831
854
  });
package/src/download.ts CHANGED
@@ -4,7 +4,7 @@ import type { ResolvedGeweAccount } from "./types.js";
4
4
  type DownloadResult = { fileUrl: string };
5
5
 
6
6
  function resolveBaseUrl(account: ResolvedGeweAccount): string {
7
- return account.config.apiBaseUrl?.trim() || "http://api.geweapi.com";
7
+ return account.config.apiBaseUrl?.trim() || "https://www.geweapi.com";
8
8
  }
9
9
 
10
10
  export async function downloadGeweImage(params: {