@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 CHANGED
@@ -163,11 +163,10 @@
163
163
  > 项目保持高频迭代,全面对齐甚至超越企业真实业务诉求。
164
164
  > **为保持精简,以下仅展示近期 5 次重要更新,完整历史版本(含全部 `v2.2.x`)请前往 [changelog/ 目录](./changelog/) 查阅。**
165
165
 
166
- #### 📌 v2.4.12(2026-04-12
167
- - **[新增优化] 自建应用菜单事件可路由到本地脚本** ⚙️ 企业微信自建应用收到 `click` `event` 事件后,现在可以按 `eventType`、`eventKey`、`changeType` 等规则命中本地 `Node.js` / `Python` 脚本,不再只能一律进入默认 Agent 流程。
168
- - **[新增优化] 脚本既能直接回复,也能继续链到 Agent** 🔀 脚本返回结果里可以控制是否继续进入 AI 流程,适合把“菜单先走固定逻辑,复杂问题再交给 Agent”的模式真正落地。
169
- - **[新增能力] 上下游企业现在可通过 Agent 渠道互通** 🏢 如果你的自建应用已经共享给上下游企业,现在插件可以根据下游 `CorpID` `agent.upstreamCorps` 把回复准确发回对应企业,图片和语音等常见媒体链路也一起补稳了。
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanhaidao/wecom",
3
- "version": "2.4.120",
3
+ "version": "2.4.160",
4
4
  "description": "OpenClaw 企业微信(WeCom)插件,默认 Bot WebSocket,支持加密媒体解密、Agent 主动发消息与多账号接入",
5
5
  "license": "ISC",
6
6
  "author": "YanHaidao (VX: YanHaidao)",
@@ -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",
@@ -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
- // 兼容旧行为:未配置 event 策略时,继续沿用历史白名单
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
- if (!res.headersSent && !res.writableEnded) {
567
- res.statusCode = 400;
568
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
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
  });
@@ -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
  };
@@ -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 { resolveWecomEgressProxyUrl, resolveWecomEgressProxyUrlFromNetwork } from "./network.js";
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,
@@ -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
+ }
@@ -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 === "TypeError" && err.message === "fetch failed") {
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
  }