@yanhaidao/wecom 2.4.120 → 2.4.160
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 +4 -5
- package/changelog/v2.4.16.md +19 -0
- package/package.json +1 -1
- package/src/agent/handler.event-filter.test.ts +0 -35
- package/src/agent/handler.ts +4 -149
- package/src/channel.config.test.ts +0 -33
- package/src/config/accounts.ts +0 -16
- package/src/config/index.ts +5 -1
- package/src/config/network.ts +33 -0
- package/src/config/schema.ts +4 -58
- package/src/http.ts +9 -1
- package/src/transport/bot-webhook/inbound-normalizer.test.ts +433 -0
- package/src/transport/bot-webhook/inbound-normalizer.ts +240 -53
- package/src/transport/bot-webhook/message-shape.ts +3 -0
- package/src/transport/bot-ws/inbound.test.ts +195 -1
- package/src/transport/bot-ws/inbound.ts +57 -10
- package/src/types/account.ts +0 -2
- package/src/types/config.ts +4 -56
- package/src/types/message.ts +11 -9
- package/MENU_EVENT_CONF.md +0 -500
- package/MENU_EVENT_PLAN.md +0 -440
- package/scripts/wecom/README.md +0 -123
- package/scripts/wecom/menu-click-help.js +0 -59
- package/scripts/wecom/menu-click-help.py +0 -55
- package/src/agent/event-router.test.ts +0 -421
- package/src/agent/event-router.ts +0 -272
- package/src/agent/script-runner.ts +0 -186
- package/src/agent/test-fixtures/invalid-json-script.mjs +0 -1
- package/src/agent/test-fixtures/reply-event-script.mjs +0 -29
- package/src/agent/test-fixtures/reply-event-script.py +0 -17
package/README.md
CHANGED
|
@@ -163,11 +163,10 @@
|
|
|
163
163
|
> 项目保持高频迭代,全面对齐甚至超越企业真实业务诉求。
|
|
164
164
|
> **为保持精简,以下仅展示近期 5 次重要更新,完整历史版本(含全部 `v2.2.x`)请前往 [changelog/ 目录](./changelog/) 查阅。**
|
|
165
165
|
|
|
166
|
-
#### 📌 v2.4.
|
|
167
|
-
- **[
|
|
168
|
-
- **[
|
|
169
|
-
- **[
|
|
170
|
-
- **[重要修复] Webhook 入站文件不再被固定 5MB 限制误拦** 📎 之前有些企业微信附件虽然在实际配置允许范围内,但进入会话工作区前仍会被旧的固定阈值挡住。这一版已经改成按当前 WeCom 配置解析后的大小限制执行,入站文件接入更稳。
|
|
166
|
+
#### 📌 v2.4.16(2026-04-16)
|
|
167
|
+
- **[版本对齐] 今日 changelog 已补齐到当前包版本** 🧾 `README` 摘要和 `changelog/` 目录现在都直接对应 `package.json` 里的 `v2.4.16`,不会再停留在旧版本号。
|
|
168
|
+
- **[展示修正] 最新版本入口已切换到 `v2.4.16`** 🔖 现在从项目首页查看最近更新时,看到的版本号就是当前实际包版本,减少发布说明和安装版本不一致的困惑。
|
|
169
|
+
- **[维护优化] 变更追踪更直接** 📚 后续如果继续补充 `v2.4.16` 的详细更新内容,可以直接落在 `changelog/v2.4.16.md`,不需要复用旧版本文档。
|
|
171
170
|
|
|
172
171
|
#### 📌 v2.3.27(2026-03-27)
|
|
173
172
|
- **[重要修复] `channel add` 重新支持 WeCom guided setup** 🧭 之前有些环境下,`wecom` 虽然已经安装,却仍会在 OpenClaw 里显示成 “does not support guided setup yet”,导致无法直接通过交互式向导添加。现在插件已经对齐 OpenClaw 当前的 `setupWizard` 接口,`openclaw channels add` 会重新正常识别和进入配置流程。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# OpenClaw WeCom 插件 v2.4.16 变更简报
|
|
2
|
+
|
|
3
|
+
> [!TIP]
|
|
4
|
+
> `v2.4.16` 是一次版本对齐型更新。本次主要补齐今天版本的 changelog 入口,让文档展示的版本号与当前 [package.json](/Users/YanHaidao/openclaw/extensions/wecom/package.json) 保持一致,避免 README 摘要、变更日志文件名和实际发布版本出现错位。
|
|
5
|
+
|
|
6
|
+
## 2026-04-16(v2.4.16)
|
|
7
|
+
- 【版本对齐】补充今天版本的独立 changelog 文件,文档侧现在可以直接对应到 `v2.4.16`。
|
|
8
|
+
- 【展示修正】README 的“最近更新”摘要已同步切换到 `v2.4.16`,版本号与当前包版本保持一致。
|
|
9
|
+
- 【维护优化】后续查阅变更时,不再需要从旧的 `v2.4.12` 文案反推当前实际发布版本。
|
|
10
|
+
|
|
11
|
+
## 升级后你会直接感受到
|
|
12
|
+
|
|
13
|
+
- 在 README 和 `changelog/` 目录里看到的最新版本号,会和当前 `package.json` 一致。
|
|
14
|
+
- 对外查看版本更新时,不会再出现“包版本已经变了,但 changelog 还停留在旧版本”的混淆。
|
|
15
|
+
|
|
16
|
+
## 升级提示
|
|
17
|
+
|
|
18
|
+
- 当前插件版本为 `v2.4.16`。
|
|
19
|
+
- 如需继续补充该版本的详细功能点,可以直接在本文件追加内容。
|
package/package.json
CHANGED
|
@@ -31,41 +31,6 @@ describe("shouldProcessAgentInboundMessage", () => {
|
|
|
31
31
|
expect(unknown.reason).toBe("event:some_random_event");
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it("blocks event processing when eventEnabled is false", () => {
|
|
35
|
-
const disabled = shouldProcessAgentInboundMessage({
|
|
36
|
-
msgType: "event",
|
|
37
|
-
eventType: "click",
|
|
38
|
-
fromUser: "zhangsan",
|
|
39
|
-
eventEnabled: false,
|
|
40
|
-
});
|
|
41
|
-
expect(disabled.shouldProcess).toBe(false);
|
|
42
|
-
expect(disabled.reason).toBe("event_disabled");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("allows configured custom event types", () => {
|
|
46
|
-
const custom = shouldProcessAgentInboundMessage({
|
|
47
|
-
msgType: "event",
|
|
48
|
-
eventType: "click",
|
|
49
|
-
fromUser: "zhangsan",
|
|
50
|
-
eventEnabled: true,
|
|
51
|
-
allowedEventTypes: ["click"],
|
|
52
|
-
});
|
|
53
|
-
expect(custom.shouldProcess).toBe(true);
|
|
54
|
-
expect(custom.reason).toBe("allowed_event:click");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("normalizes configured event type values before matching", () => {
|
|
58
|
-
const custom = shouldProcessAgentInboundMessage({
|
|
59
|
-
msgType: "event",
|
|
60
|
-
eventType: "view_miniprogram",
|
|
61
|
-
fromUser: "zhangsan",
|
|
62
|
-
eventEnabled: true,
|
|
63
|
-
allowedEventTypes: [" VIEW_MINIPROGRAM "],
|
|
64
|
-
});
|
|
65
|
-
expect(custom.shouldProcess).toBe(true);
|
|
66
|
-
expect(custom.reason).toBe("allowed_event:view_miniprogram");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
34
|
it("skips system sender callbacks", () => {
|
|
70
35
|
const systemSender = shouldProcessAgentInboundMessage({
|
|
71
36
|
msgType: "text",
|
package/src/agent/handler.ts
CHANGED
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
extractAgentId,
|
|
34
34
|
extractToUser,
|
|
35
35
|
} from "../shared/xml-parser.js";
|
|
36
|
-
import { routeAgentInboundEvent } from "./event-router.js";
|
|
37
36
|
import { resolveOutboundMediaAsset } from "../shared/media-asset.js";
|
|
38
37
|
import {
|
|
39
38
|
downloadAgentApiMedia,
|
|
@@ -193,8 +192,6 @@ export function shouldProcessAgentInboundMessage(params: {
|
|
|
193
192
|
fromUser: string;
|
|
194
193
|
chatId?: string;
|
|
195
194
|
eventType?: string;
|
|
196
|
-
eventEnabled?: boolean;
|
|
197
|
-
allowedEventTypes?: string[];
|
|
198
195
|
}): AgentInboundProcessDecision {
|
|
199
196
|
const msgType = String(params.msgType ?? "")
|
|
200
197
|
.trim()
|
|
@@ -207,8 +204,7 @@ export function shouldProcessAgentInboundMessage(params: {
|
|
|
207
204
|
.toLowerCase();
|
|
208
205
|
|
|
209
206
|
if (msgType === "event") {
|
|
210
|
-
|
|
211
|
-
const compatibilityAllowedEvents = [
|
|
207
|
+
const allowedEvents = [
|
|
212
208
|
"subscribe",
|
|
213
209
|
"enter_agent",
|
|
214
210
|
"batch_job_result",
|
|
@@ -224,26 +220,6 @@ export function shouldProcessAgentInboundMessage(params: {
|
|
|
224
220
|
"smartsheet_field_change",
|
|
225
221
|
"smartsheet_view_change",
|
|
226
222
|
];
|
|
227
|
-
const configuredAllowedEvents = Array.isArray(params.allowedEventTypes)
|
|
228
|
-
? params.allowedEventTypes
|
|
229
|
-
.map((entry) => String(entry ?? "").trim().toLowerCase())
|
|
230
|
-
.filter(Boolean)
|
|
231
|
-
: [];
|
|
232
|
-
const hasEventConfig = params.eventEnabled !== undefined || configuredAllowedEvents.length > 0;
|
|
233
|
-
|
|
234
|
-
// 显式关闭 event 时直接拒绝,优先级最高
|
|
235
|
-
if (params.eventEnabled === false) {
|
|
236
|
-
return {
|
|
237
|
-
shouldProcess: false,
|
|
238
|
-
reason: "event_disabled",
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// 配置存在时:历史白名单 + 配置白名单并集,保证平滑迁移
|
|
243
|
-
const allowedEvents = hasEventConfig
|
|
244
|
-
? Array.from(new Set([...compatibilityAllowedEvents, ...configuredAllowedEvents]))
|
|
245
|
-
: compatibilityAllowedEvents;
|
|
246
|
-
|
|
247
223
|
if (
|
|
248
224
|
allowedEvents.includes(eventType) ||
|
|
249
225
|
eventType.startsWith("doc_") ||
|
|
@@ -302,72 +278,6 @@ function normalizeAgentId(value: unknown): number | undefined {
|
|
|
302
278
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
303
279
|
}
|
|
304
280
|
|
|
305
|
-
function resolveAgentReplyTransportContext(params: {
|
|
306
|
-
agent: ResolvedAgentAccount;
|
|
307
|
-
msg: WecomAgentInboundMessage;
|
|
308
|
-
fromUser: string;
|
|
309
|
-
chatId?: string;
|
|
310
|
-
log?: (msg: string) => void;
|
|
311
|
-
error?: (msg: string) => void;
|
|
312
|
-
}): {
|
|
313
|
-
upstreamAgent?: ResolvedAgentAccount;
|
|
314
|
-
primaryAgentForUpstream?: ResolvedAgentAccount;
|
|
315
|
-
upstreamReplyTarget?: { toUser: string | undefined; chatId: string | undefined };
|
|
316
|
-
effectiveReplyTarget: { toUser: string | undefined; chatId: string | undefined };
|
|
317
|
-
} {
|
|
318
|
-
// 事件路由也可能需要即时回包,这里复用普通消息的上下游目标判定逻辑
|
|
319
|
-
const { agent, msg, fromUser, chatId, log, error } = params;
|
|
320
|
-
const isGroup = Boolean(chatId);
|
|
321
|
-
const peerId = isGroup ? chatId! : fromUser;
|
|
322
|
-
const replyTarget = isGroup
|
|
323
|
-
? ({ toUser: undefined, chatId: peerId } as const)
|
|
324
|
-
: ({ toUser: fromUser, chatId: undefined } as const);
|
|
325
|
-
const toUserName = extractToUser(msg);
|
|
326
|
-
const isUpstreamUser = detectUpstreamUser({
|
|
327
|
-
messageToUserName: toUserName,
|
|
328
|
-
primaryCorpId: agent.corpId,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (!isUpstreamUser) {
|
|
332
|
-
return {
|
|
333
|
-
upstreamAgent: undefined,
|
|
334
|
-
primaryAgentForUpstream: undefined,
|
|
335
|
-
upstreamReplyTarget: undefined,
|
|
336
|
-
effectiveReplyTarget: replyTarget,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
log?.(`[wecom-agent] detected upstream user during event routing: from=${fromUser} toCorpId=${toUserName}`);
|
|
341
|
-
const upstreamConfig = resolveUpstreamCorpConfig({
|
|
342
|
-
upstreamCorpId: toUserName,
|
|
343
|
-
upstreamCorps: agent.config.upstreamCorps,
|
|
344
|
-
});
|
|
345
|
-
if (!upstreamConfig) {
|
|
346
|
-
error?.(
|
|
347
|
-
`[wecom-agent] upstream event detected but no upstream config for corpId=${toUserName}; fallback to primary agent target`,
|
|
348
|
-
);
|
|
349
|
-
return {
|
|
350
|
-
upstreamAgent: undefined,
|
|
351
|
-
primaryAgentForUpstream: undefined,
|
|
352
|
-
upstreamReplyTarget: undefined,
|
|
353
|
-
effectiveReplyTarget: replyTarget,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const upstreamAgent = createUpstreamAgentConfig({
|
|
358
|
-
baseAgent: agent,
|
|
359
|
-
upstreamCorpId: toUserName,
|
|
360
|
-
upstreamAgentId: upstreamConfig.agentId,
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
upstreamAgent,
|
|
365
|
-
primaryAgentForUpstream: agent,
|
|
366
|
-
upstreamReplyTarget: replyTarget,
|
|
367
|
-
effectiveReplyTarget: replyTarget,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
281
|
/**
|
|
372
282
|
* **resolveQueryParams (解析查询参数)**
|
|
373
283
|
*
|
|
@@ -481,8 +391,6 @@ async function handleMessageCallback(params: AgentWebhookParams): Promise<boolea
|
|
|
481
391
|
fromUser,
|
|
482
392
|
chatId,
|
|
483
393
|
eventType,
|
|
484
|
-
eventEnabled: agent.eventEnabled,
|
|
485
|
-
allowedEventTypes: agent.allowedEventTypes,
|
|
486
394
|
});
|
|
487
395
|
if (!decision.shouldProcess) {
|
|
488
396
|
log?.(
|
|
@@ -491,57 +399,6 @@ async function handleMessageCallback(params: AgentWebhookParams): Promise<boolea
|
|
|
491
399
|
return true;
|
|
492
400
|
}
|
|
493
401
|
|
|
494
|
-
const routedEvent = await routeAgentInboundEvent({
|
|
495
|
-
agent,
|
|
496
|
-
msgType,
|
|
497
|
-
eventType,
|
|
498
|
-
fromUser,
|
|
499
|
-
chatId,
|
|
500
|
-
msg,
|
|
501
|
-
log,
|
|
502
|
-
auditSink,
|
|
503
|
-
});
|
|
504
|
-
// 路由器返回文本时,先即时回包给用户/群,再决定是否进入默认 AI 流程
|
|
505
|
-
if (routedEvent.handled && routedEvent.replyText?.trim()) {
|
|
506
|
-
const replyContext = resolveAgentReplyTransportContext({
|
|
507
|
-
agent,
|
|
508
|
-
msg,
|
|
509
|
-
fromUser,
|
|
510
|
-
chatId,
|
|
511
|
-
log,
|
|
512
|
-
error,
|
|
513
|
-
});
|
|
514
|
-
try {
|
|
515
|
-
if (replyContext.upstreamAgent && replyContext.primaryAgentForUpstream) {
|
|
516
|
-
await sendUpstreamAgentApiText({
|
|
517
|
-
upstreamAgent: replyContext.upstreamAgent,
|
|
518
|
-
primaryAgent: replyContext.primaryAgentForUpstream,
|
|
519
|
-
...(replyContext.upstreamReplyTarget ?? replyContext.effectiveReplyTarget),
|
|
520
|
-
text: routedEvent.replyText,
|
|
521
|
-
});
|
|
522
|
-
} else {
|
|
523
|
-
await sendAgentApiText({
|
|
524
|
-
agent,
|
|
525
|
-
...replyContext.effectiveReplyTarget,
|
|
526
|
-
text: routedEvent.replyText,
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
params.touchTransportSession?.({ lastOutboundAt: Date.now(), running: true });
|
|
530
|
-
log?.(
|
|
531
|
-
`[wecom-agent] event route reply delivered routeId=${routedEvent.matchedRouteId ?? "N/A"} to=${chatId ? `chat:${chatId}` : fromUser}`,
|
|
532
|
-
);
|
|
533
|
-
} catch (err) {
|
|
534
|
-
error?.(`[wecom-agent] event route reply failed: ${String(err)}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
// routedEvent 已完全消费该事件时,终止后续默认处理链
|
|
538
|
-
if (routedEvent.handled && !routedEvent.chainToAgent) {
|
|
539
|
-
log?.(
|
|
540
|
-
`[wecom-agent] event route handled routeId=${routedEvent.matchedRouteId ?? "N/A"} reason=${routedEvent.reason}`,
|
|
541
|
-
);
|
|
542
|
-
return true;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
402
|
// 异步处理消息
|
|
546
403
|
processAgentMessage({
|
|
547
404
|
agent,
|
|
@@ -563,11 +420,9 @@ async function handleMessageCallback(params: AgentWebhookParams): Promise<boolea
|
|
|
563
420
|
return true;
|
|
564
421
|
} catch (err) {
|
|
565
422
|
error?.(`[wecom-agent] callback failed: ${String(err)}`);
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
res.end(`error - 回调处理失败${ERROR_HELP}`);
|
|
570
|
-
}
|
|
423
|
+
res.statusCode = 400;
|
|
424
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
425
|
+
res.end(`error - 回调处理失败${ERROR_HELP}`);
|
|
571
426
|
return true;
|
|
572
427
|
}
|
|
573
428
|
}
|
|
@@ -2,7 +2,6 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
import { wecomPlugin } from "./channel.js";
|
|
5
|
-
import { resolveWecomAccounts } from "./config/accounts.js";
|
|
6
5
|
|
|
7
6
|
describe("wecomPlugin config.deleteAccount", () => {
|
|
8
7
|
it("removes only the target matrix account", () => {
|
|
@@ -145,36 +144,4 @@ describe("wecomPlugin account conflict guards", () => {
|
|
|
145
144
|
"Duplicate WeCom agent identity",
|
|
146
145
|
);
|
|
147
146
|
});
|
|
148
|
-
|
|
149
|
-
it("normalizes agent inbound event policy on resolved accounts", () => {
|
|
150
|
-
const cfg: OpenClawConfig = {
|
|
151
|
-
channels: {
|
|
152
|
-
wecom: {
|
|
153
|
-
enabled: true,
|
|
154
|
-
accounts: {
|
|
155
|
-
default: {
|
|
156
|
-
enabled: true,
|
|
157
|
-
agent: {
|
|
158
|
-
corpId: "corp-1",
|
|
159
|
-
corpSecret: "secret-a",
|
|
160
|
-
agentId: 1001,
|
|
161
|
-
token: "token-a",
|
|
162
|
-
encodingAESKey: "aes-a",
|
|
163
|
-
inboundPolicy: {
|
|
164
|
-
eventEnabled: true,
|
|
165
|
-
eventPolicy: {
|
|
166
|
-
allowedEventTypes: [" Click ", "view", "click"],
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
} as OpenClawConfig;
|
|
175
|
-
|
|
176
|
-
const resolved = resolveWecomAccounts(cfg);
|
|
177
|
-
expect(resolved.accounts.default?.agent?.eventEnabled).toBe(true);
|
|
178
|
-
expect(resolved.accounts.default?.agent?.allowedEventTypes).toEqual(["click", "view"]);
|
|
179
|
-
});
|
|
180
147
|
});
|
package/src/config/accounts.ts
CHANGED
|
@@ -28,15 +28,6 @@ function toNumber(value: number | string | undefined): number | undefined {
|
|
|
28
28
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function normalizeAllowedEventTypes(value: string[] | undefined): string[] | undefined {
|
|
32
|
-
// 配置层允许大小写/空白混写,这里统一规整为去重后的小写列表
|
|
33
|
-
if (!Array.isArray(value)) return undefined;
|
|
34
|
-
const normalized = value
|
|
35
|
-
.map((entry) => String(entry ?? "").trim().toLowerCase())
|
|
36
|
-
.filter(Boolean);
|
|
37
|
-
return normalized.length > 0 ? Array.from(new Set(normalized)) : undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
31
|
function resolveBotAccount(
|
|
41
32
|
accountId: string,
|
|
42
33
|
config: WecomBotConfig,
|
|
@@ -84,11 +75,6 @@ function resolveAgentAccount(
|
|
|
84
75
|
const callbackConfigured = Boolean(config.token && config.encodingAESKey);
|
|
85
76
|
const normalizedAgentSecret = config.agentSecret?.trim() || config.corpSecret?.trim() || "";
|
|
86
77
|
const apiConfigured = Boolean(config.corpId && normalizedAgentSecret && agentId);
|
|
87
|
-
// 将 event 相关策略提前归一到运行态,避免每次消息都重复解析配置
|
|
88
|
-
const eventEnabled = config.inboundPolicy?.eventEnabled;
|
|
89
|
-
const allowedEventTypes = normalizeAllowedEventTypes(
|
|
90
|
-
config.inboundPolicy?.eventPolicy?.allowedEventTypes,
|
|
91
|
-
);
|
|
92
78
|
return {
|
|
93
79
|
accountId,
|
|
94
80
|
configured: callbackConfigured || apiConfigured,
|
|
@@ -99,8 +85,6 @@ function resolveAgentAccount(
|
|
|
99
85
|
agentId,
|
|
100
86
|
token: config.token,
|
|
101
87
|
encodingAESKey: config.encodingAESKey,
|
|
102
|
-
eventEnabled,
|
|
103
|
-
allowedEventTypes,
|
|
104
88
|
config,
|
|
105
89
|
network,
|
|
106
90
|
};
|
package/src/config/index.ts
CHANGED
|
@@ -11,7 +11,11 @@ export {
|
|
|
11
11
|
} from "./accounts.js";
|
|
12
12
|
export { resolveWecomRuntimeAccount, resolveWecomRuntimeConfig, type ResolvedRuntimeAccount, type ResolvedRuntimeConfig } from "./runtime-config.js";
|
|
13
13
|
export { resolveDerivedPath, resolveDerivedPathSummary } from "./derived-paths.js";
|
|
14
|
-
export {
|
|
14
|
+
export {
|
|
15
|
+
resolveWecomEgressProxyUrl,
|
|
16
|
+
resolveWecomEgressProxyUrlFromNetwork,
|
|
17
|
+
resolveWecomMediaDownloadTimeoutMs,
|
|
18
|
+
} from "./network.js";
|
|
15
19
|
export {
|
|
16
20
|
DEFAULT_WECOM_MEDIA_MAX_BYTES,
|
|
17
21
|
getWecomDefaultMediaLocalRoots,
|
package/src/config/network.ts
CHANGED
|
@@ -2,6 +2,16 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
|
2
2
|
|
|
3
3
|
import type { WecomConfig, WecomNetworkConfig } from "../types/index.js";
|
|
4
4
|
|
|
5
|
+
const DEFAULT_WECOM_MEDIA_DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
6
|
+
|
|
7
|
+
function parsePositiveInt(value: unknown): number | undefined {
|
|
8
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
9
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return Math.floor(parsed);
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
export function resolveWecomEgressProxyUrlFromNetwork(network?: WecomNetworkConfig): string | undefined {
|
|
6
16
|
const proxyUrl = network?.egressProxyUrl ??
|
|
7
17
|
process.env.OPENCLAW_WECOM_EGRESS_PROXY_URL ??
|
|
@@ -18,3 +28,26 @@ export function resolveWecomEgressProxyUrl(cfg: OpenClawConfig): string | undefi
|
|
|
18
28
|
const wecom = cfg.channels?.wecom as WecomConfig | undefined;
|
|
19
29
|
return resolveWecomEgressProxyUrlFromNetwork(wecom?.network);
|
|
20
30
|
}
|
|
31
|
+
|
|
32
|
+
export function resolveWecomMediaDownloadTimeoutMs(cfg: OpenClawConfig): number {
|
|
33
|
+
const wecom = cfg.channels?.wecom as
|
|
34
|
+
| {
|
|
35
|
+
media?: { downloadTimeoutMs?: unknown };
|
|
36
|
+
mediaDownloadTimeoutMs?: unknown;
|
|
37
|
+
network?: {
|
|
38
|
+
mediaDownloadTimeoutMs?: unknown;
|
|
39
|
+
timeoutMs?: unknown;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
| undefined;
|
|
43
|
+
|
|
44
|
+
const timeoutMs =
|
|
45
|
+
parsePositiveInt(wecom?.media?.downloadTimeoutMs) ??
|
|
46
|
+
parsePositiveInt(wecom?.mediaDownloadTimeoutMs) ??
|
|
47
|
+
parsePositiveInt(wecom?.network?.mediaDownloadTimeoutMs) ??
|
|
48
|
+
parsePositiveInt(wecom?.network?.timeoutMs) ??
|
|
49
|
+
parsePositiveInt(process.env.OPENCLAW_WECOM_MEDIA_TIMEOUT_MS) ??
|
|
50
|
+
parsePositiveInt(process.env.WECOM_MEDIA_TIMEOUT_MS);
|
|
51
|
+
|
|
52
|
+
return timeoutMs ?? DEFAULT_WECOM_MEDIA_DOWNLOAD_TIMEOUT_MS;
|
|
53
|
+
}
|
package/src/config/schema.ts
CHANGED
|
@@ -3,71 +3,19 @@ export interface DmConfig {
|
|
|
3
3
|
allowFrom?: (string | number)[];
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export interface AgentEventPolicyConfig {
|
|
7
|
-
allowedEventTypes?: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface AgentInboundPolicyConfig {
|
|
11
|
-
eventEnabled?: boolean;
|
|
12
|
-
eventPolicy?: AgentEventPolicyConfig;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface AgentEventRouteMatchConfig {
|
|
16
|
-
eventType?: string;
|
|
17
|
-
changeType?: string;
|
|
18
|
-
eventKey?: string;
|
|
19
|
-
eventKeyPrefix?: string;
|
|
20
|
-
eventKeyPattern?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface AgentEventRouteHandlerBuiltinConfig {
|
|
24
|
-
type: "builtin";
|
|
25
|
-
name?: "echo";
|
|
26
|
-
chainToAgent?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface AgentEventRouteHandlerScriptConfig {
|
|
30
|
-
type: "node_script" | "python_script";
|
|
31
|
-
entry: string;
|
|
32
|
-
timeoutMs?: number;
|
|
33
|
-
chainToAgent?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type AgentEventRouteHandlerConfig =
|
|
37
|
-
| AgentEventRouteHandlerBuiltinConfig
|
|
38
|
-
| AgentEventRouteHandlerScriptConfig;
|
|
39
|
-
|
|
40
|
-
export interface AgentEventRouteConfig {
|
|
41
|
-
id?: string;
|
|
42
|
-
when?: AgentEventRouteMatchConfig;
|
|
43
|
-
handler: AgentEventRouteHandlerConfig;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface AgentEventRoutingConfig {
|
|
47
|
-
unmatchedAction?: "ignore" | "forwardToAgent";
|
|
48
|
-
routes?: AgentEventRouteConfig[];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface AgentScriptRuntimeConfig {
|
|
52
|
-
enabled?: boolean;
|
|
53
|
-
allowPaths?: string[];
|
|
54
|
-
maxStdoutBytes?: number;
|
|
55
|
-
maxStderrBytes?: number;
|
|
56
|
-
defaultTimeoutMs?: number;
|
|
57
|
-
pythonCommand?: string;
|
|
58
|
-
nodeCommand?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
6
|
export interface MediaConfig {
|
|
62
7
|
tempDir?: string;
|
|
63
8
|
retentionHours?: number;
|
|
64
9
|
cleanupOnStart?: boolean;
|
|
65
10
|
maxBytes?: number;
|
|
11
|
+
downloadTimeoutMs?: number;
|
|
66
12
|
localRoots?: string[];
|
|
67
13
|
}
|
|
68
14
|
|
|
69
15
|
export interface NetworkConfig {
|
|
70
16
|
egressProxyUrl?: string;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
mediaDownloadTimeoutMs?: number;
|
|
71
19
|
}
|
|
72
20
|
|
|
73
21
|
export interface RoutingConfig {
|
|
@@ -105,9 +53,6 @@ export interface AgentConfig {
|
|
|
105
53
|
encodingAESKey: string;
|
|
106
54
|
welcomeText?: string;
|
|
107
55
|
dm?: DmConfig;
|
|
108
|
-
inboundPolicy?: AgentInboundPolicyConfig;
|
|
109
|
-
eventRouting?: AgentEventRoutingConfig;
|
|
110
|
-
scriptRuntime?: AgentScriptRuntimeConfig;
|
|
111
56
|
}
|
|
112
57
|
|
|
113
58
|
export interface DynamicAgentsConfig {
|
|
@@ -128,6 +73,7 @@ export interface AccountConfig {
|
|
|
128
73
|
export interface WecomConfigInput {
|
|
129
74
|
enabled?: boolean;
|
|
130
75
|
mediaMaxMb?: number;
|
|
76
|
+
mediaDownloadTimeoutMs?: number;
|
|
131
77
|
bot?: BotConfig;
|
|
132
78
|
agent?: AgentConfig;
|
|
133
79
|
accounts?: Record<string, AccountConfig>;
|
package/src/http.ts
CHANGED
|
@@ -92,11 +92,19 @@ export async function wecomFetch(input: string | URL, init?: RequestInit, opts?:
|
|
|
92
92
|
);
|
|
93
93
|
return response;
|
|
94
94
|
} catch (err: unknown) {
|
|
95
|
-
if (err instanceof Error && err.name === "
|
|
95
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
96
|
+
console.error(
|
|
97
|
+
`[wecom-http] timeout method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`,
|
|
98
|
+
);
|
|
99
|
+
} else if (err instanceof Error && err.name === "TypeError" && err.message === "fetch failed") {
|
|
96
100
|
const cause = (err as any).cause;
|
|
97
101
|
console.error(
|
|
98
102
|
`[wecom-http] fetch failed method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}${cause ? ` cause=${String(cause)}` : ""}`,
|
|
99
103
|
);
|
|
104
|
+
} else if (err instanceof Error && err.name === "AbortError") {
|
|
105
|
+
console.error(
|
|
106
|
+
`[wecom-http] aborted method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`,
|
|
107
|
+
);
|
|
100
108
|
}
|
|
101
109
|
throw err;
|
|
102
110
|
}
|