@tencent-weixin/openclaw-weixin 1.0.3 → 2.0.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
@@ -4,10 +4,22 @@
4
4
 
5
5
  OpenClaw's WeChat channel plugin, supporting login authorization via QR code scanning.
6
6
 
7
+ ## Compatibility
8
+
9
+ | Plugin Version | OpenClaw Version | npm dist-tag | Status |
10
+ |---------------|------------------------|--------------|-------------|
11
+ | 2.0.x | >=2026.3.22 | `latest` | Active |
12
+ | 1.0.x | >=2026.1.0 <2026.3.22 | `legacy` | Maintenance |
13
+
14
+ > The plugin checks the host version at startup and will refuse to load if the
15
+ > running OpenClaw version is outside the supported range.
16
+
7
17
  ## Prerequisites
8
18
 
9
19
  [OpenClaw](https://docs.openclaw.ai/install) must be installed (the `openclaw` CLI needs to be available).
10
20
 
21
+ Check your version: `openclaw --version`
22
+
11
23
  ## Quick Install
12
24
 
13
25
  ```bash
@@ -269,3 +281,34 @@ All media types (image/voice/file/video) are transferred via CDN using AES-128-E
269
281
  6. Use the returned `encrypt_query_param` to construct a `CDNMedia` reference, include it in the `MessageItem`, and send
270
282
 
271
283
  > For complete type definitions, see [`src/api/types.ts`](src/api/types.ts). For API call implementations, see [`src/api/api.ts`](src/api/api.ts).
284
+
285
+ ## Uninstall
286
+
287
+ ```bash
288
+ openclaw openclaw-weixin uninstall
289
+ ```
290
+
291
+ ## Troubleshooting
292
+
293
+ ### "requires OpenClaw >=2026.3.22" error
294
+
295
+ Your OpenClaw version is too old for this plugin version. Check with:
296
+
297
+ ```bash
298
+ openclaw --version
299
+ ```
300
+
301
+ Install the legacy plugin line instead:
302
+
303
+ ```bash
304
+ openclaw plugins install @tencent-weixin/openclaw-weixin@legacy
305
+ ```
306
+
307
+ ### Channel shows "OK" but doesn't connect
308
+
309
+ Ensure `plugins.entries.openclaw-weixin.enabled` is `true` in `~/.openclaw/openclaw.json`:
310
+
311
+ ```bash
312
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
313
+ openclaw gateway restart
314
+ ```
package/README.zh_CN.md CHANGED
@@ -4,10 +4,21 @@
4
4
 
5
5
  OpenClaw 的微信渠道插件,支持通过扫码完成登录授权。
6
6
 
7
+ ## 兼容性
8
+
9
+ | 插件版本 | OpenClaw 版本 | npm dist-tag | 状态 |
10
+ |---------|--------------------------|--------------|--------|
11
+ | 2.0.x | >=2026.3.22 | `latest` | 活跃 |
12
+ | 1.0.x | >=2026.1.0 <2026.3.22 | `legacy` | 维护中 |
13
+
14
+ > 插件在启动时会检查宿主版本,如果运行的 OpenClaw 版本超出支持范围,插件将拒绝加载。
15
+
7
16
  ## 前提条件
8
17
 
9
18
  已安装 [OpenClaw](https://docs.openclaw.ai/install)(需要 `openclaw` CLI 可用)。
10
19
 
20
+ 查看版本:`openclaw --version`
21
+
11
22
  ## 一键安装
12
23
 
13
24
  ```bash
@@ -269,3 +280,34 @@ openclaw config set agents.mode per-channel-per-peer
269
280
  6. 使用返回的 `encrypt_query_param` 构造 `CDNMedia` 引用,放入 `MessageItem` 发送
270
281
 
271
282
  > 完整的类型定义见 [`src/api/types.ts`](src/api/types.ts),API 调用实现见 [`src/api/api.ts`](src/api/api.ts)。
283
+
284
+ ## 卸载
285
+
286
+ ```bash
287
+ openclaw openclaw-weixin uninstall
288
+ ```
289
+
290
+ ## 故障排查
291
+
292
+ ### "requires OpenClaw >=2026.3.22" 报错
293
+
294
+ 你的 OpenClaw 版本太旧,不兼容当前插件版本。检查版本:
295
+
296
+ ```bash
297
+ openclaw --version
298
+ ```
299
+
300
+ 安装旧版插件线:
301
+
302
+ ```bash
303
+ openclaw plugins install @tencent-weixin/openclaw-weixin@legacy
304
+ ```
305
+
306
+ ### Channel 显示 "OK" 但未连接
307
+
308
+ 确保 `~/.openclaw/openclaw.json` 中 `plugins.entries.openclaw-weixin.enabled` 为 `true`:
309
+
310
+ ```bash
311
+ openclaw config set plugins.entries.openclaw-weixin.enabled true
312
+ openclaw gateway restart
313
+ ```
package/index.ts CHANGED
@@ -1,27 +1,33 @@
1
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
- import { buildChannelConfigSchema } from "openclaw/plugin-sdk";
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
3
3
 
4
4
  import { weixinPlugin } from "./src/channel.js";
5
+ import { assertHostCompatibility } from "./src/compat.js";
5
6
  import { WeixinConfigSchema } from "./src/config/config-schema.js";
6
7
  import { registerWeixinCli } from "./src/log-upload.js";
7
8
  import { setWeixinRuntime } from "./src/runtime.js";
8
9
 
9
- const plugin = {
10
+ export default {
10
11
  id: "openclaw-weixin",
11
12
  name: "Weixin",
12
13
  description: "Weixin channel (getUpdates long-poll + sendMessage)",
13
14
  configSchema: buildChannelConfigSchema(WeixinConfigSchema),
14
15
  register(api: OpenClawPluginApi) {
15
- if (!api?.runtime) {
16
- throw new Error("[weixin] api.runtime is not available in register()");
16
+ // Fail-fast: reject incompatible host versions before any side-effects.
17
+ assertHostCompatibility(api.runtime?.version);
18
+
19
+ if (api.runtime) {
20
+ setWeixinRuntime(api.runtime);
17
21
  }
18
- setWeixinRuntime(api.runtime);
19
22
 
20
23
  api.registerChannel({ plugin: weixinPlugin });
24
+
25
+ // registrationMode exists in 2026.3.22+; skip heavy registrations in setup-only mode.
26
+ const mode = (api as { registrationMode?: string }).registrationMode;
27
+ if (mode && mode !== "full") return;
28
+
21
29
  api.registerCli(({ program, config }) => registerWeixinCli({ program, config }), {
22
30
  commands: ["openclaw-weixin"],
23
31
  });
24
32
  },
25
33
  };
26
-
27
- export default plugin;
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "id": "openclaw-weixin",
3
+ "version": "2.0.0",
3
4
  "channels": ["openclaw-weixin"],
4
5
  "configSchema": {
5
6
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-weixin/openclaw-weixin",
3
- "version": "1.0.3",
3
+ "version": "2.0.1",
4
4
  "description": "OpenClaw Weixin channel",
5
5
  "license": "MIT",
6
6
  "author": "Tencent",
@@ -29,8 +29,13 @@
29
29
  "qrcode-terminal": "0.12.0",
30
30
  "zod": "4.3.6"
31
31
  },
32
+ "peerDependencies": {
33
+ "openclaw": ">=2026.3.22"
34
+ },
32
35
  "devDependencies": {
33
36
  "@vitest/coverage-v8": "^3.1.0",
37
+ "openclaw": "2026.3.23",
38
+ "silk-wasm": "^3.7.1",
34
39
  "typescript": "^5.8.0",
35
40
  "vitest": "^3.1.0"
36
41
  },
@@ -49,7 +54,8 @@
49
54
  },
50
55
  "install": {
51
56
  "npmSpec": "@tencent-weixin/openclaw-weixin",
52
- "defaultChoice": "npm"
57
+ "defaultChoice": "npm",
58
+ "minHostVersion": ">=2026.3.22"
53
59
  }
54
60
  }
55
61
  }
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { normalizeAccountId } from "openclaw/plugin-sdk";
5
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
4
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
5
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
6
6
 
