@elizaos/plugin-wechat 2.0.0-alpha.537 → 2.0.0-beta.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/auto-enable.ts ADDED
@@ -0,0 +1,21 @@
1
+ // Auto-enable check for @elizaos/plugin-wechat.
2
+ //
3
+ // Plugin manifest entry-point — referenced by package.json's
4
+ // `elizaos.plugin.autoEnableModule`. Keep this module light: env reads only,
5
+ // no service init, no transitive imports of the full plugin runtime. The
6
+ // auto-enable engine loads dozens of these per boot.
7
+ import type { PluginAutoEnableContext } from "@elizaos/core";
8
+
9
+ /** Enable when a `wechat` connector block is present and not explicitly disabled. */
10
+ export function shouldEnable(ctx: PluginAutoEnableContext): boolean {
11
+ const c = (ctx.config?.connectors as Record<string, unknown> | undefined)
12
+ ?.wechat;
13
+ if (!c || typeof c !== "object") return false;
14
+ const config = c as Record<string, unknown>;
15
+ if (config.enabled === false) return false;
16
+ // The full per-connector field check (appId/appSecret/encodingAESKey) lives
17
+ // in the central engine's isConnectorConfigured. We delegate to a simple
18
+ // "block present + not explicitly disabled" check here; the central
19
+ // engine's stricter check remains as a fallback during migration.
20
+ return true;
21
+ }
package/dist/index.d.ts CHANGED
@@ -98,6 +98,17 @@ declare class WechatChannel {
98
98
  stop(): Promise<void>;
99
99
  sendText(accountId: string, to: string, text: string): Promise<void>;
100
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
+ }>;
101
112
  private routeIncoming;
102
113
  private ensureLoggedIn;
103
114
  private doLogin;
@@ -167,6 +178,15 @@ interface Plugin {
167
178
  name: string;
168
179
  description: string;
169
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
+ };
170
190
  }
171
191
  declare const wechatPlugin: Plugin;
172
192
  //#endregion
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
+ import { getConnectorAccountManager, stringToUuid } from "@elizaos/core";
1
2
  import { timingSafeEqual } from "node:crypto";
2
3
  import { createServer } from "node:http";
3
- import { stringToUuid } from "@elizaos/core";
4
-
5
4
  //#region src/bot.ts
6
5
  const DEFAULT_DEDUP_WINDOW_MS = 1800 * 1e3;
7
6
  const DEDUP_MAX_ENTRIES = 1e3;
@@ -48,7 +47,6 @@ var Bot = class {
48
47
  this.seen.clear();
49
48
  }
50
49
  };
51
-
52
50
  //#endregion
53
51
  //#region src/callback-server.ts
54
52
  const WECHAT_TYPE_MAP = {
@@ -251,7 +249,6 @@ function normalizePayload(payload) {
251
249
  raw: payload
252
250
  };
253
251
  }
254
-
255
252
  //#endregion
256
253
  //#region src/proxy-client.ts
257
254
  const SUCCESS = 1e3;
@@ -367,7 +364,6 @@ function requireData(response, action) {
367
364
  if (response.data === void 0) throw new Error(`${action} failed: missing response data`);
368
365
  return response.data;
369
366
  }
370
-
371
367
  //#endregion
372
368
  //#region src/reply-dispatcher.ts
373
369
  const DEFAULT_CHUNK_SIZE = 2e3;
@@ -413,7 +409,6 @@ var ReplyDispatcher = class {
413
409
  return chunks;
414
410
  }
415
411
  };
416
-
417
412
  //#endregion
418
413
  //#region src/utils/qrcode.ts
