@yanhaidao/wecom 2.3.160 → 2.3.180
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -399
- package/SKILLS_CAL.md +895 -0
- package/SKILLS_DOC.md +2136 -0
- package/changelog/v2.3.18.md +22 -0
- package/index.ts +39 -3
- package/package.json +2 -3
- package/src/agent/handler.event-filter.test.ts +11 -0
- package/src/agent/handler.ts +732 -643
- package/src/app/account-runtime.ts +46 -20
- package/src/app/index.ts +19 -1
- package/src/capability/calendar/SKILLS_CHECKLIST.md +251 -0
- package/src/capability/calendar/client.ts +815 -0
- package/src/capability/calendar/index.ts +3 -0
- package/src/capability/calendar/schema.ts +417 -0
- package/src/capability/calendar/tool.ts +417 -0
- package/src/capability/calendar/types.ts +309 -0
- package/src/capability/doc/client.ts +567 -62
- package/src/capability/doc/schema.ts +419 -318
- package/src/capability/doc/tool.ts +1510 -1178
- package/src/capability/doc/types.ts +130 -14
- package/src/capability/mcp/index.ts +10 -0
- package/src/capability/mcp/schema.ts +107 -0
- package/src/capability/mcp/tool.ts +170 -0
- package/src/capability/mcp/transport.ts +394 -0
- package/src/channel.ts +70 -28
- package/src/config/schema.ts +71 -102
- package/src/outbound.test.ts +91 -14
- package/src/outbound.ts +143 -30
- package/src/runtime/reply-orchestrator.test.ts +35 -2
- package/src/runtime/reply-orchestrator.ts +14 -2
- package/src/runtime/session-manager.ts +20 -6
- package/src/runtime/source-registry.ts +165 -0
- package/src/transport/bot-ws/media.ts +269 -0
- package/src/transport/bot-ws/reply.test.ts +85 -17
- package/src/transport/bot-ws/reply.ts +109 -21
- package/src/transport/bot-ws/sdk-adapter.test.ts +64 -1
- package/src/transport/bot-ws/sdk-adapter.ts +88 -12
- package/.claude/settings.local.json +0 -11
- package/docs/update-content-fix.md +0 -135
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { generateReqId } from "@wecom/aibot-node-sdk";
|
|
2
|
+
import { getBotWsPushHandle } from "../../runtime.js";
|
|
3
|
+
|
|
4
|
+
const HTTP_REQUEST_TIMEOUT_MS = 30_000;
|
|
5
|
+
const MCP_CONFIG_FETCH_TIMEOUT_MS = 15_000;
|
|
6
|
+
const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
|
|
7
|
+
const MCP_PLUGIN_VERSION = "wecom-dual-plane";
|
|
8
|
+
const LOG_TAG = "[wecom-mcp]";
|
|
9
|
+
|
|
10
|
+
interface JsonRpcRequest {
|
|
11
|
+
jsonrpc: "2.0";
|
|
12
|
+
id?: string;
|
|
13
|
+
method: string;
|
|
14
|
+
params?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface JsonRpcResponse {
|
|
18
|
+
jsonrpc: "2.0";
|
|
19
|
+
id: number | string;
|
|
20
|
+
result?: unknown;
|
|
21
|
+
error?: {
|
|
22
|
+
code: number;
|
|
23
|
+
message: string;
|
|
24
|
+
data?: unknown;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface McpSession {
|
|
29
|
+
sessionId: string | null;
|
|
30
|
+
initialized: boolean;
|
|
31
|
+
stateless: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const CACHE_CLEAR_ERROR_CODES = new Set([-32001, -32002, -32003]);
|
|
35
|
+
|
|
36
|
+
const mcpConfigCache = new Map<string, Record<string, unknown>>();
|
|
37
|
+
const mcpSessionCache = new Map<string, McpSession>();
|
|
38
|
+
const statelessKeys = new Set<string>();
|
|
39
|
+
const inflightInitRequests = new Map<string, Promise<McpSession>>();
|
|
40
|
+
|
|
41
|
+
function cacheKey(accountId: string, category: string): string {
|
|
42
|
+
return `${accountId}::${category}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
46
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
47
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
48
|
+
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
49
|
+
});
|
|
50
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
51
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class McpRpcError extends Error {
|
|
56
|
+
constructor(
|
|
57
|
+
public readonly code: number,
|
|
58
|
+
message: string,
|
|
59
|
+
public readonly data?: unknown,
|
|
60
|
+
) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.name = "McpRpcError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class McpHttpError extends Error {
|
|
67
|
+
constructor(
|
|
68
|
+
public readonly statusCode: number,
|
|
69
|
+
message: string,
|
|
70
|
+
) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = "McpHttpError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function fetchMcpConfig(
|
|
77
|
+
accountId: string,
|
|
78
|
+
category: string,
|
|
79
|
+
): Promise<Record<string, unknown>> {
|
|
80
|
+
const handle = getBotWsPushHandle(accountId);
|
|
81
|
+
if (!handle?.isConnected()) {
|
|
82
|
+
throw new Error(`当前企微账号 MCP 服务未就绪:account=${accountId} 的 Bot WS 未连接。`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const response = await withTimeout(
|
|
86
|
+
handle.replyCommand({
|
|
87
|
+
cmd: MCP_GET_CONFIG_CMD,
|
|
88
|
+
body: {
|
|
89
|
+
biz_type: category,
|
|
90
|
+
plugin_version: MCP_PLUGIN_VERSION,
|
|
91
|
+
},
|
|
92
|
+
headers: {
|
|
93
|
+
req_id: generateReqId("mcp_config"),
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
MCP_CONFIG_FETCH_TIMEOUT_MS,
|
|
97
|
+
`MCP config fetch timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const errcode = Number((response as { errcode?: number }).errcode ?? 0);
|
|
101
|
+
if (errcode !== 0) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`MCP 配置请求失败: errcode=${String((response as { errcode?: number }).errcode)} errmsg=${String((response as { errmsg?: string }).errmsg ?? "unknown")}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const body = (response as { body?: { url?: string } }).body;
|
|
108
|
+
if (!body?.url) {
|
|
109
|
+
throw new Error(`MCP 配置响应缺少 url 字段 (account=${accountId}, category=${category})`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`${LOG_TAG} config ready account=${accountId} category=${category} url=${body.url}`);
|
|
113
|
+
return body as Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function getMcpUrl(accountId: string, category: string): Promise<string> {
|
|
117
|
+
const key = cacheKey(accountId, category);
|
|
118
|
+
const cached = mcpConfigCache.get(key);
|
|
119
|
+
if (cached?.url) {
|
|
120
|
+
return String(cached.url);
|
|
121
|
+
}
|
|
122
|
+
const body = await fetchMcpConfig(accountId, category);
|
|
123
|
+
mcpConfigCache.set(key, body);
|
|
124
|
+
return String(body.url);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function sendRawJsonRpc(
|
|
128
|
+
url: string,
|
|
129
|
+
session: McpSession,
|
|
130
|
+
body: JsonRpcRequest,
|
|
131
|
+
): Promise<{ rpcResult: unknown; newSessionId: string | null }> {
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const timeoutId = setTimeout(() => controller.abort(), HTTP_REQUEST_TIMEOUT_MS);
|
|
134
|
+
try {
|
|
135
|
+
const headers: Record<string, string> = {
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
Accept: "application/json, text/event-stream",
|
|
138
|
+
};
|
|
139
|
+
if (session.sessionId) {
|
|
140
|
+
headers["Mcp-Session-Id"] = session.sessionId;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const response = await fetch(url, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers,
|
|
146
|
+
body: JSON.stringify(body),
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
});
|
|
149
|
+
const newSessionId = response.headers.get("mcp-session-id");
|
|
150
|
+
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new McpHttpError(
|
|
153
|
+
response.status,
|
|
154
|
+
`MCP HTTP 请求失败: ${response.status} ${response.statusText}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const contentLength = response.headers.get("content-length");
|
|
159
|
+
if (response.status === 204 || contentLength === "0") {
|
|
160
|
+
return { rpcResult: undefined, newSessionId };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
164
|
+
if (contentType.includes("text/event-stream")) {
|
|
165
|
+
return {
|
|
166
|
+
rpcResult: await parseSseResponse(response),
|
|
167
|
+
newSessionId,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const text = await response.text();
|
|
172
|
+
if (!text.trim()) {
|
|
173
|
+
return { rpcResult: undefined, newSessionId };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const rpc = JSON.parse(text) as JsonRpcResponse;
|
|
177
|
+
if (rpc.error) {
|
|
178
|
+
throw new McpRpcError(
|
|
179
|
+
rpc.error.code,
|
|
180
|
+
`MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
|
|
181
|
+
rpc.error.data,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return { rpcResult: rpc.result, newSessionId };
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
187
|
+
throw new Error(`MCP 请求超时 (${HTTP_REQUEST_TIMEOUT_MS}ms)`);
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
} finally {
|
|
191
|
+
clearTimeout(timeoutId);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function initializeSession(
|
|
196
|
+
accountId: string,
|
|
197
|
+
category: string,
|
|
198
|
+
url: string,
|
|
199
|
+
): Promise<McpSession> {
|
|
200
|
+
const key = cacheKey(accountId, category);
|
|
201
|
+
const session: McpSession = { sessionId: null, initialized: false, stateless: false };
|
|
202
|
+
|
|
203
|
+
const initializeRequest: JsonRpcRequest = {
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
id: generateReqId("mcp_init"),
|
|
206
|
+
method: "initialize",
|
|
207
|
+
params: {
|
|
208
|
+
protocolVersion: "2025-03-26",
|
|
209
|
+
capabilities: {},
|
|
210
|
+
clientInfo: { name: "wecom_mcp", version: MCP_PLUGIN_VERSION },
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const initResult = await sendRawJsonRpc(url, session, initializeRequest);
|
|
215
|
+
if (initResult.newSessionId) {
|
|
216
|
+
session.sessionId = initResult.newSessionId;
|
|
217
|
+
}
|
|
218
|
+
if (!session.sessionId) {
|
|
219
|
+
session.stateless = true;
|
|
220
|
+
session.initialized = true;
|
|
221
|
+
statelessKeys.add(key);
|
|
222
|
+
mcpSessionCache.set(key, session);
|
|
223
|
+
return session;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const notifyRequest: JsonRpcRequest = {
|
|
227
|
+
jsonrpc: "2.0",
|
|
228
|
+
method: "notifications/initialized",
|
|
229
|
+
};
|
|
230
|
+
const notifyResult = await sendRawJsonRpc(url, session, notifyRequest);
|
|
231
|
+
if (notifyResult.newSessionId) {
|
|
232
|
+
session.sessionId = notifyResult.newSessionId;
|
|
233
|
+
}
|
|
234
|
+
session.initialized = true;
|
|
235
|
+
mcpSessionCache.set(key, session);
|
|
236
|
+
return session;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function getOrCreateSession(
|
|
240
|
+
accountId: string,
|
|
241
|
+
category: string,
|
|
242
|
+
url: string,
|
|
243
|
+
): Promise<McpSession> {
|
|
244
|
+
const key = cacheKey(accountId, category);
|
|
245
|
+
if (statelessKeys.has(key)) {
|
|
246
|
+
const cached = mcpSessionCache.get(key);
|
|
247
|
+
if (cached) return cached;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const cached = mcpSessionCache.get(key);
|
|
251
|
+
if (cached?.initialized) {
|
|
252
|
+
return cached;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const inflight = inflightInitRequests.get(key);
|
|
256
|
+
if (inflight) {
|
|
257
|
+
return inflight;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const promise = initializeSession(accountId, category, url).finally(() => {
|
|
261
|
+
inflightInitRequests.delete(key);
|
|
262
|
+
});
|
|
263
|
+
inflightInitRequests.set(key, promise);
|
|
264
|
+
return promise;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function rebuildSession(
|
|
268
|
+
accountId: string,
|
|
269
|
+
category: string,
|
|
270
|
+
url: string,
|
|
271
|
+
): Promise<McpSession> {
|
|
272
|
+
const key = cacheKey(accountId, category);
|
|
273
|
+
const inflight = inflightInitRequests.get(key);
|
|
274
|
+
if (inflight) return inflight;
|
|
275
|
+
const promise = initializeSession(accountId, category, url).finally(() => {
|
|
276
|
+
inflightInitRequests.delete(key);
|
|
277
|
+
});
|
|
278
|
+
inflightInitRequests.set(key, promise);
|
|
279
|
+
return promise;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function parseSseResponse(response: Response): Promise<unknown> {
|
|
283
|
+
const text = await response.text();
|
|
284
|
+
const lines = text.split("\n");
|
|
285
|
+
let currentParts: string[] = [];
|
|
286
|
+
let lastEventData = "";
|
|
287
|
+
|
|
288
|
+
for (const line of lines) {
|
|
289
|
+
if (line.startsWith("data: ")) {
|
|
290
|
+
currentParts.push(line.slice(6));
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (line.startsWith("data:")) {
|
|
294
|
+
currentParts.push(line.slice(5));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (line.trim() === "" && currentParts.length > 0) {
|
|
298
|
+
lastEventData = currentParts.join("\n").trim();
|
|
299
|
+
currentParts = [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (currentParts.length > 0) {
|
|
303
|
+
lastEventData = currentParts.join("\n").trim();
|
|
304
|
+
}
|
|
305
|
+
if (!lastEventData) {
|
|
306
|
+
throw new Error("SSE 响应中未包含有效数据");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const rpc = JSON.parse(lastEventData) as JsonRpcResponse;
|
|
310
|
+
if (rpc.error) {
|
|
311
|
+
throw new McpRpcError(
|
|
312
|
+
rpc.error.code,
|
|
313
|
+
`MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
|
|
314
|
+
rpc.error.data,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return rpc.result;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function clearWecomMcpCategoryCache(accountId: string, category: string): void {
|
|
321
|
+
const key = cacheKey(accountId, category);
|
|
322
|
+
console.log(`${LOG_TAG} clear cache account=${accountId} category=${category}`);
|
|
323
|
+
mcpConfigCache.delete(key);
|
|
324
|
+
mcpSessionCache.delete(key);
|
|
325
|
+
statelessKeys.delete(key);
|
|
326
|
+
inflightInitRequests.delete(key);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function clearWecomMcpAccountCache(accountId: string): void {
|
|
330
|
+
const prefix = `${accountId}::`;
|
|
331
|
+
for (const key of [...mcpConfigCache.keys()]) {
|
|
332
|
+
if (key.startsWith(prefix)) mcpConfigCache.delete(key);
|
|
333
|
+
}
|
|
334
|
+
for (const key of [...mcpSessionCache.keys()]) {
|
|
335
|
+
if (key.startsWith(prefix)) mcpSessionCache.delete(key);
|
|
336
|
+
}
|
|
337
|
+
for (const key of [...statelessKeys]) {
|
|
338
|
+
if (key.startsWith(prefix)) statelessKeys.delete(key);
|
|
339
|
+
}
|
|
340
|
+
for (const key of [...inflightInitRequests.keys()]) {
|
|
341
|
+
if (key.startsWith(prefix)) inflightInitRequests.delete(key);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface McpToolInfo {
|
|
346
|
+
name: string;
|
|
347
|
+
description?: string;
|
|
348
|
+
inputSchema?: Record<string, unknown>;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export async function sendJsonRpc(
|
|
352
|
+
accountId: string,
|
|
353
|
+
category: string,
|
|
354
|
+
method: string,
|
|
355
|
+
params?: Record<string, unknown>,
|
|
356
|
+
): Promise<unknown> {
|
|
357
|
+
const url = await getMcpUrl(accountId, category);
|
|
358
|
+
const body: JsonRpcRequest = {
|
|
359
|
+
jsonrpc: "2.0",
|
|
360
|
+
id: generateReqId("mcp_rpc"),
|
|
361
|
+
method,
|
|
362
|
+
...(params !== undefined ? { params } : {}),
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
let session = await getOrCreateSession(accountId, category, url);
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const result = await sendRawJsonRpc(url, session, body);
|
|
369
|
+
if (result.newSessionId) {
|
|
370
|
+
session.sessionId = result.newSessionId;
|
|
371
|
+
}
|
|
372
|
+
return result.rpcResult;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error instanceof McpRpcError && CACHE_CLEAR_ERROR_CODES.has(error.code)) {
|
|
375
|
+
clearWecomMcpCategoryCache(accountId, category);
|
|
376
|
+
}
|
|
377
|
+
if (session.stateless) {
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
if (error instanceof McpHttpError && error.statusCode === 404) {
|
|
381
|
+
mcpSessionCache.delete(cacheKey(accountId, category));
|
|
382
|
+
session = await rebuildSession(accountId, category, url);
|
|
383
|
+
const result = await sendRawJsonRpc(url, session, body);
|
|
384
|
+
if (result.newSessionId) {
|
|
385
|
+
session.sessionId = result.newSessionId;
|
|
386
|
+
}
|
|
387
|
+
return result.rpcResult;
|
|
388
|
+
}
|
|
389
|
+
console.error(
|
|
390
|
+
`${LOG_TAG} rpc failed account=${accountId} category=${category} method=${method} error=${error instanceof Error ? error.message : String(error)}`,
|
|
391
|
+
);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
package/src/channel.ts
CHANGED
|
@@ -27,16 +27,22 @@ const meta = {
|
|
|
27
27
|
selectionLabel: "WeCom (企业微信)",
|
|
28
28
|
docsPath: "/channels/wecom",
|
|
29
29
|
docsLabel: "企业微信",
|
|
30
|
-
blurb:
|
|
30
|
+
blurb:
|
|
31
|
+
"企业微信官方推荐三方插件,默认 Bot WS 配置简单,支持主动发消息与 Agent 全能力。",
|
|
31
32
|
selectionDocsPrefix: "文档:",
|
|
32
33
|
aliases: ["wechatwork", "wework", "qywx", "企微", "企业微信"],
|
|
33
34
|
order: 85,
|
|
34
35
|
quickstartAllowFrom: true,
|
|
35
36
|
};
|
|
36
37
|
|
|
37
|
-
function resolveAccountInboundPath(
|
|
38
|
+
function resolveAccountInboundPath(
|
|
39
|
+
account: ResolvedWecomAccount,
|
|
40
|
+
): string | undefined {
|
|
38
41
|
const derivedPaths = resolveDerivedPathSummary(account.accountId);
|
|
39
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
account.bot?.primaryTransport === "webhook" &&
|
|
44
|
+
account.bot.webhookConfigured
|
|
45
|
+
) {
|
|
40
46
|
return derivedPaths.botWebhook[0];
|
|
41
47
|
}
|
|
42
48
|
if (account.agent?.callbackConfigured) {
|
|
@@ -51,7 +57,9 @@ function normalizeWecomMessagingTarget(raw: string): string | undefined {
|
|
|
51
57
|
if (/^wecom-agent:/i.test(trimmed)) {
|
|
52
58
|
return trimmed;
|
|
53
59
|
}
|
|
54
|
-
return
|
|
60
|
+
return (
|
|
61
|
+
trimmed.replace(/^(wecom|wechatwork|wework|qywx):/i, "").trim() || undefined
|
|
62
|
+
);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
@@ -68,9 +76,6 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
|
68
76
|
blockStreaming: false,
|
|
69
77
|
},
|
|
70
78
|
reload: { configPrefixes: ["channels.wecom"] },
|
|
71
|
-
// NOTE: We intentionally avoid Zod -> JSON Schema conversion at plugin-load time.
|
|
72
|
-
// Some OpenClaw runtime environments load plugin modules via jiti in a way that can
|
|
73
|
-
// surface zod `toJSONSchema()` binding issues (e.g. `this` undefined leading to `_zod` errors).
|
|
74
79
|
// A permissive schema keeps config UX working while preventing startup failures.
|
|
75
80
|
configSchema: {
|
|
76
81
|
schema: {
|
|
@@ -81,8 +86,10 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
|
81
86
|
},
|
|
82
87
|
config: {
|
|
83
88
|
listAccountIds: (cfg) => listWecomAccountIds(cfg as OpenClawConfig),
|
|
84
|
-
resolveAccount: (cfg, accountId) =>
|
|
85
|
-
|
|
89
|
+
resolveAccount: (cfg, accountId) =>
|
|
90
|
+
resolveWecomAccount({ cfg: cfg as OpenClawConfig, accountId }),
|
|
91
|
+
defaultAccountId: (cfg) =>
|
|
92
|
+
resolveDefaultWecomAccountId(cfg as OpenClawConfig),
|
|
86
93
|
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
|
87
94
|
setAccountEnabledInConfigSection({
|
|
88
95
|
cfg: cfg as OpenClawConfig,
|
|
@@ -126,9 +133,15 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
|
126
133
|
};
|
|
127
134
|
},
|
|
128
135
|
resolveAllowFrom: ({ cfg, accountId }) => {
|
|
129
|
-
const account = resolveWecomAccount({
|
|
136
|
+
const account = resolveWecomAccount({
|
|
137
|
+
cfg: cfg as OpenClawConfig,
|
|
138
|
+
accountId,
|
|
139
|
+
});
|
|
130
140
|
// 与其他渠道保持一致:直接返回 allowFrom,空则允许所有人
|
|
131
|
-
const allowFrom =
|
|
141
|
+
const allowFrom =
|
|
142
|
+
account.agent?.config.dm?.allowFrom ??
|
|
143
|
+
account.bot?.config.dm?.allowFrom ??
|
|
144
|
+
[];
|
|
132
145
|
return allowFrom.map((entry) => String(entry));
|
|
133
146
|
},
|
|
134
147
|
formatAllowFrom: ({ allowFrom }) =>
|
|
@@ -170,20 +183,31 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
|
170
183
|
transport: (snapshot as { transport?: string }).transport ?? null,
|
|
171
184
|
ownerId: (snapshot as { ownerId?: string }).ownerId ?? null,
|
|
172
185
|
health: (snapshot as { health?: string }).health ?? "idle",
|
|
173
|
-
ownerDriftAt:
|
|
186
|
+
ownerDriftAt:
|
|
187
|
+
(snapshot as { ownerDriftAt?: number | null }).ownerDriftAt ?? null,
|
|
174
188
|
connected: (snapshot as { connected?: boolean }).connected,
|
|
175
189
|
authenticated: (snapshot as { authenticated?: boolean }).authenticated,
|
|
176
190
|
lastStartAt: snapshot.lastStartAt ?? null,
|
|
177
191
|
lastStopAt: snapshot.lastStopAt ?? null,
|
|
178
192
|
lastError: snapshot.lastError ?? null,
|
|
179
|
-
lastErrorAt:
|
|
193
|
+
lastErrorAt:
|
|
194
|
+
(snapshot as { lastErrorAt?: number | null }).lastErrorAt ?? null,
|
|
180
195
|
lastInboundAt: snapshot.lastInboundAt ?? null,
|
|
181
196
|
lastOutboundAt: snapshot.lastOutboundAt ?? null,
|
|
182
|
-
recentInboundSummary:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
197
|
+
recentInboundSummary:
|
|
198
|
+
(snapshot as { recentInboundSummary?: string | null })
|
|
199
|
+
.recentInboundSummary ?? null,
|
|
200
|
+
recentOutboundSummary:
|
|
201
|
+
(snapshot as { recentOutboundSummary?: string | null })
|
|
202
|
+
.recentOutboundSummary ?? null,
|
|
203
|
+
recentIssueCategory:
|
|
204
|
+
(snapshot as { recentIssueCategory?: string | null })
|
|
205
|
+
.recentIssueCategory ?? null,
|
|
206
|
+
recentIssueSummary:
|
|
207
|
+
(snapshot as { recentIssueSummary?: string | null })
|
|
208
|
+
.recentIssueSummary ?? null,
|
|
209
|
+
transportSessions:
|
|
210
|
+
(snapshot as { transportSessions?: string[] }).transportSessions ?? [],
|
|
187
211
|
probe: snapshot.probe,
|
|
188
212
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
189
213
|
}),
|
|
@@ -199,25 +223,43 @@ export const wecomPlugin: ChannelPlugin<ResolvedWecomAccount> = {
|
|
|
199
223
|
enabled: account.enabled,
|
|
200
224
|
configured: account.configured && !conflict,
|
|
201
225
|
webhookPath: resolveAccountInboundPath(account),
|
|
202
|
-
primaryTransport:
|
|
203
|
-
|
|
226
|
+
primaryTransport:
|
|
227
|
+
account.bot?.primaryTransport ??
|
|
228
|
+
(account.agent ? "agent-callback" : null),
|
|
229
|
+
transport:
|
|
230
|
+
(runtime as { transport?: string } | undefined)?.transport ?? null,
|
|
204
231
|
ownerId: (runtime as { ownerId?: string } | undefined)?.ownerId ?? null,
|
|
205
232
|
health: (runtime as { health?: string } | undefined)?.health ?? "idle",
|
|
206
|
-
ownerDriftAt:
|
|
233
|
+
ownerDriftAt:
|
|
234
|
+
(runtime as { ownerDriftAt?: number | null } | undefined)
|
|
235
|
+
?.ownerDriftAt ?? null,
|
|
207
236
|
connected: (runtime as { connected?: boolean } | undefined)?.connected,
|
|
208
|
-
authenticated: (runtime as { authenticated?: boolean } | undefined)
|
|
237
|
+
authenticated: (runtime as { authenticated?: boolean } | undefined)
|
|
238
|
+
?.authenticated,
|
|
209
239
|
running: runtime?.running ?? false,
|
|
210
240
|
lastStartAt: runtime?.lastStartAt ?? null,
|
|
211
241
|
lastStopAt: runtime?.lastStopAt ?? null,
|
|
212
242
|
lastError: runtime?.lastError ?? conflict?.message ?? null,
|
|
213
|
-
lastErrorAt:
|
|
243
|
+
lastErrorAt:
|
|
244
|
+
(runtime as { lastErrorAt?: number | null } | undefined)
|
|
245
|
+
?.lastErrorAt ?? null,
|
|
214
246
|
lastInboundAt: runtime?.lastInboundAt ?? null,
|
|
215
247
|
lastOutboundAt: runtime?.lastOutboundAt ?? null,
|
|
216
|
-
recentInboundSummary:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
recentInboundSummary:
|
|
249
|
+
(runtime as { recentInboundSummary?: string | null } | undefined)
|
|
250
|
+
?.recentInboundSummary ?? null,
|
|
251
|
+
recentOutboundSummary:
|
|
252
|
+
(runtime as { recentOutboundSummary?: string | null } | undefined)
|
|
253
|
+
?.recentOutboundSummary ?? null,
|
|
254
|
+
recentIssueCategory:
|
|
255
|
+
(runtime as { recentIssueCategory?: string | null } | undefined)
|
|
256
|
+
?.recentIssueCategory ?? null,
|
|
257
|
+
recentIssueSummary:
|
|
258
|
+
(runtime as { recentIssueSummary?: string | null } | undefined)
|
|
259
|
+
?.recentIssueSummary ?? null,
|
|
260
|
+
transportSessions:
|
|
261
|
+
(runtime as { transportSessions?: string[] } | undefined)
|
|
262
|
+
?.transportSessions ?? [],
|
|
221
263
|
dmPolicy: account.bot?.config.dm?.policy ?? "pairing",
|
|
222
264
|
};
|
|
223
265
|
},
|