7
7
  import { getWeixinRuntime } from "../runtime.js";
8
8
  import { resolveStateDir } from "../storage/state-dir.js";
@@ -291,9 +291,32 @@ export function loadConfigRouteTag(accountId?: string): string | undefined {
291
291
  }
292
292
 
293
293
  /**
294
- * No-op stub config reload is now handled externally via `openclaw gateway restart`.
294
+ * Ensure the openclaw-weixin channel section exists in openclaw.json so the gateway
295
+ * recognises it as a configured channel at startup, then trigger a config reload.
295
296
  */
296
- export async function triggerWeixinChannelReload(): Promise<void> {}
297
+ export async function triggerWeixinChannelReload(): Promise<void> {
298
+ try {
299
+ const { loadConfig, writeConfigFile } = await import("openclaw/plugin-sdk/config-runtime");
300
+ const cfg = loadConfig();
301
+ const channels = (cfg.channels ?? {}) as Record<string, unknown>;
302
+ if (!channels["openclaw-weixin"] || Object.keys(channels["openclaw-weixin"] as Record<string, unknown>).every((k) => k === "enabled")) {
303
+ const updated: OpenClawConfig = {
304
+ ...cfg,
305
+ channels: {
306
+ ...channels,
307
+ "openclaw-weixin": {
308
+ ...(channels["openclaw-weixin"] as Record<string, unknown> ?? {}),
309
+ accounts: {},
310
+ },
311
+ },
312
+ };
313
+ await writeConfigFile(updated);
314
+ logger.info("triggerWeixinChannelReload: wrote channel config to openclaw.json");
315
+ }
316
+ } catch (err) {
317
+ logger.warn(`triggerWeixinChannelReload: failed to update config: ${String(err)}`);
318
+ }
319
+ }
297
320
 