419
414
  /**
@@ -432,7 +427,6 @@ function displayQRUrl(url) {
432
427
  console.log("Open the URL above in your browser to see the QR code.");
433
428
  console.log("");
434
429
  }
435
-
436
430
  //#endregion
437
431
  //#region src/channel.ts
438
432
  const HEALTH_CHECK_INTERVAL_MS = 6e4;
@@ -540,6 +534,14 @@ var WechatChannel = class {
540
534
  } else throw err;
541
535
  }
542
536
  }
537
+ getAccountIds() {
538
+ return Array.from(this.accounts.keys());
539
+ }
540
+ async listContacts(accountId) {
541
+ const entry = this.accounts.get(accountId);
542
+ if (!entry) throw new Error(`Unknown account: ${accountId}`);
543
+ return entry.client.getContacts();
544
+ }
543
545
  routeIncoming(accountId, msg) {
544
546
  const entry = this.accounts.get(accountId);
545
547
  if (!entry) {
@@ -619,7 +621,110 @@ var WechatChannel = class {
619
621
  function sleep(ms) {
620
622
  return new Promise((resolve) => setTimeout(resolve, ms));
621
623
  }
622
-
624
+ //#endregion
625
+ //#region src/connector-account-provider.ts
626
+ const WECHAT_PROVIDER_ID = "wechat";
627
+ const WECHAT_DEFAULT_ACCOUNT_ID = "default";
628
+ function getWechatConfig(runtime) {
629
+ const character = runtime.character?.settings;
630
+ return character?.connectors?.wechat ?? character?.wechat;
631
+ }
632
+ function listWechatAccounts(runtime) {
633
+ const config = getWechatConfig(runtime);
634
+ const result = [];
635
+ if (!config) {
636
+ const envApiKey = runtime.getSetting?.("WECHAT_API_KEY");
637
+ const envProxy = runtime.getSetting?.("WECHAT_PROXY_URL");
638
+ if (envApiKey?.trim() || envProxy?.trim()) result.push({
639
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
640
+ enabled: true,
641
+ apiKeyConfigured: Boolean(envApiKey?.trim()),
642
+ proxyUrl: envProxy?.trim() || void 0
643
+ });
644
+ return result;
645
+ }
646
+ if (config.enabled === false) {
647
+ if (config.apiKey?.trim() || config.accounts) result.push({
648
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
649
+ enabled: false,
650
+ apiKeyConfigured: Boolean(config.apiKey?.trim()),
651
+ proxyUrl: config.proxyUrl
652
+ });
653
+ return result;
654
+ }
655
+ if (config.apiKey?.trim()) result.push({
656
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
657
+ enabled: true,
658
+ apiKeyConfigured: true,
659
+ proxyUrl: config.proxyUrl
660
+ });
661
+ if (config.accounts && typeof config.accounts === "object") for (const [id, account] of Object.entries(config.accounts)) {
662
+ if (!id) continue;
663
+ result.push({
664
+ id: id.trim().toLowerCase(),
665
+ enabled: account.enabled !== false,
666
+ apiKeyConfigured: Boolean(account.apiKey?.trim()),
667
+ proxyUrl: account.proxyUrl,
668
+ wcId: account.wcId,
669
+ nickName: account.nickName,
670
+ name: account.name
671
+ });
672
+ }
673
+ return result;
674
+ }
675
+ function toConnectorAccount(account) {
676
+ const now = Date.now();
677
+ return {
678
+ id: account.id,
679
+ provider: WECHAT_PROVIDER_ID,
680
+ label: account.name ?? account.nickName ?? account.id,
681
+ role: "AGENT",
682
+ purpose: ["messaging"],
683
+ accessGate: "open",
684
+ status: account.enabled && account.apiKeyConfigured ? "connected" : "disabled",
685
+ externalId: account.wcId || void 0,
686
+ displayHandle: account.nickName || void 0,
687
+ createdAt: now,
688
+ updatedAt: now,
689
+ metadata: {
690
+ proxyUrl: account.proxyUrl ?? "",
691
+ wcId: account.wcId ?? "",
692
+ nickName: account.nickName ?? ""
693
+ }
694
+ };
695
+ }
696
+ function createWechatConnectorAccountProvider(runtime) {
697
+ return {
698
+ provider: WECHAT_PROVIDER_ID,
699
+ label: "WeChat",
700
+ listAccounts: async (_manager) => {
701
+ const accounts = listWechatAccounts(runtime);
702
+ if (accounts.length === 0) return [toConnectorAccount({
703
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
704
+ enabled: false,
705
+ apiKeyConfigured: false
706
+ })];
707
+ return accounts.map(toConnectorAccount);
708
+ },
709
+ createAccount: async (input, _manager) => {
710
+ return {
711
+ ...input,
712
+ provider: WECHAT_PROVIDER_ID,
713
+ role: input.role ?? "AGENT",
714
+ purpose: input.purpose ?? ["messaging"],
715
+ accessGate: input.accessGate ?? "open",
716
+ status: input.status ?? "pending"
717
+ };
718
+ },
719
+ patchAccount: async (_accountId, patch, _manager) => {
720
+ return {
721
+ ...patch,
722
+ provider: WECHAT_PROVIDER_ID
723
+ };
724
+ },
725
+ deleteAccount: async (_accountId, _manager) => {}
726
+ };
727
+ }
623
728
  //#endregion
624
729
  //#region src/runtime-bridge.ts
625
730
  async function deliverIncomingWechatMessage(options) {
@@ -775,7 +880,6 @@ async function maybeHandleResponseContent(result, replyDelivered, onResponse) {
775
880
  if (replyDelivered || !result?.responseContent) return;
776
881
  await onResponse(result.responseContent);
777
882
  }
778
-
779
883
  //#endregion
780
884
  //#region src/index.ts
781
885
  const WECHAT_PLUGIN_PACKAGE = "@elizaos/plugin-wechat";
@@ -790,11 +894,154 @@ function isWechatConnectorConfigured(config) {
790
894
  return false;
791
895
  }
792
896
  let channel = null;
897
+ function readRuntimeSetting(runtime, key) {
898
+ const value = runtime.getSetting?.(key);
899
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
900
+ }
901
+ function resolveWechatConfig(config, runtime) {
902
+ const explicit = config?.connectors?.wechat;
903
+ if (explicit) return explicit;
904
+ const apiKey = readRuntimeSetting(runtime, "WECHAT_API_KEY");
905
+ const proxyUrl = readRuntimeSetting(runtime, "WECHAT_PROXY_URL");
906
+ if (!apiKey && !proxyUrl) return void 0;
907
+ return {
908
+ apiKey,
909
+ proxyUrl
910
+ };
911
+ }
912
+ function normalizeConnectorLimit(limit, fallback = 50) {
913
+ if (!Number.isFinite(limit) || !limit || limit <= 0) return fallback;
914
+ return Math.min(Math.floor(limit), 200);
915
+ }
916
+ function getConfiguredAccountIds(config) {
917
+ if (config.accounts && typeof config.accounts === "object") return Object.entries(config.accounts).filter(([, account]) => account.enabled !== false && Boolean(account.apiKey)).map(([id]) => id);
918
+ return config.apiKey ? ["default"] : [];
919
+ }
920
+ function resolveWechatAccountId(config, target) {
921
+ const metadata = target?.metadata;
922
+ const accountId = typeof metadata?.accountId === "string" && metadata.accountId.trim() ? metadata.accountId.trim() : void 0;
923
+ if (accountId) return accountId;
924
+ return channel?.getAccountIds()[0] ?? getConfiguredAccountIds(config)[0] ?? "default";
925
+ }
926
+ function wechatTarget(accountId, wxid, name, kind, score = .55) {
927
+ return {
928
+ target: {
929
+ source: "wechat",
930
+ channelId: wxid,
931
+ roomId: stringToUuid(`wechat:room:${accountId}:${wxid}`),
932
+ metadata: { accountId }
933
+ },
934
+ label: name || wxid,
935
+ kind,
936
+ score,
937
+ contexts: ["social", "connectors"],
938
+ metadata: {
939
+ accountId,
940
+ wxid
941
+ }
942
+ };
943
+ }
944
+ async function listWechatTargets(config) {
945
+ if (!channel) return [];
946
+ const targets = [];
947
+ for (const accountId of channel.getAccountIds()) {
948
+ const contacts = await channel.listContacts(accountId).catch(() => null);
949
+ if (!contacts) continue;
950
+ targets.push(...contacts.friends.map((friend) => wechatTarget(accountId, friend.wxid, friend.name, "user")), ...contacts.chatrooms.map((chatroom) => wechatTarget(accountId, chatroom.wxid, chatroom.name, "group")));
951
+ }
952
+ if (targets.length > 0) return targets;
953
+ return getConfiguredAccountIds(config).map((accountId) => wechatTarget(accountId, accountId, `WeChat account ${accountId}`, "user", .25));
954
+ }
955
+ function filterMemoriesByQuery(memories, query, limit) {
956
+ const normalized = query.trim().toLowerCase();
957
+ if (!normalized) return memories.slice(0, limit);
958
+ return memories.filter((memory) => {
959
+ return (typeof memory.content?.text === "string" ? memory.content.text : "").toLowerCase().includes(normalized);
960
+ }).slice(0, limit);
961
+ }
962
+ function registerWechatMessageConnector(runtime, config) {
963
+ const connectorRuntime = runtime;
964
+ const sendHandler = async (_runtime, target, content) => {
965
+ if (!channel) throw new Error("[wechat] Channel is not available");
966
+ const text = typeof content.text === "string" ? content.text.trim() : "";
967
+ if (!text) return;
968
+ const accountId = resolveWechatAccountId(config, target);
969
+ const to = String(target.channelId ?? target.entityId ?? "").trim();
970
+ if (!to) throw new Error("[wechat] target is missing channelId/entityId");
971
+ await channel.sendText(accountId, to, text);
972
+ };
973
+ if (typeof connectorRuntime.registerMessageConnector === "function") {
974
+ connectorRuntime.registerMessageConnector({
975
+ source: "wechat",
976
+ label: "WeChat",
977
+ description: "WeChat connector for sending and reading stored DM/group messages.",
978
+ capabilities: [
979
+ "send_message",
980
+ "resolve_targets",
981
+ "list_rooms",
982
+ "chat_context"
983
+ ],
984
+ supportedTargetKinds: [
985
+ "user",
986
+ "group",
987
+ "room"
988
+ ],
989
+ contexts: ["social", "connectors"],
990
+ resolveTargets: async (query) => {
991
+ const normalized = query.trim().toLowerCase();
992
+ return (await listWechatTargets(config)).map((target) => {
993
+ const haystack = `${target.label ?? ""} ${target.target.channelId ?? ""}`.toLowerCase();
994
+ return {
995
+ ...target,
996
+ score: normalized && haystack.includes(normalized) ? .8 : target.score ?? .4
997
+ };
998
+ }).filter((target) => !normalized || (target.score ?? 0) >= .8).slice(0, 25);
999
+ },
1000
+ listRecentTargets: async () => (await listWechatTargets(config)).slice(0, 10),
1001
+ listRooms: async () => listWechatTargets(config),
1002
+ fetchMessages: async (context, params) => {
1003
+ const limit = normalizeConnectorLimit(params?.limit);
1004
+ const target = params?.target ?? context.target;
1005
+ if (target?.roomId) return context.runtime.getMemories({
1006
+ tableName: "messages",
1007
+ roomId: target.roomId,
1008
+ limit,
1009
+ orderBy: "createdAt",
1010
+ orderDirection: "desc"
1011
+ });
1012
+ const targets = (await listWechatTargets(config)).slice(0, 10);
1013
+ return (await Promise.all(targets.map((candidate) => candidate.target.roomId).filter((roomId) => Boolean(roomId)).map((roomId) => context.runtime.getMemories({
1014
+ tableName: "messages",
1015
+ roomId,
1016
+ limit,
1017
+ orderBy: "createdAt",
1018
+ orderDirection: "desc"
1019
+ })))).flat().sort((left, right) => (right.createdAt ?? 0) - (left.createdAt ?? 0)).slice(0, limit);
1020
+ },
1021
+ searchMessages: async (context, params) => {
1022
+ const limit = normalizeConnectorLimit(params.limit);
1023
+ return filterMemoriesByQuery(await (connectorRuntime.getMessageConnectors?.().find((connector) => connector.source === "wechat"))?.fetchMessages?.(context, {
1024
+ target: params.target ?? context.target,
1025
+ limit: Math.max(limit, 100)
1026
+ }) ?? [], params.query, limit);
1027
+ },
1028
+ sendHandler
1029
+ });
1030
+ return;
1031
+ }
1032
+ connectorRuntime.registerSendHandler?.("wechat", sendHandler);
1033
+ }
793
1034
  const wechatPlugin = {
794
1035
  name: "wechat",
795
1036
  description: "WeChat messaging via proxy API",
1037
+ autoEnable: { connectorKeys: ["wechat"] },
796
1038
  async init(config, runtime) {
797
- const wechatConfig = config?.connectors?.wechat;
1039
+ try {
1040
+ getConnectorAccountManager(runtime).registerProvider(createWechatConnectorAccountProvider(runtime));
1041
+ } catch (err) {
1042
+ console.warn("[wechat] Failed to register provider with ConnectorAccountManager:", err instanceof Error ? err.message : String(err));
1043
+ }
1044
+ const wechatConfig = resolveWechatConfig(config, runtime);
798
1045
  if (!wechatConfig) {
799
1046
  console.warn("[wechat] No wechat config found in connectors — skipping");
800
1047
  return;
@@ -818,6 +1065,7 @@ const wechatPlugin = {
818
1065
  }
819
1066
  });
820
1067
  await channel.start();
1068
+ registerWechatMessageConnector(runtime, wechatConfig);
821
1069
  console.log("[wechat] Plugin initialized");
822
1070
  return async () => {
823
1071
  if (channel) {
@@ -828,6 +1076,5 @@ const wechatPlugin = {
828
1076
  };
829
1077
  }
830
1078
  };
831
-
832
1079
  //#endregion
833
- export { Bot, ProxyClient, ReplyDispatcher, WECHAT_PLUGIN_PACKAGE, WechatChannel, wechatPlugin as default, wechatPlugin, deliverIncomingWechatMessage, isWechatConnectorConfigured };
1080
+ export { Bot, ProxyClient, ReplyDispatcher, WECHAT_PLUGIN_PACKAGE, WechatChannel, wechatPlugin as default, wechatPlugin, deliverIncomingWechatMessage, isWechatConnectorConfigured };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/plugin-wechat",
3
- "version": "2.0.0-alpha.537",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "WeChat connector plugin for elizaOS via proxy API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,25 +22,34 @@
22
22
  },
23
23
  "files": [
24
24
  "dist",
25
- "src"
25
+ "src",
26
+ "auto-enable.ts"
26
27
  ],
28
+ "elizaos": {
29
+ "plugin": {
30
+ "autoEnableModule": "./auto-enable.ts",
31
+ "capabilities": [
32
+ "messaging"
33
+ ]
34
+ }
35
+ },
27
36
  "scripts": {
28
- "build": "tsdown src/index.ts --format esm --dts --clean && mv dist/index-*.d.ts dist/index.d.ts 2>/dev/null || true",
37
+ "build": "tsdown && mv dist/index-*.d.ts dist/index.d.ts 2>/dev/null || true",
29
38
  "check": "tsc --noEmit",
30
39
  "test": "vitest run --config ./vitest.config.ts",
31
40
  "test:watch": "vitest --config ./vitest.config.ts",
32
41
  "clean": "rm -rf dist"
33
42
  },
34
43
  "peerDependencies": {
35
- "@elizaos/core": "^2.0.0-alpha.537"
44
+ "@elizaos/core": "2.0.0-beta.1"
36
45
  },
37
46
  "publishConfig": {
38
47
  "access": "public"
39
48
  },
40
49
  "devDependencies": {
41
- "@elizaos/core": "workspace:*",
42
- "tsdown": "^0.21.0",
43
- "typescript": "^6.0.0",
50
+ "@elizaos/core": "2.0.0-beta.1",
51
+ "tsdown": "^0.21.10",
52
+ "typescript": "^6.0.3",
44
53
  "vitest": "^4.0.18"
45
54
  },
46
55
  "agentConfig": {
package/src/channel.ts CHANGED
@@ -184,6 +184,19 @@ export class WechatChannel {
184
184
  }
185
185
  }
186
186
 
187
+ getAccountIds(): string[] {
188
+ return Array.from(this.accounts.keys());
189
+ }
190
+
191
+ async listContacts(accountId: string): Promise<{
192
+ friends: Array<{ wxid: string; name: string }>;
193
+ chatrooms: Array<{ wxid: string; name: string }>;
194
+ }> {
195
+ const entry = this.accounts.get(accountId);
196
+ if (!entry) throw new Error(`Unknown account: ${accountId}`);
197
+ return entry.client.getContacts();
198
+ }
199
+
187
200
  private routeIncoming(accountId: string, msg: WechatMessageContext): void {
188
201
  const entry = this.accounts.get(accountId);
189
202
  if (!entry) {
@@ -0,0 +1,180 @@
1
+ /**
2
+ * WeChat ConnectorAccountManager provider.
3
+ *
4
+ * Adapts the multi-account scaffolding (`WechatConfig.accounts`) to the
5
+ * `ConnectorAccountProvider` contract from
6
+ * `@elizaos/core/connectors/account-manager`.
7
+ *
8
+ * Source of truth is the wechat config block (proxy URL + API key per account).
9
+ * AccountKey is the proxy account id (the key in `WechatConfig.accounts`) or
10
+ * `default` for single-account env-only deployments. Role is `AGENT` since
11
+ * wechat proxy creds authenticate the bot, not the user.
12
+ */
13
+
14
+ import type {
15
+ ConnectorAccount,
16
+ ConnectorAccountManager,
17
+ ConnectorAccountPatch,
18
+ ConnectorAccountProvider,
19
+ IAgentRuntime,
20
+ } from "@elizaos/core";
21
+ import type { WechatConfig } from "./types";
22
+
23
+ export const WECHAT_PROVIDER_ID = "wechat";
24
+ export const WECHAT_DEFAULT_ACCOUNT_ID = "default";
25
+
26
+ function getWechatConfig(runtime: IAgentRuntime): WechatConfig | undefined {
27
+ const character = runtime.character?.settings as
28
+ | { connectors?: { wechat?: WechatConfig }; wechat?: WechatConfig }
29
+ | undefined;
30
+ return character?.connectors?.wechat ?? character?.wechat;
31
+ }
32
+
33
+ interface WechatResolvedAccount {
34
+ id: string;
35
+ enabled: boolean;
36
+ apiKeyConfigured: boolean;
37
+ proxyUrl?: string;
38
+ wcId?: string;
39
+ nickName?: string;
40
+ name?: string;
41
+ }
42
+
43
+ function listWechatAccounts(runtime: IAgentRuntime): WechatResolvedAccount[] {
44
+ const config = getWechatConfig(runtime);
45
+ const result: WechatResolvedAccount[] = [];
46
+
47
+ if (!config) {
48
+ // Single-account env-only fallback
49
+ const envApiKey = runtime.getSetting?.("WECHAT_API_KEY") as
50
+ | string
51
+ | undefined;
52
+ const envProxy = runtime.getSetting?.("WECHAT_PROXY_URL") as
53
+ | string
54
+ | undefined;
55
+ if (envApiKey?.trim() || envProxy?.trim()) {
56
+ result.push({
57
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
58
+ enabled: true,
59
+ apiKeyConfigured: Boolean(envApiKey?.trim()),
60
+ proxyUrl: envProxy?.trim() || undefined,
61
+ });
62
+ }
63
+ return result;
64
+ }
65
+
66
+ if (config.enabled === false) {
67
+ // Plugin disabled — still surface the account as `disabled`.
68
+ if (config.apiKey?.trim() || config.accounts) {
69
+ result.push({
70
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
71
+ enabled: false,
72
+ apiKeyConfigured: Boolean(config.apiKey?.trim()),
73
+ proxyUrl: config.proxyUrl,
74
+ });
75
+ }
76
+ return result;
77
+ }
78
+
79
+ if (config.apiKey?.trim()) {
80
+ result.push({
81
+ id: WECHAT_DEFAULT_ACCOUNT_ID,
82
+ enabled: true,
83
+ apiKeyConfigured: true,
84
+ proxyUrl: config.proxyUrl,
85
+ });
86
+ }
87
+
88
+ if (config.accounts && typeof config.accounts === "object") {
89
+ for (const [id, account] of Object.entries(config.accounts)) {
90
+ if (!id) continue;
91
+ result.push({
92
+ id: id.trim().toLowerCase(),
93
+ enabled: account.enabled !== false,
94
+ apiKeyConfigured: Boolean(account.apiKey?.trim()),
95
+ proxyUrl: account.proxyUrl,
96
+ wcId: account.wcId,
97
+ nickName: account.nickName,
98
+ name: account.name,
99
+ });
100
+ }
101
+ }
102
+
103
+ return result;
104
+ }
105
+
106
+ function toConnectorAccount(account: WechatResolvedAccount): ConnectorAccount {
107
+ const now = Date.now();
108
+ return {
109
+ id: account.id,
110
+ provider: WECHAT_PROVIDER_ID,
111
+ label: account.name ?? account.nickName ?? account.id,
112
+ role: "AGENT",
113
+ purpose: ["messaging"],
114
+ accessGate: "open",
115
+ status:
116
+ account.enabled && account.apiKeyConfigured ? "connected" : "disabled",
117
+ externalId: account.wcId || undefined,
118
+ displayHandle: account.nickName || undefined,
119
+ createdAt: now,
120
+ updatedAt: now,
121
+ metadata: {
122
+ proxyUrl: account.proxyUrl ?? "",
123
+ wcId: account.wcId ?? "",
124
+ nickName: account.nickName ?? "",
125
+ },
126
+ };
127
+ }
128
+
129
+ export function createWechatConnectorAccountProvider(
130
+ runtime: IAgentRuntime,
131
+ ): ConnectorAccountProvider {
132
+ return {
133
+ provider: WECHAT_PROVIDER_ID,
134
+ label: "WeChat",
135
+ listAccounts: async (
136
+ _manager: ConnectorAccountManager,
137
+ ): Promise<ConnectorAccount[]> => {
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
+ return accounts.map(toConnectorAccount);
151
+ },
152
+ createAccount: async (
153
+ input: ConnectorAccountPatch,
154
+ _manager: ConnectorAccountManager,
155
+ ) => {
156
+ return {
157
+ ...input,
158
+ provider: WECHAT_PROVIDER_ID,
159
+ role: input.role ?? "AGENT",
160
+ purpose: input.purpose ?? ["messaging"],
161
+ accessGate: input.accessGate ?? "open",
162
+ status: input.status ?? "pending",
163
+ };
164
+ },
165
+ patchAccount: async (
166
+ _accountId: string,
167
+ patch: ConnectorAccountPatch,
168
+ _manager: ConnectorAccountManager,
169
+ ) => {
170
+ return { ...patch, provider: WECHAT_PROVIDER_ID };
171
+ },
172
+ deleteAccount: async (
173
+ _accountId: string,
174
+ _manager: ConnectorAccountManager,
175
+ ) => {
176
+ // No-op at provider layer — runtime credentials live in character
177
+ // settings; deletion of those is out of band.
178
+ },
179
+ };
180
+ }