moltbot-wecom 1.0.2 → 2.0.0

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.
Files changed (40) hide show
  1. package/clawdbot.plugin.json +42 -0
  2. package/dist/index.d.ts +29 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +31 -0
  5. package/dist/src/accounts.d.ts +15 -0
  6. package/dist/src/accounts.d.ts.map +1 -0
  7. package/dist/src/accounts.js +90 -0
  8. package/dist/src/channel.d.ts +8 -0
  9. package/dist/src/channel.d.ts.map +1 -0
  10. package/dist/src/channel.js +365 -0
  11. package/dist/src/config-schema.d.ts +211 -0
  12. package/dist/src/config-schema.d.ts.map +1 -0
  13. package/dist/src/config-schema.js +21 -0
  14. package/dist/src/dedup.d.ts +8 -0
  15. package/dist/src/dedup.d.ts.map +1 -0
  16. package/dist/src/dedup.js +24 -0
  17. package/dist/src/group-filter.d.ts +15 -0
  18. package/dist/src/group-filter.d.ts.map +1 -0
  19. package/dist/src/group-filter.js +43 -0
  20. package/dist/src/probe.d.ts +10 -0
  21. package/dist/src/probe.d.ts.map +1 -0
  22. package/dist/src/probe.js +70 -0
  23. package/dist/src/receive.d.ts +26 -0
  24. package/dist/src/receive.d.ts.map +1 -0
  25. package/dist/src/receive.js +282 -0
  26. package/dist/src/runtime.d.ts +7 -0
  27. package/dist/src/runtime.d.ts.map +1 -0
  28. package/dist/src/runtime.js +13 -0
  29. package/dist/src/send.d.ts +26 -0
  30. package/dist/src/send.d.ts.map +1 -0
  31. package/dist/src/send.js +75 -0
  32. package/dist/src/status-issues.d.ts +7 -0
  33. package/dist/src/status-issues.d.ts.map +1 -0
  34. package/dist/src/status-issues.js +47 -0
  35. package/dist/src/types.d.ts +79 -0
  36. package/dist/src/types.d.ts.map +1 -0
  37. package/dist/src/types.js +4 -0
  38. package/package.json +28 -11
  39. package/moltbot.plugin.json +0 -27
  40. package/plugin.js +0 -243