298
321
  // ---------------------------------------------------------------------------
299
322
  // Account resolution (merge config + stored credentials)
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { withFileLock } from "openclaw/plugin-sdk";
4
+ import { withFileLock } from "openclaw/plugin-sdk/infra-runtime";
5
5
 
6
6
  import { resolveStateDir } from "../storage/state-dir.js";
7
7
  import { logger } from "../util/logger.js";
package/src/channel.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import path from "node:path";
2
2
 
3
- import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
4
- import { normalizeAccountId, resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk";
3
+ import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk/core";
4
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
5
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/infra-runtime";
5
6
 
6
7
  import {
7
8
  registerWeixinAccountId,
package/src/compat.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Runtime host-version compatibility check for openclaw-weixin.
3
+ *
4
+ * OpenClaw uses a date-based version format: YYYY.M.DD (e.g. 2026.3.22).
5
+ * This module parses that format and validates the running host is within
6
+ * the supported range for this plugin version.
7
+ */
8
+
9
+ import { logger } from "./util/logger.js";
10
+
11
+ export const PLUGIN_VERSION = "2.0.0";
12
+
13
+ export const SUPPORTED_HOST_MIN = "2026.3.22";
14
+
15
+ export interface OpenClawVersion {
16
+ year: number;
17
+ month: number;
18
+ day: number;
19
+ }
20
+
21
+ /**
22
+ * Parse an OpenClaw date version string (e.g. "2026.3.22") into components.
23
+ * Returns null for unparseable strings.
24
+ */
25
+ export function parseOpenClawVersion(version: string): OpenClawVersion | null {
26
+ // Strip any pre-release suffix (e.g. "2026.3.22-beta.1" -> "2026.3.22")
27
+ const base = version.trim().split("-")[0];
28
+ const parts = base.split(".");
29
+ if (parts.length !== 3) return null;
30
+ const [year, month, day] = parts.map(Number);
31
+ if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) return null;
32
+ return { year, month, day };
33
+ }
34
+
35
+ /**
36
+ * Compare two parsed versions. Returns -1 | 0 | 1.
37
+ */
38
+ export function compareVersions(a: OpenClawVersion, b: OpenClawVersion): -1 | 0 | 1 {
39
+ for (const key of ["year", "month", "day"] as const) {
40
+ if (a[key] < b[key]) return -1;
41
+ if (a[key] > b[key]) return 1;
42
+ }
43
+ return 0;
44
+ }
45
+
46
+ /**
47
+ * Check whether a host version string is >= SUPPORTED_HOST_MIN.
48
+ */
49
+ export function isHostVersionSupported(hostVersion: string): boolean {
50
+ const host = parseOpenClawVersion(hostVersion);
51
+ if (!host) return false;
52
+ const min = parseOpenClawVersion(SUPPORTED_HOST_MIN)!;
53
+ return compareVersions(host, min) >= 0;
54
+ }
55
+
56
+ /**
57
+ * Fail-fast guard. Call at the very start of `register()` to prevent the
58
+ * plugin from loading on an incompatible host.
59
+ *
60
+ * @throws {Error} with a human-readable message when the host is out of range.
61
+ */
62
+ export function assertHostCompatibility(hostVersion: string | undefined): void {
63
+ if (!hostVersion || hostVersion === "unknown") {
64
+ logger.warn(
65
+ `[compat] Could not determine host OpenClaw version; skipping compatibility check.`,
66
+ );
67
+ return;
68
+ }
69
+ if (isHostVersionSupported(hostVersion)) {
70
+ logger.info(`[compat] Host OpenClaw ${hostVersion} >= ${SUPPORTED_HOST_MIN}, OK.`);
71
+ return;
72
+ }
73
+ throw new Error(
74
+ `openclaw-weixin@${PLUGIN_VERSION} requires OpenClaw >=${SUPPORTED_HOST_MIN}, ` +
75
+ `but found ${hostVersion}. ` +
76
+ `Please upgrade OpenClaw, or install openclaw-weixin@1.x (legacy) for older hosts:\n` +
77
+ ` openclaw plugins install @tencent-weixin/openclaw-weixin@legacy`,
78
+ );
79
+ }
package/src/log-upload.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
5
- import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk";
4
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
5
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/infra-runtime";
6
6
 
7
7
 
8
8
  /** Minimal subset of commander's Command used by registerWeixinCli. */
@@ -51,12 +51,34 @@ function getConfiguredUploadUrl(config: OpenClawConfig): string | undefined {
51
51
  return section?.logUploadUrl;
52
52
  }
53
53
 
54
- /** Register the `openclaw openclaw-weixin logs-upload` CLI subcommand. */
54
+ /** Register the `openclaw openclaw-weixin` CLI subcommands. */
55
55
  export function registerWeixinCli(params: { program: CliCommand; config: OpenClawConfig }): void {
56
56
  const { program, config } = params;
57
57
 
58
58
  const root = program.command("openclaw-weixin").description("Weixin channel utilities");
59
59
 
60
+ root
61
+ .command("uninstall")
62
+ .description("Uninstall the Weixin plugin (cleans up channel config automatically)")
63
+ .action(async () => {
64
+ // 1. Remove channels.openclaw-weixin from config
65
+ const { loadConfig, writeConfigFile } = await import("openclaw/plugin-sdk/config-runtime");
66
+ const cfg = loadConfig();
67
+ const channels = (cfg.channels ?? {}) as Record<string, unknown>;
68
+ if (channels["openclaw-weixin"]) {
69
+ delete channels["openclaw-weixin"];
70
+ await writeConfigFile({ ...cfg, channels });
71
+ console.log("[weixin] Cleaned up channel config.");
72
+ }
73
+ // 2. Run the actual uninstall
74
+ const { execSync } = await import("node:child_process");
75
+ try {
76
+ execSync("openclaw plugins uninstall openclaw-weixin", { stdio: "inherit" });
77
+ } catch {
78
+ // uninstall command handles its own error output
79
+ }
80
+ });
81
+
60
82
  root
61
83
  .command("logs-upload")
62
84
  .description("Upload a Weixin log file to a remote URL via HTTP POST")
@@ -1,12 +1,12 @@
1
1
  import path from "node:path";
2
2
 
3
+ import { createTypingCallbacks } from "openclaw/plugin-sdk/channel-runtime";
3
4
  import {
4
- createTypingCallbacks,
5
5
  resolveSenderCommandAuthorizationWithRuntime,
6
6
  resolveDirectDmAuthorizationOutcome,
7
- resolvePreferredOpenClawTmpDir,
8
- } from "openclaw/plugin-sdk";
9
- import type { PluginRuntime } from "openclaw/plugin-sdk";
7
+ } from "openclaw/plugin-sdk/command-auth";
8
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/infra-runtime";
9
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
10
10
 
11
11
  import { sendTyping } from "../api/api.js";
12
12
  import type { WeixinMessage } from "../api/types.js";
@@ -1,5 +1,5 @@
1
- import type { ReplyPayload } from "openclaw/plugin-sdk";
2
- import { stripMarkdown } from "openclaw/plugin-sdk";
1
+ import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
2
+ import { stripMarkdown } from "openclaw/plugin-sdk/text-runtime";
3
3
 
4
4
  import { sendMessage as sendMessageApi } from "../api/api.js";
5
5
  import type { WeixinApiOptions } from "../api/api.js";
@@ -1,4 +1,5 @@
1
- import type { ChannelAccountSnapshot, PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract";
2
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
2
3
 
3
4
  import { getUpdates } from "../api/api.js";
4
5
  import { WeixinConfigManager } from "../api/config-cache.js";
package/src/runtime.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/core";
2
2
 
3
3
  import { logger } from "./util/logger.js";
4
4
 
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
- import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk";
5
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/infra-runtime";
6
6
 
7
7
  /**
8
8
  * Plugin logger — writes JSON lines to the main openclaw log file: