@elizaos/plugin-wechat 2.0.0-beta.1 → 2.0.3-beta.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shaw Walters and elizaOS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,12 +2,18 @@
2
2
 
3
3
  WeChat connector plugin for [elizaOS](https://github.com/elizaOS/eliza) via proxy API.
4
4
 
5
+ Adds WeChat DM and group messaging to an Eliza agent. The plugin connects to a
6
+ third-party WeChat proxy service, starts a local webhook server to receive
7
+ inbound messages, and registers a `MessageConnector` so the agent can send/
8
+ receive text and images, resolve contacts, and read chat history.
9
+
5
10
  ## Features
6
- - Text and image messaging
7
- - DM and group support
11
+
12
+ - Text and image messaging (DM and group)
8
13
  - Multi-account support
9
- - QR code login flow
10
- - Webhook-based message delivery
14
+ - QR-code login flow (prints URL to terminal; scan with WeChat mobile)
15
+ - Webhook-based inbound message delivery with deduplication
16
+ - Automatic session health checks and re-login on expiry
11
17
 
12
18
  ## Install
13
19
 
@@ -17,10 +23,59 @@ npx elizaos plugins add @elizaos/plugin-wechat
17
23
 
18
24
  ## Configuration
19
25
 
20
- | Env Var | Description |
21
- |---------|-------------|
22
- | `WECHAT_API_KEY` | Proxy service API key |
23
- | `WECHAT_WEBHOOK_PORT` | Webhook listener port (default: 18790) |
26
+ ### Environment variables (single-account)
27
+
28
+ | Env Var | Required | Description |
29
+ |---|---|---|
30
+ | `WECHAT_API_KEY` | Yes | Proxy service API key |
31
+ | `WECHAT_PROXY_URL` | Yes | Base URL of the WeChat proxy (`https://` only) |
32
+ | `ELIZA_WECHAT_WEBHOOK_PORT` | No | Webhook listener port (default: `18790`) |
33
+
34
+ ### Character config block (recommended)
35
+
36
+ ```jsonc
37
+ {
38
+ "connectors": {
39
+ "wechat": {
40
+ "apiKey": "YOUR_API_KEY",
41
+ "proxyUrl": "https://your-proxy.example.com",
42
+ "webhookPort": 18790,
43
+ "deviceType": "ipad",
44
+ "loginTimeoutMs": 300000,
45
+ "features": {
46
+ "images": true,
47
+ "groups": true
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ #### Multi-account
55
+
56
+ ```jsonc
57
+ {
58
+ "connectors": {
59
+ "wechat": {
60
+ "accounts": {
61
+ "main": { "apiKey": "...", "proxyUrl": "https://proxy1.example.com" },
62
+ "support": { "apiKey": "...", "proxyUrl": "https://proxy2.example.com" }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## How it works
70
+
71
+ 1. On agent startup the plugin reads config, spins up a local HTTP webhook
72
+ server (default port 18790), and connects to the proxy.
73
+ 2. If the WeChat session is not active, it fetches a QR-code login URL and
74
+ prints it to the terminal. Scan it with the WeChat mobile app.
75
+ 3. Once logged in, the proxy pushes inbound messages to the webhook server.
76
+ The plugin normalises the payload and routes it through the elizaOS message
77
+ pipeline (the agent reads and replies normally).
78
+ 4. Outgoing replies are chunked at 2 000 characters and sent back via the proxy.
79
+ 5. A background health check runs every 60 seconds and re-initiates login if
80
+ the session expires.
24
81
 
25
- ## License
26
- MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/plugin-wechat",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.3-beta.6",
4
4
  "description": "WeChat connector plugin for elizaOS via proxy API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,12 +13,28 @@
13
13
  "types": "dist/index.d.ts",
14
14
  "exports": {
15
15
  ".": {
16
+ "eliza-source": {
17
+ "types": "./src/index.ts",
18
+ "import": "./src/index.ts",
19
+ "default": "./src/index.ts"
20
+ },
16
21
  "import": {
17
22
  "types": "./dist/index.d.ts",
18
23
  "default": "./dist/index.js"
19
24
  }
20
25
  },
21
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./*.css": "./dist/*.css",
28
+ "./*": {
29
+ "types": "./dist/*.d.ts",
30
+ "eliza-source": {
31
+ "types": "./src/*.ts",
32
+ "import": "./src/*.ts",
33
+ "default": "./src/*.ts"
34
+ },
35
+ "import": "./dist/*.js",
36
+ "default": "./dist/*.js"
37
+ }
22
38
  },
23
39
  "files": [
24
40
  "dist",
@@ -34,21 +50,21 @@
34
50
  }
35
51
  },
36
52
  "scripts": {
37
- "build": "tsdown && mv dist/index-*.d.ts dist/index.d.ts 2>/dev/null || true",
53
+ "build": "tsup --config tsup.config.ts && tsc --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src",
38
54
  "check": "tsc --noEmit",
39
55
  "test": "vitest run --config ./vitest.config.ts",
40
56
  "test:watch": "vitest --config ./vitest.config.ts",
41
- "clean": "rm -rf dist"
57
+ "clean": "node ../../packages/scripts/rm-path-recursive.mjs dist"
42
58
  },
43
59
  "peerDependencies": {
44
- "@elizaos/core": "2.0.0-beta.1"
60
+ "@elizaos/core": "2.0.3-beta.6"
45
61
  },
46
62
  "publishConfig": {
47
63
  "access": "public"
48
64
  },
49
65
  "devDependencies": {
50
- "@elizaos/core": "2.0.0-beta.1",
51
- "tsdown": "^0.21.10",
66
+ "@elizaos/core": "2.0.3-beta.6",
67
+ "tsup": "^8.5.1",
52
68
  "typescript": "^6.0.3",
53
69
  "vitest": "^4.0.18"
54
70
  },
@@ -70,5 +86,6 @@
70
86
  "connector",
71
87
  "messaging"
72
88
  ],
73
- "packageType": "plugin"
89
+ "packageType": "plugin",
90
+ "gitHead": "990dc996172b3e0fb525a75052a5ac28a4cd4de5"
74
91
  }
@@ -0,0 +1,44 @@
1
+ import type { ConnectorAccountManager, IAgentRuntime } from "@elizaos/core";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { createWechatConnectorAccountProvider } from "./connector-account-provider";
4
+
5
+ const manager = {} as ConnectorAccountManager;
6
+
7
+ describe("createWechatConnectorAccountProvider", () => {
8
+ it("returns no accounts when WeChat is not configured", async () => {
9
+ const runtime = {
10
+ character: { settings: {} },
11
+ getSetting: vi.fn(() => undefined),
12
+ } as unknown as IAgentRuntime;
13
+ const provider = createWechatConnectorAccountProvider(runtime);
14
+
15
+ await expect(provider.listAccounts?.(manager)).resolves.toEqual([]);
16
+ });
17
+
18
+ it("surfaces env-configured single-account credentials", async () => {
19
+ const runtime = {
20
+ character: { settings: {} },
21
+ getSetting: vi.fn((key: string) => {
22
+ if (key === "WECHAT_API_KEY") return "wechat-key";
23
+ if (key === "WECHAT_PROXY_URL") return "https://proxy.example.com";
24
+ return undefined;
25
+ }),
26
+ } as unknown as IAgentRuntime;
27
+ const provider = createWechatConnectorAccountProvider(runtime);
28
+
29
+ await expect(provider.listAccounts?.(manager)).resolves.toEqual([
30
+ expect.objectContaining({
31
+ id: "default",
32
+ provider: "wechat",
33
+ label: "default",
34
+ role: "AGENT",
35
+ purpose: ["messaging"],
36
+ accessGate: "open",
37
+ status: "connected",
38
+ metadata: expect.objectContaining({
39
+ proxyUrl: "https://proxy.example.com",
40
+ }),
41
+ }),
42
+ ]);
43
+ });
44
+ });
@@ -20,8 +20,8 @@ import type {
20
20
  } from "@elizaos/core";
21
21
  import type { WechatConfig } from "./types";
22
22
 
23
- export const WECHAT_PROVIDER_ID = "wechat";
24
- export const WECHAT_DEFAULT_ACCOUNT_ID = "default";
23
+ const WECHAT_PROVIDER_ID = "wechat";
24
+ const WECHAT_DEFAULT_ACCOUNT_ID = "default";
25
25
 
26
26
  function getWechatConfig(runtime: IAgentRuntime): WechatConfig | undefined {
27
27
  const character = runtime.character?.settings as
@@ -136,17 +136,6 @@ export function createWechatConnectorAccountProvider(
136
136
  _manager: ConnectorAccountManager,
137
137
  ): Promise<ConnectorAccount[]> => {
138
138
  const accounts = listWechatAccounts(runtime);
139
- if (accounts.length === 0) {
140
- // No accounts configured — surface a placeholder default so the
141
- // connector is visible in the manager UI.
142
- return [
143
- toConnectorAccount({
144
- id: WECHAT_DEFAULT_ACCOUNT_ID,
145
- enabled: false,
146
- apiKeyConfigured: false,
147
- }),
148
- ];
149
- }
150
139
  return accounts.map(toConnectorAccount);
151
140
  },
152
141
  createAccount: async (
@@ -173,7 +162,7 @@ export function createWechatConnectorAccountProvider(
173
162
  _accountId: string,
174
163
  _manager: ConnectorAccountManager,
175
164
  ) => {
176
- // No-op at provider layer runtime credentials live in character
165
+ // Provider-layer account deletion returns cleanly; runtime credentials live in character
177
166
  // settings; deletion of those is out of band.
178
167
  },
179
168
  };
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
- getConnectorAccountManager,
3
- stringToUuid,
4
2
  type Content,
3
+ getConnectorAccountManager,
5
4
  type IAgentRuntime,
6
5
  type Memory,
7
6
  type MessageConnectorTarget,
7
+ stringToUuid,
8
8
  type TargetInfo,
9
9
  type UUID,
10
10
  } from "@elizaos/core";
@@ -48,6 +48,7 @@ export interface Plugin {
48
48
  config: Record<string, unknown>,
49
49
  runtime: unknown,
50
50
  ) => Promise<void | (() => Promise<void>)>;
51
+ dispose?: () => Promise<void> | void;
51
52
  /**
52
53
  * Declarative auto-enable conditions consumed by the runtime's
53
54
  * plugin-auto-enable engine. Mirrors the shape on `@elizaos/core` Plugin.
@@ -84,31 +85,32 @@ type RuntimeWithWechatConnector = {
84
85
  };
85
86
 
86
87
  type WechatConnectorReadParams = {
87
- target?: TargetInfo;
88
- limit?: number;
89
- query?: string;
88
+ target?: TargetInfo;
89
+ limit?: number;
90
+ query?: string;
90
91
  };
91
92
 
92
93
  function readRuntimeSetting(runtime: unknown, key: string): string | undefined {
93
- const value = (runtime as { getSetting?: (setting: string) => unknown })
94
- .getSetting?.(key);
95
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
94
+ const value = (
95
+ runtime as { getSetting?: (setting: string) => unknown }
96
+ ).getSetting?.(key);
97
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
96
98
  }
97
99
 
98
100
  function resolveWechatConfig(
99
- config: Record<string, unknown>,
100
- runtime: unknown,
101
+ config: Record<string, unknown>,
102
+ runtime: unknown,
101
103
  ): WechatConfig | undefined {
102
- const explicit = (config as { connectors?: { wechat?: WechatConfig } })
103
- ?.connectors?.wechat;
104
- if (explicit) return explicit;
105
- const apiKey = readRuntimeSetting(runtime, "WECHAT_API_KEY");
106
- const proxyUrl = readRuntimeSetting(runtime, "WECHAT_PROXY_URL");
107
- if (!apiKey && !proxyUrl) return undefined;
108
- return {
109
- apiKey,
110
- proxyUrl,
111
- };
104
+ const explicit = (config as { connectors?: { wechat?: WechatConfig } })
105
+ ?.connectors?.wechat;
106
+ if (explicit) return explicit;
107
+ const apiKey = readRuntimeSetting(runtime, "WECHAT_API_KEY");
108
+ const proxyUrl = readRuntimeSetting(runtime, "WECHAT_PROXY_URL");
109
+ if (!apiKey && !proxyUrl) return undefined;
110
+ return {
111
+ apiKey,
112
+ proxyUrl,
113
+ };
112
114
  }
113
115
 
114
116
  function normalizeConnectorLimit(
@@ -418,6 +420,13 @@ const wechatPlugin: Plugin = {
418
420
  }
419
421
  };
420
422
  },
423
+ async dispose() {
424
+ if (channel) {
425
+ await channel.stop();
426
+ channel = null;
427
+ console.log("[wechat] Plugin disposed");
428
+ }
429
+ },
421
430
  };
422
431
 
423
432
  export default wechatPlugin;
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
- export type DeviceType = "ipad" | "mac";
2
- export type LoginStatus = "waiting" | "need_verify" | "logged_in";
1
+ type DeviceType = "ipad" | "mac";
2
+ type LoginStatus = "waiting" | "need_verify" | "logged_in";
3
3
 
4
- export interface WechatAccountConfig {
4
+ interface WechatAccountConfig {
5
5
  enabled?: boolean;
6
6
  name?: string;
7
7
  apiKey: string;
package/dist/index.d.ts DELETED
@@ -1,193 +0,0 @@
1
- //#region src/types.d.ts
2
- type DeviceType = "ipad" | "mac";
3
- type LoginStatus = "waiting" | "need_verify" | "logged_in";
4
- interface WechatAccountConfig {
5
- enabled?: boolean;
6
- name?: string;
7
- apiKey: string;
8
- proxyUrl: string;
9
- deviceType?: DeviceType;
10
- webhookPort?: number;
11
- webhookUrl?: string;
12
- wcId?: string;
13
- nickName?: string;
14
- }
15
- interface WechatConfig {
16
- enabled?: boolean;
17
- apiKey?: string;
18
- proxyUrl?: string;
19
- webhookPort?: number;
20
- deviceType?: DeviceType;
21
- loginTimeoutMs?: number;
22
- accounts?: Record<string, WechatAccountConfig>;
23
- features?: {
24
- images?: boolean;
25
- groups?: boolean;
26
- };
27
- }
28
- interface ResolvedWechatAccount {
29
- id: string;
30
- apiKey: string;
31
- proxyUrl: string;
32
- deviceType: DeviceType;
33
- webhookPort: number;
34
- wcId?: string;
35
- nickName?: string;
36
- }
37
- type WechatMessageType = "text" | "image" | "video" | "file" | "voice" | "unknown";
38
- interface WechatMessageContext {
39
- id: string;
40
- type: WechatMessageType;
41
- sender: string;
42
- recipient: string;
43
- content: string;
44
- timestamp: number;
45
- threadId?: string;
46
- group?: {
47
- subject: string;
48
- };
49
- imageUrl?: string;
50
- raw: unknown;
51
- }
52
- interface AccountStatus {
53
- valid: boolean;
54
- wcId?: string;
55
- loginState: LoginStatus;
56
- nickName?: string;
57
- tier?: string;
58
- quota?: number;
59
- }
60
- //#endregion
61
- //#region src/bot.d.ts
62
- interface BotOptions {
63
- onMessage: (msg: WechatMessageContext) => void | Promise<void>;
64
- featuresGroups?: boolean;
65
- featuresImages?: boolean;
66
- /** Deduplication window in milliseconds. Defaults to 30 minutes. */
67
- dedupWindowMs?: number;
68
- }
69
- declare class Bot {
70
- private readonly seen;
71
- private readonly onMessage;
72
- private readonly featuresGroups;
73
- private readonly featuresImages;
74
- private readonly dedupWindowMs;
75
- private cleanupTimer;
76
- constructor(options: BotOptions);
77
- handleIncoming(message: WechatMessageContext): void;
78
- private isDuplicate;
79
- private cleanup;
80
- stop(): void;
81
- }
82
- //#endregion
83
- //#region src/channel.d.ts
84
- interface ChannelOptions {
85
- config: WechatConfig;
86
- onMessage: (accountId: string, msg: WechatMessageContext) => void | Promise<void>;
87
- }
88
- declare class WechatChannel {
89
- private readonly config;
90
- private readonly onMessage;
91
- private readonly accounts;
92
- private readonly callbackServers;
93
- private readonly loginPromises;
94
- private healthTimer;
95
- private abortController;
96
- constructor(options: ChannelOptions);
97
- start(): Promise<void>;
98
- stop(): Promise<void>;
99
- sendText(accountId: string, to: string, text: string): Promise<void>;
100
- sendImage(accountId: string, to: string, imagePath: string, caption?: string): Promise<void>;
101
- getAccountIds(): string[];
102
- listContacts(accountId: string): Promise<{
103
- friends: Array<{
104
- wxid: string;
105
- name: string;
106
- }>;
107
- chatrooms: Array<{
108
- wxid: string;
109
- name: string;
110
- }>;
111
- }>;
112
- private routeIncoming;
113
- private ensureLoggedIn;
114
- private doLogin;
115
- private healthCheck;
116
- private resolveAccounts;
117
- }
118
- //#endregion
119
- //#region src/proxy-client.d.ts
120
- declare class ProxyClient {
121
- private readonly apiKey;
122
- private readonly baseUrl;
123
- private readonly accountId;
124
- private readonly deviceType;
125
- constructor(account: ResolvedWechatAccount);
126
- private request;
127
- getStatus(): Promise<AccountStatus>;
128
- getQRCode(): Promise<string>;
129
- checkLogin(): Promise<{
130
- status: "waiting" | "need_verify" | "logged_in";
131
- verifyUrl?: string;
132
- wcId?: string;
133
- nickName?: string;
134
- }>;
135
- sendText(to: string, text: string): Promise<void>;
136
- sendImage(to: string, imagePath: string, text?: string): Promise<void>;
137
- getContacts(): Promise<{
138
- friends: Array<{
139
- wxid: string;
140
- name: string;
141
- }>;
142
- chatrooms: Array<{
143
- wxid: string;
144
- name: string;
145
- }>;
146
- }>;
147
- registerWebhook(url: string): Promise<void>;
148
- get needsLogin(): boolean;
149
- }
150
- //#endregion
151
- //#region src/reply-dispatcher.d.ts
152
- interface ReplyDispatcherOptions {
153
- client: ProxyClient;
154
- chunkSize?: number;
155
- }
156
- declare class ReplyDispatcher {
157
- private readonly client;
158
- private readonly chunkSize;
159
- constructor(options: ReplyDispatcherOptions);
160
- sendText(to: string, text: string): Promise<void>;
161
- sendImage(to: string, imagePath: string, caption?: string): Promise<void>;
162
- private chunk;
163
- }
164
- //#endregion
165
- //#region src/runtime-bridge.d.ts
166
- interface IncomingWechatDeliveryOptions {
167
- runtime: unknown;
168
- accountId: string;
169
- message: WechatMessageContext;
170
- sendText: (accountId: string, to: string, text: string) => Promise<void>;
171
- }
172
- declare function deliverIncomingWechatMessage(options: IncomingWechatDeliveryOptions): Promise<void>;
173
- //#endregion
174
- //#region src/index.d.ts
175
- declare const WECHAT_PLUGIN_PACKAGE: "@elizaos/plugin-wechat";
176
- declare function isWechatConnectorConfigured(config: WechatConfig | Record<string, unknown> | null | undefined): boolean;
177
- interface Plugin {
178
- name: string;
179
- description: string;
180
- init?: (config: Record<string, unknown>, runtime: unknown) => Promise<void | (() => Promise<void>)>;
181
- /**
182
- * Declarative auto-enable conditions consumed by the runtime's
183
- * plugin-auto-enable engine. Mirrors the shape on `@elizaos/core` Plugin.
184
- */
185
- autoEnable?: {
186
- envKeys?: string[];
187
- connectorKeys?: string[];
188
- shouldEnable?: (env: Record<string, string | undefined>, config: Record<string, unknown>) => boolean;
189
- };
190
- }
191
- declare const wechatPlugin: Plugin;
192
- //#endregion
193
- export { Bot, Plugin, ProxyClient, ReplyDispatcher, WECHAT_PLUGIN_PACKAGE, WechatChannel, type WechatConfig, type WechatMessageContext, wechatPlugin as default, wechatPlugin, deliverIncomingWechatMessage, isWechatConnectorConfigured };