@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 +21 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +259 -12
- package/package.json +16 -7
- package/src/channel.ts +13 -0
- package/src/connector-account-provider.ts +180 -0
- package/src/index.test.ts +89 -0
- package/src/index.ts +332 -2
- package/dist/bot.d.ts +0 -25
- package/dist/bot.js +0 -49
- package/dist/callback-server.js +0 -207
- package/dist/channel.d.ts +0 -28
- package/dist/channel.js +0 -194
- package/dist/proxy-client.d.ts +0 -35
- package/dist/proxy-client.js +0 -117
- package/dist/reply-dispatcher.d.ts +0 -17
- package/dist/reply-dispatcher.js +0 -47
- package/dist/runtime-bridge.d.ts +0 -12
- package/dist/runtime-bridge.js +0 -159
- package/dist/types.d.ts +0 -61
- package/dist/utils/qrcode.js +0 -20
- package/src/callback-server.test.ts +0 -190
- package/src/channel.test.ts +0 -121
- package/src/proxy-client-429.test.ts +0 -24
- package/src/proxy-client.test.ts +0 -46
- package/src/runtime-bridge.test.ts +0 -135
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
|
-
|
|
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-
|
|
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
|
|
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": "
|
|
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": "
|
|
42
|
-
"tsdown": "^0.21.
|
|
43
|
-
"typescript": "^6.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
|
+
}
|