@@ -0,0 +1,282 @@
1
+ /**
2
+ * WeCom WebSocket message receive handler.
3
+ *
4
+ * Connects to WeCom via a WebSocket proxy, dispatches incoming
5
+ * messages to Clawdbot's channel reply infrastructure.
6
+ */
7
+ import { isDuplicate } from "./dedup.js";
8
+ import { shouldRespondInGroup } from "./group-filter.js";
9
+ import { sendReplyMessage, sendPing, registerConnection, unregisterConnection } from "./send.js";
10
+ import { getWeComRuntime } from "./runtime.js";
11
+ /** Start the WeCom WebSocket provider. Returns a stop function. */
12
+ export function startWeComProvider(options) {
13
+ const { account, config, log, statusSink, abortSignal } = options;
14
+ const { proxyUrl, proxyToken, accountId } = account;
15
+ const thinkingThresholdMs = account.config.thinkingThresholdMs ?? 0;
16
+ const botNames = account.config.botNames;
17
+ const pingIntervalMs = account.config.pingIntervalMs ?? 30000;
18
+ const reconnectDelayMs = account.config.reconnectDelayMs ?? 5000;
19
+ log.info(`[wecom:${accountId}] Starting WebSocket provider (proxy=${proxyUrl})`);
20
+ const state = {
21
+ ws: null,
22
+ pingTimer: null,
23
+ reconnectTimer: null,
24
+ isAlive: false,
25
+ stopped: false,
26
+ };
27
+ // Handle abort signal
28
+ if (abortSignal) {
29
+ abortSignal.addEventListener("abort", () => {
30
+ state.stopped = true;
31
+ cleanup();
32
+ });
33
+ }
34
+ function cleanup() {
35
+ if (state.pingTimer) {
36
+ clearInterval(state.pingTimer);
37
+ state.pingTimer = null;
38
+ }
39
+ if (state.reconnectTimer) {
40
+ clearTimeout(state.reconnectTimer);
41
+ state.reconnectTimer = null;
42
+ }
43
+ if (state.ws) {
44
+ unregisterConnection(accountId);
45
+ try {
46
+ state.ws.terminate();
47
+ }
48
+ catch {
49
+ // Ignore
50
+ }
51
+ state.ws = null;
52
+ }
53
+ }
54
+ function startHeartbeat() {
55
+ if (state.pingTimer) {
56
+ clearInterval(state.pingTimer);
57
+ }
58
+ state.pingTimer = setInterval(() => {
59
+ if (!state.ws || state.ws.readyState !== 1 /* OPEN */) {
60
+ return;
61
+ }
62
+ if (!state.isAlive) {
63
+ log.warn?.(`[wecom:${accountId}] Heartbeat timeout, reconnecting...`);
64
+ state.ws.terminate();
65
+ return;
66
+ }
67
+ state.isAlive = false;
68
+ sendPing(state.ws);
69
+ }, pingIntervalMs);
70
+ }
71
+ function scheduleReconnect() {
72
+ if (state.stopped || state.reconnectTimer)
73
+ return;
74
+ state.reconnectTimer = setTimeout(() => {
75
+ state.reconnectTimer = null;
76
+ if (!state.stopped) {
77
+ connect();
78
+ }
79
+ }, reconnectDelayMs);
80
+ }
81
+ async function connect() {
82
+ if (state.stopped)
83
+ return;
84
+ try {
85
+ const { default: WebSocket } = await import("ws");
86
+ const url = proxyToken
87
+ ? `${proxyUrl}?token=${encodeURIComponent(proxyToken)}`
88
+ : proxyUrl;
89
+ log.info(`[wecom:${accountId}] Connecting to proxy: ${proxyUrl}`);
90
+ const ws = new WebSocket(url);
91
+ state.ws = ws;
92
+ ws.on("open", () => {
93
+ log.info(`[wecom:${accountId}] WebSocket connected`);
94
+ state.isAlive = true;
95
+ registerConnection(accountId, ws);
96
+ statusSink?.({ running: true, lastStartAt: Date.now() });
97
+ if (state.reconnectTimer) {
98
+ clearTimeout(state.reconnectTimer);
99
+ state.reconnectTimer = null;
100
+ }
101
+ startHeartbeat();
102
+ });
103
+ ws.on("message", async (data) => {
104
+ try {
105
+ const event = JSON.parse(data.toString());
106
+ await handleIncomingEvent(event, {
107
+ ws,
108
+ account,
109
+ config,
110
+ log,
111
+ thinkingThresholdMs,
112
+ botNames,
113
+ statusSink,
114
+ });
115
+ }
116
+ catch (err) {
117
+ log.error(`[wecom:${accountId}] Message handler error: ${err instanceof Error ? err.message : String(err)}`);
118
+ }
119
+ });
120
+ ws.on("close", () => {
121
+ log.warn?.(`[wecom:${accountId}] WebSocket disconnected`);
122
+ state.isAlive = false;
123
+ unregisterConnection(accountId);
124
+ statusSink?.({ running: false, lastStopAt: Date.now() });
125
+ if (state.pingTimer) {
126
+ clearInterval(state.pingTimer);
127
+ state.pingTimer = null;
128
+ }
129
+ scheduleReconnect();
130
+ });
131
+ ws.on("error", (err) => {
132
+ log.error(`[wecom:${accountId}] WebSocket error: ${err.message}`);
133
+ statusSink?.({ lastError: err.message });
134
+ ws.terminate();
135
+ });
136
+ }
137
+ catch (err) {
138
+ log.error(`[wecom:${accountId}] Connection failed: ${err instanceof Error ? err.message : String(err)}`);
139
+ scheduleReconnect();
140
+ }
141
+ }
142
+ // Start initial connection
143
+ connect();
144
+ const stop = () => {
145
+ log.info(`[wecom:${accountId}] Stopping WebSocket provider`);
146
+ state.stopped = true;
147
+ cleanup();
148
+ statusSink?.({ running: false, lastStopAt: Date.now() });
149
+ };
150
+ return { stop };
151
+ }
152
+ /** Handle a single incoming WeCom event. */
153
+ async function handleIncomingEvent(event, ctx) {
154
+ // Handle pong response
155
+ if (event.kind === "pong") {
156
+ return;
157
+ }
158
+ // Handle connection confirmation
159
+ if (event.kind === "connected") {
160
+ ctx.log.info(`[wecom:${ctx.account.accountId}] Connection confirmed by proxy`);
161
+ return;
162
+ }
163
+ // Only process message events
164
+ if (event.kind !== "message") {
165
+ return;
166
+ }
167
+ const payload = event.payload;
168
+ if (!payload)
169
+ return;
170
+ const msgId = event.msgId;
171
+ const senderId = payload.FromUserName;
172
+ const text = payload.Content?.trim();
173
+ // Basic validation
174
+ if (!senderId || !text) {
175
+ // Send empty reply for non-text messages
176
+ await sendReplyMessage(ctx.ws, msgId ?? "", "");
177
+ return;
178
+ }
179
+ // Dedup check
180
+ if (isDuplicate(msgId)) {
181
+ ctx.log.debug?.(`[wecom:${ctx.account.accountId}] Duplicate message ignored: ${msgId}`);
182
+ await sendReplyMessage(ctx.ws, msgId ?? "", "");
183
+ return;
184
+ }
185
+ ctx.statusSink?.({ lastInboundAt: Date.now() });
186
+ // WeCom Smart Bot messages are typically direct; group logic kept for future use
187
+ const isGroup = false;
188
+ if (isGroup && !shouldRespondInGroup(text, [], ctx.botNames)) {
189
+ ctx.log.debug?.(`[wecom:${ctx.account.accountId}] Ignoring group message (no trigger)`);
190
+ await sendReplyMessage(ctx.ws, msgId ?? "", "");
191
+ return;
192
+ }
193
+ const sessionKey = `wecom:${senderId}`;
194
+ ctx.log.info(`[wecom:${ctx.account.accountId}] Rx [${senderId}]: ${text.slice(0, 80)}`);
195
+ // Dispatch via Clawdbot runtime
196
+ const runtime = getWeComRuntime();
197
+ const channel = runtime.channel;
198
+ if (!channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) {
199
+ ctx.log.error(`[wecom:${ctx.account.accountId}] dispatchReplyWithBufferedBlockDispatcher not available`);
200
+ await sendReplyMessage(ctx.ws, msgId ?? "", "");
201
+ return;
202
+ }
203
+ // Build inbound context
204
+ const inboundCtx = {
205
+ Body: text,
206
+ RawBody: text,
207
+ CommandBody: text,
208
+ From: senderId,
209
+ To: payload.ToUserName ?? "",
210
+ SessionKey: sessionKey,
211
+ AccountId: ctx.account.accountId,
212
+ MessageSid: msgId,
213
+ ChatType: "direct",
214
+ ConversationLabel: senderId,
215
+ SenderId: senderId,
216
+ CommandAuthorized: true,
217
+ Provider: "wecom",
218
+ Surface: "wecom",
219
+ OriginatingChannel: "wecom",
220
+ OriginatingTo: senderId,
221
+ DeliveryContext: {
222
+ channel: "wecom",
223
+ to: senderId,
224
+ accountId: ctx.account.accountId,
225
+ },
226
+ };
227
+ // Timeout for passive reply (WeCom has strict 5s limit)
228
+ const REPLY_TIMEOUT_MS = 4500;
229
+ let replied = false;
230
+ // Promise to handle the reply
231
+ const replyPromise = new Promise((resolve) => {
232
+ const timeout = setTimeout(() => {
233
+ if (!replied) {
234
+ replied = true;
235
+ ctx.log.warn?.(`[wecom:${ctx.account.accountId}] Reply timeout, sending empty`);
236
+ sendReplyMessage(ctx.ws, msgId ?? "", "").finally(resolve);
237
+ }
238
+ }, REPLY_TIMEOUT_MS);
239
+ // Create reply handler
240
+ const replyHandler = async (replyText) => {
241
+ if (replied)
242
+ return;
243
+ replied = true;
244
+ clearTimeout(timeout);
245
+ if (!replyText || replyText === "NO_REPLY") {
246
+ await sendReplyMessage(ctx.ws, msgId ?? "", "");
247
+ }
248
+ else {
249
+ ctx.log.info(`[wecom:${ctx.account.accountId}] Tx [${senderId}]: ${replyText.slice(0, 50)}...`);
250
+ ctx.statusSink?.({ lastOutboundAt: Date.now() });
251
+ await sendReplyMessage(ctx.ws, msgId ?? "", replyText);
252
+ }
253
+ resolve();
254
+ };
255
+ // Dispatch to Clawdbot pipeline
256
+ channel.reply?.dispatchReplyWithBufferedBlockDispatcher?.({
257
+ ctx: inboundCtx,
258
+ replyFn: replyHandler,
259
+ blockHandler: {
260
+ onBlock: async (block) => {
261
+ // For buffered responses, we collect and send at end
262
+ if (block.text && !replied) {
263
+ await replyHandler(block.text);
264
+ }
265
+ },
266
+ onComplete: async () => {
267
+ if (!replied) {
268
+ await replyHandler("");
269
+ }
270
+ },
271
+ },
272
+ }).catch((err) => {
273
+ ctx.log.error(`[wecom:${ctx.account.accountId}] Pipeline error: ${err.message}`);
274
+ if (!replied) {
275
+ replied = true;
276
+ clearTimeout(timeout);
277
+ sendReplyMessage(ctx.ws, msgId ?? "", "").finally(resolve);
278
+ }
279
+ });
280
+ });
281
+ await replyPromise;
282
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Global runtime reference for the WeCom plugin.
3
+ */
4
+ import type { PluginRuntime } from "clawdbot/plugin-sdk";
5
+ export declare function setWeComRuntime(next: PluginRuntime): void;
6
+ export declare function getWeComRuntime(): PluginRuntime;
7
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAEzD;AAED,wBAAgB,eAAe,IAAI,aAAa,CAK/C"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Global runtime reference for the WeCom plugin.
3
+ */
4
+ let runtime = null;
5
+ export function setWeComRuntime(next) {
6
+ runtime = next;
7
+ }
8
+ export function getWeComRuntime() {
9
+ if (!runtime) {
10
+ throw new Error("WeCom runtime not initialized");
11
+ }
12
+ return runtime;
13
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Send messages to WeCom via WebSocket proxy.
3
+ */
4
+ import type { WeComSendResult } from "./types.js";
5
+ import type WebSocket from "ws";
6
+ /** Register an active WebSocket connection for an account. */
7
+ export declare function registerConnection(accountId: string, ws: WebSocket): void;
8
+ /** Unregister a WebSocket connection for an account. */
9
+ export declare function unregisterConnection(accountId: string): void;
10
+ /** Get active WebSocket connection for an account. */
11
+ export declare function getConnection(accountId: string): WebSocket | undefined;
12
+ /**
13
+ * Send a reply to a specific message via the WeCom proxy.
14
+ */
15
+ export declare function sendReplyMessage(ws: WebSocket | undefined, msgId: string, text: string): Promise<WeComSendResult>;
16
+ /**
17
+ * Send a text message to a WeCom chat.
18
+ * Note: For passive reply mode, messages go through sendReplyMessage.
19
+ * This is a placeholder for potential active messaging support.
20
+ */
21
+ export declare function sendTextMessage(accountId: string, chatId: string, text: string): Promise<WeComSendResult>;
22
+ /**
23
+ * Send a ping to keep the connection alive.
24
+ */
25
+ export declare function sendPing(ws: WebSocket | undefined): boolean;
26
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/send.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAKhC,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,IAAI,CAEzE;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEtE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,SAAS,GAAG,SAAS,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CAwB1B;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,CAAC,CAc1B;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAW3D"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Send messages to WeCom via WebSocket proxy.
3
+ */
4
+ /** Active WebSocket connections by account. */
5
+ const activeConnections = new Map();
6
+ /** Register an active WebSocket connection for an account. */
7
+ export function registerConnection(accountId, ws) {
8
+ activeConnections.set(accountId, ws);
9
+ }
10
+ /** Unregister a WebSocket connection for an account. */
11
+ export function unregisterConnection(accountId) {
12
+ activeConnections.delete(accountId);
13
+ }
14
+ /** Get active WebSocket connection for an account. */
15
+ export function getConnection(accountId) {
16
+ return activeConnections.get(accountId);
17
+ }
18
+ /**
19
+ * Send a reply to a specific message via the WeCom proxy.
20
+ */
21
+ export async function sendReplyMessage(ws, msgId, text) {
22
+ if (!ws || ws.readyState !== 1 /* OPEN */) {
23
+ return { ok: false, error: "WebSocket not connected" };
24
+ }
25
+ if (!msgId) {
26
+ return { ok: false, error: "No message ID provided" };
27
+ }
28
+ try {
29
+ ws.send(JSON.stringify({
30
+ kind: "reply",
31
+ msgId,
32
+ text: text ?? "",
33
+ }));
34
+ return { ok: true, messageId: msgId };
35
+ }
36
+ catch (err) {
37
+ return {
38
+ ok: false,
39
+ error: err instanceof Error ? err.message : String(err),
40
+ };
41
+ }
42
+ }
43
+ /**
44
+ * Send a text message to a WeCom chat.
45
+ * Note: For passive reply mode, messages go through sendReplyMessage.
46
+ * This is a placeholder for potential active messaging support.
47
+ */
48
+ export async function sendTextMessage(accountId, chatId, text) {
49
+ const ws = activeConnections.get(accountId);
50
+ if (!ws || ws.readyState !== 1 /* OPEN */) {
51
+ return { ok: false, error: "WebSocket not connected" };
52
+ }
53
+ // WeCom Smart Bot uses passive reply mode; active messaging
54
+ // would require calling WeCom APIs directly (future enhancement).
55
+ // For now, return an error indicating this limitation.
56
+ return {
57
+ ok: false,
58
+ error: "Active messaging not supported in proxy mode. Use reply flow.",
59
+ };
60
+ }
61
+ /**
62
+ * Send a ping to keep the connection alive.
63
+ */
64
+ export function sendPing(ws) {
65
+ if (!ws || ws.readyState !== 1 /* OPEN */) {
66
+ return false;
67
+ }
68
+ try {
69
+ ws.send(JSON.stringify({ kind: "ping", ts: Date.now() }));
70
+ return true;
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Diagnostic issues collector for WeCom channel.
3
+ */
4
+ import type { ChannelAccountSnapshot, ChannelStatusIssue } from "clawdbot/plugin-sdk";
5
+ /** Collect configuration issues for all WeCom accounts. */
6
+ export declare function collectWeComStatusIssues(accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[];
7
+ //# sourceMappingURL=status-issues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-issues.d.ts","sourceRoot":"","sources":["../../src/status-issues.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA2BtF,2DAA2D;AAC3D,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,sBAAsB,EAAE,GACjC,kBAAkB,EAAE,CAiCtB"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Diagnostic issues collector for WeCom channel.
3
+ */
4
+ const isRecord = (value) => Boolean(value && typeof value === "object");
5
+ const asString = (value) => typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
6
+ function readWeComAccountStatus(value) {
7
+ if (!isRecord(value))
8
+ return null;
9
+ return {
10
+ accountId: value.accountId,
11
+ enabled: value.enabled,
12
+ configured: value.configured,
13
+ dmPolicy: value.dmPolicy,
14
+ proxyUrl: value.proxyUrl,
15
+ };
16
+ }
17
+ /** Collect configuration issues for all WeCom accounts. */
18
+ export function collectWeComStatusIssues(accounts) {
19
+ const issues = [];
20
+ for (const entry of accounts) {
21
+ const account = readWeComAccountStatus(entry);
22
+ if (!account)
23
+ continue;
24
+ const accountId = asString(account.accountId) ?? "default";
25
+ const enabled = account.enabled !== false;
26
+ const configured = account.configured === true;
27
+ if (enabled && !configured) {
28
+ issues.push({
29
+ channel: "wecom",
30
+ accountId,
31
+ kind: "config",
32
+ message: "WeCom account is enabled but not configured (missing proxyUrl).",
33
+ fix: "Set channels.wecom.proxyUrl in clawdbot.json or set WECOM_PROXY_URL environment variable.",
34
+ });
35
+ }
36
+ if (enabled && configured && account.dmPolicy === "open") {
37
+ issues.push({
38
+ channel: "wecom",
39
+ accountId,
40
+ kind: "config",
41
+ message: 'WeCom dmPolicy is "open", allowing any user to message the bot without pairing.',
42
+ fix: 'Set channels.wecom.dmPolicy to "pairing" or "allowlist" to restrict access.',
43
+ });
44
+ }
45
+ }
46
+ return issues;
47
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * WeCom channel plugin — type definitions.
3
+ */
4
+ /** Per-account configuration stored in clawdbot.json channels.wecom */
5
+ export type WeComAccountConfig = {
6
+ /** Optional display name for this account. */
7
+ name?: string;
8
+ /** If false, do not start this WeCom account. Default: true. */
9
+ enabled?: boolean;
10
+ /** WebSocket proxy URL for WeCom Smart Bot. */
11
+ proxyUrl?: string;
12
+ /** Token for authenticating with the WebSocket proxy. */
13
+ proxyToken?: string;
14
+ /** Direct message access policy (default: pairing). */
15
+ dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
16
+ /** Allowlist for DM senders (WeCom user IDs). */
17
+ allowFrom?: Array<string | number>;
18
+ /** "Thinking…" placeholder threshold in milliseconds. 0 to disable. */
19
+ thinkingThresholdMs?: number;
20
+ /** Bot name aliases used for group-chat address detection. */
21
+ botNames?: string[];
22
+ /** Heartbeat ping interval in milliseconds. Default: 30000. */
23
+ pingIntervalMs?: number;
24
+ /** Reconnect delay in milliseconds. Default: 5000. */
25
+ reconnectDelayMs?: number;
26
+ };
27
+ /** Top-level WeCom config section (channels.wecom). */
28
+ export type WeComConfig = {
29
+ /** Multi-account map. */
30
+ accounts?: Record<string, WeComAccountConfig>;
31
+ /** Default account ID when multiple accounts exist. */
32
+ defaultAccount?: string;
33
+ } & WeComAccountConfig;
34
+ /** How the credentials were resolved. */
35
+ export type WeComTokenSource = "config" | "plugin" | "env" | "none";
36
+ /** Resolved account ready for use. */
37
+ export type ResolvedWeComAccount = {
38
+ accountId: string;
39
+ name?: string;
40
+ enabled: boolean;
41
+ proxyUrl: string;
42
+ proxyToken: string;
43
+ tokenSource: WeComTokenSource;
44
+ config: WeComAccountConfig;
45
+ };
46
+ /** Result of sending a message via WeCom proxy. */
47
+ export type WeComSendResult = {
48
+ ok: boolean;
49
+ messageId?: string;
50
+ error?: string;
51
+ };
52
+ /** WeCom probe result. */
53
+ export type WeComProbeResult = {
54
+ ok: boolean;
55
+ error?: string;
56
+ elapsedMs: number;
57
+ };
58
+ /** Incoming message from WeCom proxy. */
59
+ export type WeComIncomingMessage = {
60
+ kind: "message" | "pong" | "connected" | "error";
61
+ msgId?: string;
62
+ payload?: {
63
+ FromUserName?: string;
64
+ ToUserName?: string;
65
+ Content?: string;
66
+ MsgType?: string;
67
+ CreateTime?: number;
68
+ };
69
+ ts?: number;
70
+ error?: string;
71
+ };
72
+ /** Outgoing reply to WeCom proxy. */
73
+ export type WeComOutgoingReply = {
74
+ kind: "reply" | "ping";
75
+ msgId?: string;
76
+ text?: string;
77
+ ts?: number;
78
+ };
79
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uEAAuE;AACvE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;IACzD,iDAAiD;IACjD,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACnC,uEAAuE;IACvE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG;IACxB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC9C,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,kBAAkB,CAAC;AAEvB,yCAAyC;AACzC,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpE,sCAAsC;AACtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,gBAAgB,CAAC;IAC9B,MAAM,EAAE,kBAAkB,CAAC;CAC5B,CAAC;AAEF,mDAAmD;AACnD,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,0BAA0B;AAC1B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,yCAAyC;AACzC,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QACR,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * WeCom channel plugin — type definitions.
3
+ */
4
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moltbot-wecom",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "企业微信机器人插件 - 让 AI 助手接入企业微信智能机器人 | WeCom Smart Bot channel plugin for Moltbot",
6
6
  "author": "xiajingan",
@@ -20,21 +20,30 @@
20
20
  "websocket",
21
21
  "smart-bot"
22
22
  ],
23
- "main": "plugin.js",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
24
25
  "exports": {
25
26
  ".": {
26
- "import": "./plugin.js"
27
+ "import": "./dist/index.js",
28
+ "types": "./dist/index.d.ts"
27
29
  }
28
30
  },
29
31
  "files": [
30
- "plugin.js",
31
- "moltbot.plugin.json",
32
+ "dist",
33
+ "clawdbot.plugin.json",
32
34
  "README.md",
33
35
  "LICENSE"
34
36
  ],
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "clean": "rm -rf dist",
40
+ "prepublishOnly": "npm run clean && npm run build",
41
+ "typecheck": "tsc --noEmit",
42
+ "dev": "tsc --watch"
43
+ },
35
44
  "moltbot": {
36
45
  "extensions": [
37
- "./plugin.js"
46
+ "./dist/index.js"
38
47
  ],
39
48
  "channel": {
40
49
  "id": "wecom",
@@ -45,9 +54,11 @@
45
54
  "blurb": "WeCom (Enterprise WeChat) Smart Bot with WebSocket proxy.",
46
55
  "aliases": [
47
56
  "企微",
48
- "qw"
57
+ "qw",
58
+ "wechat-work",
59
+ "qywx"
49
60
  ],
50
- "order": 80,
61
+ "order": 86,
51
62
  "quickstartAllowFrom": true
52
63
  },
53
64
  "install": {
@@ -56,13 +67,19 @@
56
67
  }
57
68
  },
58
69
  "dependencies": {
59
- "ws": "^8.16.0"
70
+ "ws": "^8.16.0",
71
+ "zod": "^3.22.0"
72
+ },
73
+ "devDependencies": {
74
+ "@types/node": "^20.0.0",
75
+ "@types/ws": "^8.5.10",
76
+ "typescript": "^5.3.0"
60
77
  },
61
78
  "peerDependencies": {
62
- "moltbot": ">=1.0.0"
79
+ "clawdbot": ">=1.0.0"
63
80
  },
64
81
  "peerDependenciesMeta": {
65
- "moltbot": {
82
+ "clawdbot": {
66
83
  "optional": false
67
84
  }
68
85
  },
@@ -1,27 +0,0 @@
1
- {
2
- "id": "wecom",
3
- "name": "WeCom",
4
- "description": "WeCom (企业微信) channel plugin — WebSocket proxy connection for Smart Bot",
5
- "version": "1.0.1",
6
- "channels": ["wecom"],
7
- "configSchema": {
8
- "type": "object",
9
- "properties": {
10
- "proxyUrl": {
11
- "type": "string",
12
- "description": "WeCom Proxy WebSocket URL (wss://your-domain.com)"
13
- },
14
- "proxyToken": {
15
- "type": "string",
16
- "description": "Authentication token for wecom-proxy connection"
17
- },
18
- "pingInterval": {
19
- "type": "number",
20
- "default": 30000,
21
- "description": "WebSocket heartbeat interval in milliseconds"
22
- }
23
- },
24
- "required": ["proxyUrl", "proxyToken"],
25
- "additionalProperties": true
26
- }
27
- }