@yanhaidao/wecom 2.4.120 → 2.5.110
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/dist/index.js +68 -0
- package/dist/src/accounts.js +20 -0
- package/dist/src/agent/handler.js +895 -0
- package/dist/src/agent/index.js +5 -0
- package/dist/src/app/account-runtime.js +216 -0
- package/dist/src/app/bootstrap.js +19 -0
- package/dist/src/app/index.js +118 -0
- package/dist/src/capability/agent/delivery-service.js +63 -0
- package/dist/src/capability/agent/fallback-policy.js +6 -0
- package/dist/src/capability/agent/ingress-service.js +33 -0
- package/dist/src/capability/agent/upstream-delivery-service.js +71 -0
- package/dist/src/capability/bot/dispatch-config.js +45 -0
- package/dist/src/capability/bot/fallback-delivery.js +147 -0
- package/dist/src/capability/bot/local-path-delivery.js +178 -0
- package/dist/src/capability/bot/sandbox-media.js +138 -0
- package/dist/src/capability/bot/service.js +49 -0
- package/dist/src/capability/bot/stream-delivery.js +321 -0
- package/dist/src/capability/bot/stream-finalizer.js +81 -0
- package/dist/src/capability/bot/stream-orchestrator.js +318 -0
- package/dist/src/capability/bot/types.js +1 -0
- package/{src/capability/calendar/client.ts → dist/src/capability/calendar/client.js} +118 -241
- package/{src/capability/calendar/schema.ts → dist/src/capability/calendar/schema.js} +0 -38
- package/dist/src/capability/calendar/tool.js +365 -0
- package/dist/src/capability/calendar/types.js +12 -0
- package/{src/capability/doc/client.ts → dist/src/capability/doc/client.js} +370 -605
- package/{src/capability/doc/schema.ts → dist/src/capability/doc/schema.js} +345 -394
- package/dist/src/capability/doc/tool.js +1556 -0
- package/dist/src/capability/doc/types.js +113 -0
- package/dist/src/capability/mcp/index.js +3 -0
- package/dist/src/capability/mcp/schema.js +102 -0
- package/dist/src/capability/mcp/tool.js +146 -0
- package/dist/src/capability/mcp/transport.js +293 -0
- package/dist/src/channel.js +224 -0
- package/dist/src/config/accounts.js +236 -0
- package/dist/src/config/derived-paths.js +31 -0
- package/dist/src/config/index.js +7 -0
- package/dist/src/config/media.js +110 -0
- package/dist/src/config/network.js +32 -0
- package/dist/src/config/routing.js +20 -0
- package/dist/src/config/runtime-config.js +25 -0
- package/dist/src/config/schema.js +4 -0
- package/{src/config-schema.ts → dist/src/config-schema.js} +1 -1
- package/dist/src/context-store.js +219 -0
- package/{src/crypto/aes.ts → dist/src/crypto/aes.js} +11 -28
- package/dist/src/crypto/index.js +9 -0
- package/{src/crypto/signature.ts → dist/src/crypto/signature.js} +3 -18
- package/{src/crypto/xml.ts → dist/src/crypto/xml.js} +3 -11
- package/dist/src/crypto.js +145 -0
- package/dist/src/domain/models.js +1 -0
- package/dist/src/domain/policies.js +32 -0
- package/{src/dynamic-agent.ts → dist/src/dynamic-agent.js} +36 -73
- package/dist/src/gateway-monitor.js +139 -0
- package/dist/src/http.js +114 -0
- package/{src/media.ts → dist/src/media.js} +21 -40
- package/dist/src/monitor/limits.js +7 -0
- package/dist/src/monitor/state.js +28 -0
- package/dist/src/monitor.js +84 -0
- package/dist/src/observability/audit-log.js +30 -0
- package/dist/src/observability/legacy-operational-event-store.js +22 -0
- package/dist/src/observability/raw-envelope-log.js +24 -0
- package/dist/src/observability/status-registry.js +9 -0
- package/dist/src/observability/transport-session-view.js +14 -0
- package/dist/src/onboarding.js +546 -0
- package/dist/src/outbound.js +557 -0
- package/dist/src/runtime/dispatcher.js +57 -0
- package/{src/runtime/index.ts → dist/src/runtime/index.js} +0 -1
- package/dist/src/runtime/outbound-intent.js +1 -0
- package/dist/src/runtime/reply-orchestrator.js +38 -0
- package/dist/src/runtime/routing-bridge.js +26 -0
- package/dist/src/runtime/session-manager.js +112 -0
- package/dist/src/runtime/source-registry.js +174 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/shared/command-auth.js +57 -0
- package/{src/shared/index.ts → dist/src/shared/index.js} +0 -1
- package/dist/src/shared/media-asset.js +65 -0
- package/dist/src/shared/media-service.js +59 -0
- package/dist/src/shared/media-types.js +1 -0
- package/{src/shared/xml-parser.ts → dist/src/shared/xml-parser.js} +72 -63
- package/dist/src/store/active-reply-store.js +41 -0
- package/dist/src/store/interfaces.js +1 -0
- package/dist/src/store/memory-store.js +33 -0
- package/dist/src/store/stream-batch-store.js +319 -0
- package/{src/target.ts → dist/src/target.js} +15 -48
- package/dist/src/transport/agent-api/client.js +168 -0
- package/dist/src/transport/agent-api/core.js +337 -0
- package/dist/src/transport/agent-api/delivery.js +28 -0
- package/dist/src/transport/agent-api/media-upload.js +4 -0
- package/dist/src/transport/agent-api/reply.js +24 -0
- package/dist/src/transport/agent-api/upstream-delivery.js +30 -0
- package/dist/src/transport/agent-api/upstream-media-upload.js +46 -0
- package/dist/src/transport/agent-api/upstream-reply.js +26 -0
- package/dist/src/transport/agent-callback/http-handler.js +30 -0
- package/dist/src/transport/agent-callback/inbound.js +4 -0
- package/dist/src/transport/agent-callback/reply.js +8 -0
- package/dist/src/transport/agent-callback/request-handler.js +189 -0
- package/dist/src/transport/agent-callback/session.js +15 -0
- package/dist/src/transport/bot-webhook/active-reply.js +27 -0
- package/dist/src/transport/bot-webhook/http-handler.js +31 -0
- package/dist/src/transport/bot-webhook/inbound-normalizer.js +496 -0
- package/dist/src/transport/bot-webhook/inbound.js +4 -0
- package/dist/src/transport/bot-webhook/message-shape.js +98 -0
- package/dist/src/transport/bot-webhook/protocol.js +124 -0
- package/dist/src/transport/bot-webhook/reply.js +9 -0
- package/dist/src/transport/bot-webhook/request-handler.js +285 -0
- package/dist/src/transport/bot-webhook/session.js +15 -0
- package/dist/src/transport/bot-ws/inbound.js +147 -0
- package/dist/src/transport/bot-ws/media.js +236 -0
- package/dist/src/transport/bot-ws/reply.js +310 -0
- package/dist/src/transport/bot-ws/sdk-adapter.js +257 -0
- package/dist/src/transport/bot-ws/session.js +15 -0
- package/dist/src/transport/http/common.js +78 -0
- package/dist/src/transport/http/registry.js +71 -0
- package/dist/src/transport/http/request-handler.js +51 -0
- package/{src/transport/index.ts → dist/src/transport/index.js} +2 -10
- package/dist/src/types/account.js +1 -0
- package/dist/src/types/config.js +1 -0
- package/dist/src/types/constants.js +28 -0
- package/dist/src/types/events.js +1 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/types/legacy-stream.js +1 -0
- package/dist/src/types/message.js +5 -0
- package/dist/src/types/runtime-context.js +1 -0
- package/dist/src/types/runtime.js +1 -0
- package/dist/src/types.js +1 -0
- package/dist/src/upstream/index.js +111 -0
- package/dist/src/wecom_msg_adapter/markdown_adapter.js +280 -0
- package/openclaw.plugin.json +15 -0
- package/package.json +18 -1
- package/.github/workflows/release.yml +0 -143
- package/GOVERNANCE.md +0 -26
- package/MENU_EVENT_CONF.md +0 -500
- package/MENU_EVENT_PLAN.md +0 -440
- package/SKILLS_CAL.md +0 -895
- package/SKILLS_DOC.md +0 -2288
- package/UPSTREAM_CONFIG.md +0 -170
- package/UPSTREAM_PLAN.md +0 -175
- package/assets/01.bot-add.png +0 -0
- package/assets/01.bot-setp2.png +0 -0
- package/assets/01.image.jpg +0 -0
- package/assets/02.agent.add.png +0 -0
- package/assets/02.agent.api-set.png +0 -0
- package/assets/02.image.jpg +0 -0
- package/assets/03.agent.page.png +0 -0
- package/assets/03.bot.page.png +0 -0
- package/assets/link-me.jpg +0 -0
- package/assets/register.png +0 -0
- package/changelog/v2.2.28.md +0 -70
- package/changelog/v2.3.10.md +0 -17
- package/changelog/v2.3.11.md +0 -19
- package/changelog/v2.3.12.md +0 -25
- package/changelog/v2.3.13.md +0 -19
- package/changelog/v2.3.14.md +0 -48
- package/changelog/v2.3.15.md +0 -15
- package/changelog/v2.3.16.md +0 -11
- package/changelog/v2.3.18.md +0 -22
- package/changelog/v2.3.19.md +0 -73
- package/changelog/v2.3.2.md +0 -28
- package/changelog/v2.3.26.md +0 -21
- package/changelog/v2.3.27.md +0 -33
- package/changelog/v2.3.4.md +0 -20
- package/changelog/v2.3.9.md +0 -22
- package/changelog/v2.4.12.md +0 -37
- package/compat-single-account.md +0 -148
- package/index.test.ts +0 -38
- package/scripts/test-proxy.ts +0 -70
- 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/accounts.ts +0 -34
- package/src/agent/api-client.upload.test.ts +0 -109
- package/src/agent/event-router.test.ts +0 -421
- package/src/agent/event-router.ts +0 -272
- package/src/agent/handler.event-filter.test.ts +0 -135
- package/src/agent/handler.ts +0 -1250
- package/src/agent/index.ts +0 -12
- 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/src/app/account-runtime.ts +0 -276
- package/src/app/bootstrap.ts +0 -29
- package/src/app/index.ts +0 -192
- package/src/capability/agent/delivery-service.ts +0 -87
- package/src/capability/agent/fallback-policy.ts +0 -13
- package/src/capability/agent/ingress-service.ts +0 -38
- package/src/capability/agent/upstream-delivery-service.ts +0 -96
- package/src/capability/bot/dispatch-config.ts +0 -47
- package/src/capability/bot/fallback-delivery.ts +0 -178
- package/src/capability/bot/local-path-delivery.ts +0 -215
- package/src/capability/bot/sandbox-media.test.ts +0 -221
- package/src/capability/bot/sandbox-media.ts +0 -176
- package/src/capability/bot/service.ts +0 -56
- package/src/capability/bot/stream-delivery.ts +0 -379
- package/src/capability/bot/stream-finalizer.ts +0 -120
- package/src/capability/bot/stream-orchestrator.ts +0 -371
- package/src/capability/bot/types.ts +0 -8
- package/src/capability/calendar/SKILLS_CHECKLIST.md +0 -251
- package/src/capability/calendar/tool.ts +0 -417
- package/src/capability/calendar/types.ts +0 -309
- package/src/capability/doc/tool.ts +0 -1629
- package/src/capability/doc/types.ts +0 -792
- package/src/capability/mcp/index.ts +0 -10
- package/src/capability/mcp/schema.ts +0 -107
- package/src/capability/mcp/tool.ts +0 -174
- package/src/capability/mcp/transport.ts +0 -394
- package/src/channel.config.test.ts +0 -180
- package/src/channel.lifecycle.test.ts +0 -255
- package/src/channel.meta.test.ts +0 -26
- package/src/channel.ts +0 -256
- package/src/config/accounts.resolve.test.ts +0 -75
- package/src/config/accounts.ts +0 -312
- package/src/config/derived-paths.test.ts +0 -111
- package/src/config/derived-paths.ts +0 -41
- package/src/config/index.ts +0 -22
- package/src/config/media.test.ts +0 -113
- package/src/config/media.ts +0 -139
- package/src/config/network.ts +0 -20
- package/src/config/routing.test.ts +0 -88
- package/src/config/routing.ts +0 -26
- package/src/config/runtime-config.ts +0 -46
- package/src/config/schema.ts +0 -144
- package/src/context-store.ts +0 -297
- package/src/crypto/index.ts +0 -24
- package/src/crypto.test.ts +0 -32
- package/src/crypto.ts +0 -176
- package/src/domain/models.ts +0 -7
- package/src/domain/policies.ts +0 -36
- package/src/dynamic-agent.account-scope.test.ts +0 -17
- package/src/gateway-monitor.ts +0 -181
- package/src/http.ts +0 -137
- package/src/media.test.ts +0 -82
- package/src/monitor/limits.ts +0 -7
- package/src/monitor/state.queue.test.ts +0 -185
- package/src/monitor/state.ts +0 -34
- package/src/monitor.active.test.ts +0 -245
- package/src/monitor.inbound-filter.test.ts +0 -63
- package/src/monitor.integration.test.ts +0 -208
- package/src/monitor.ts +0 -121
- package/src/monitor.webhook.test.ts +0 -774
- package/src/observability/audit-log.ts +0 -48
- package/src/observability/legacy-operational-event-store.ts +0 -36
- package/src/observability/raw-envelope-log.ts +0 -28
- package/src/observability/status-registry.ts +0 -13
- package/src/observability/transport-session-view.ts +0 -14
- package/src/onboarding.test.ts +0 -336
- package/src/onboarding.ts +0 -704
- package/src/outbound.test.ts +0 -1271
- package/src/outbound.ts +0 -746
- package/src/runtime/dispatcher.ts +0 -71
- package/src/runtime/outbound-intent.ts +0 -4
- package/src/runtime/reply-orchestrator.test.ts +0 -71
- package/src/runtime/reply-orchestrator.ts +0 -67
- package/src/runtime/routing-bridge.test.ts +0 -115
- package/src/runtime/routing-bridge.ts +0 -44
- package/src/runtime/session-manager.test.ts +0 -174
- package/src/runtime/session-manager.ts +0 -139
- package/src/runtime/source-registry.ts +0 -249
- package/src/runtime.ts +0 -14
- package/src/shared/command-auth.ts +0 -87
- package/src/shared/media-asset.ts +0 -78
- package/src/shared/media-service.test.ts +0 -111
- package/src/shared/media-service.ts +0 -84
- package/src/shared/media-types.ts +0 -5
- package/src/shared/xml-parser.test.ts +0 -50
- package/src/store/active-reply-store.ts +0 -42
- package/src/store/interfaces.ts +0 -11
- package/src/store/memory-store.ts +0 -43
- package/src/store/stream-batch-store.ts +0 -350
- package/src/transport/agent-api/client.ts +0 -277
- package/src/transport/agent-api/core.ts +0 -463
- package/src/transport/agent-api/delivery.ts +0 -41
- package/src/transport/agent-api/media-upload.ts +0 -11
- package/src/transport/agent-api/reply.ts +0 -39
- package/src/transport/agent-api/upstream-delivery.ts +0 -45
- package/src/transport/agent-api/upstream-media-upload.ts +0 -70
- package/src/transport/agent-api/upstream-reply.ts +0 -43
- package/src/transport/agent-callback/http-handler.ts +0 -47
- package/src/transport/agent-callback/inbound.ts +0 -5
- package/src/transport/agent-callback/reply.ts +0 -13
- package/src/transport/agent-callback/request-handler.ts +0 -244
- package/src/transport/agent-callback/session.ts +0 -23
- package/src/transport/bot-webhook/active-reply.ts +0 -39
- package/src/transport/bot-webhook/http-handler.ts +0 -48
- package/src/transport/bot-webhook/inbound-normalizer.ts +0 -371
- package/src/transport/bot-webhook/inbound.ts +0 -5
- package/src/transport/bot-webhook/message-shape.ts +0 -89
- package/src/transport/bot-webhook/protocol.ts +0 -148
- package/src/transport/bot-webhook/reply.ts +0 -15
- package/src/transport/bot-webhook/request-handler.ts +0 -394
- package/src/transport/bot-webhook/session.ts +0 -23
- package/src/transport/bot-ws/inbound.test.ts +0 -96
- package/src/transport/bot-ws/inbound.ts +0 -116
- package/src/transport/bot-ws/media.test.ts +0 -44
- package/src/transport/bot-ws/media.ts +0 -321
- package/src/transport/bot-ws/reply.test.ts +0 -450
- package/src/transport/bot-ws/reply.ts +0 -365
- package/src/transport/bot-ws/sdk-adapter.test.ts +0 -187
- package/src/transport/bot-ws/sdk-adapter.ts +0 -314
- package/src/transport/bot-ws/session.ts +0 -28
- package/src/transport/http/common.ts +0 -109
- package/src/transport/http/registry.ts +0 -92
- package/src/transport/http/request-handler.ts +0 -84
- package/src/types/account.ts +0 -72
- package/src/types/config.ts +0 -166
- package/src/types/constants.ts +0 -31
- package/src/types/events.ts +0 -21
- package/src/types/global.d.ts +0 -9
- package/src/types/index.ts +0 -17
- package/src/types/legacy-stream.ts +0 -50
- package/src/types/message.ts +0 -187
- package/src/types/runtime-context.ts +0 -28
- package/src/types/runtime.ts +0 -165
- package/src/types.ts +0 -41
- package/src/upstream/index.ts +0 -150
- package/src/upstream.test.ts +0 -84
- package/src/wecom_msg_adapter/markdown_adapter.ts +0 -331
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -26
- /package/{src/capability/agent/index.ts → dist/src/capability/agent/index.js} +0 -0
- /package/{src/capability/bot/index.ts → dist/src/capability/bot/index.js} +0 -0
- /package/{src/capability/calendar/index.ts → dist/src/capability/calendar/index.js} +0 -0
- /package/{src/capability/index.ts → dist/src/capability/index.js} +0 -0
|
@@ -4,23 +4,13 @@
|
|
|
4
4
|
* 为每个用户/群组自动生成独立的 Agent ID,实现会话隔离。
|
|
5
5
|
* 参考: openclaw-plugin-wecom/dynamic-agent.js
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
9
|
-
|
|
10
|
-
export interface DynamicAgentConfig {
|
|
11
|
-
enabled: boolean;
|
|
12
|
-
dmCreateAgent: boolean;
|
|
13
|
-
groupEnabled: boolean;
|
|
14
|
-
adminUsers: string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
7
|
/**
|
|
18
8
|
* **getDynamicAgentConfig (读取动态 Agent 配置)**
|
|
19
9
|
*
|
|
20
10
|
* 从全局配置中读取动态 Agent 配置,提供默认值。
|
|
21
11
|
*/
|
|
22
|
-
export function getDynamicAgentConfig(config
|
|
23
|
-
const dynamicAgents =
|
|
12
|
+
export function getDynamicAgentConfig(config) {
|
|
13
|
+
const dynamicAgents = config?.channels?.wecom?.dynamicAgents;
|
|
24
14
|
return {
|
|
25
15
|
enabled: dynamicAgents?.enabled ?? false,
|
|
26
16
|
dmCreateAgent: dynamicAgents?.dmCreateAgent ?? true,
|
|
@@ -28,114 +18,89 @@ export function getDynamicAgentConfig(config: OpenClawConfig): DynamicAgentConfi
|
|
|
28
18
|
adminUsers: dynamicAgents?.adminUsers ?? [],
|
|
29
19
|
};
|
|
30
20
|
}
|
|
31
|
-
|
|
32
|
-
function sanitizeDynamicIdPart(value: string): string {
|
|
21
|
+
function sanitizeDynamicIdPart(value) {
|
|
33
22
|
return String(value)
|
|
34
23
|
.trim()
|
|
35
24
|
.toLowerCase()
|
|
36
25
|
.replace(/[^a-z0-9_-]/g, "_");
|
|
37
26
|
}
|
|
38
|
-
|
|
39
27
|
/**
|
|
40
28
|
* **generateAgentId (生成动态 Agent ID)**
|
|
41
29
|
*
|
|
42
30
|
* 根据账号 + 聊天类型 + 对端 ID 生成确定性的 Agent ID,避免多账号串会话。
|
|
43
31
|
* 格式: wecom-{accountId}-{type}-{sanitizedPeerId}
|
|
44
32
|
*/
|
|
45
|
-
export function generateAgentId(chatType
|
|
33
|
+
export function generateAgentId(chatType, peerId, accountId) {
|
|
46
34
|
const sanitizedPeer = sanitizeDynamicIdPart(peerId) || "unknown";
|
|
47
35
|
const sanitizedAccountId = sanitizeDynamicIdPart(accountId ?? "default") || "default";
|
|
48
36
|
return `wecom-${sanitizedAccountId}-${chatType}-${sanitizedPeer}`;
|
|
49
37
|
}
|
|
50
|
-
|
|
51
|
-
export function buildAgentSessionTarget(userId: string, accountId?: string): string {
|
|
38
|
+
export function buildAgentSessionTarget(userId, accountId) {
|
|
52
39
|
const normalizedUserId = String(userId).trim();
|
|
53
40
|
const sanitizedAccountId = sanitizeDynamicIdPart(accountId ?? "default") || "default";
|
|
54
41
|
// Always use explicit user: prefix to avoid ambiguity with numeric party IDs
|
|
55
42
|
return `wecom-agent:${sanitizedAccountId}:user:${normalizedUserId}`;
|
|
56
43
|
}
|
|
57
|
-
|
|
58
44
|
/**
|
|
59
45
|
* **shouldUseDynamicAgent (检查是否使用动态 Agent)**
|
|
60
46
|
*
|
|
61
47
|
* 根据配置和发送者信息判断是否应使用动态 Agent。
|
|
62
48
|
* 管理员(adminUsers)始终绕过动态路由,使用主 Agent。
|
|
63
49
|
*/
|
|
64
|
-
export function shouldUseDynamicAgent(params
|
|
65
|
-
chatType: "dm" | "group";
|
|
66
|
-
senderId: string;
|
|
67
|
-
config: OpenClawConfig;
|
|
68
|
-
}): boolean {
|
|
50
|
+
export function shouldUseDynamicAgent(params) {
|
|
69
51
|
const { chatType, senderId, config } = params;
|
|
70
52
|
const dynamicConfig = getDynamicAgentConfig(config);
|
|
71
|
-
|
|
72
53
|
if (!dynamicConfig.enabled) {
|
|
73
54
|
return false;
|
|
74
55
|
}
|
|
75
|
-
|
|
76
56
|
// 管理员绕过动态路由
|
|
77
57
|
const sender = String(senderId).trim().toLowerCase();
|
|
78
|
-
const isAdmin = dynamicConfig.adminUsers.some(
|
|
79
|
-
(admin) => admin.trim().toLowerCase() === sender
|
|
80
|
-
);
|
|
58
|
+
const isAdmin = dynamicConfig.adminUsers.some((admin) => admin.trim().toLowerCase() === sender);
|
|
81
59
|
if (isAdmin) {
|
|
82
60
|
return false;
|
|
83
61
|
}
|
|
84
|
-
|
|
85
62
|
if (chatType === "group") {
|
|
86
63
|
return dynamicConfig.groupEnabled;
|
|
87
64
|
}
|
|
88
65
|
return dynamicConfig.dmCreateAgent;
|
|
89
66
|
}
|
|
90
|
-
|
|
91
67
|
/**
|
|
92
68
|
* 内存中已确保的 Agent ID(避免重复写入)
|
|
93
69
|
*/
|
|
94
|
-
const ensuredDynamicAgentIds = new Set
|
|
95
|
-
|
|
70
|
+
const ensuredDynamicAgentIds = new Set();
|
|
96
71
|
/**
|
|
97
72
|
* 写入队列(避免并发冲突)
|
|
98
73
|
*/
|
|
99
|
-
let ensureDynamicAgentWriteQueue
|
|
100
|
-
|
|
74
|
+
let ensureDynamicAgentWriteQueue = Promise.resolve();
|
|
101
75
|
/**
|
|
102
76
|
* 将 Agent ID 插入 agents.list(如果不存在)
|
|
103
77
|
*/
|
|
104
|
-
function upsertAgentIdOnlyEntry(cfg
|
|
78
|
+
function upsertAgentIdOnlyEntry(cfg, agentId) {
|
|
105
79
|
if (!cfg.agents || typeof cfg.agents !== "object") {
|
|
106
80
|
cfg.agents = {};
|
|
107
81
|
}
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.map((entry) => entry?.id?.trim().toLowerCase())
|
|
114
|
-
.filter((id): id is string => Boolean(id))
|
|
115
|
-
);
|
|
116
|
-
|
|
82
|
+
const agentsObj = cfg.agents;
|
|
83
|
+
const currentList = Array.isArray(agentsObj.list) ? agentsObj.list : [];
|
|
84
|
+
const existingIds = new Set(currentList
|
|
85
|
+
.map((entry) => entry?.id?.trim().toLowerCase())
|
|
86
|
+
.filter((id) => Boolean(id)));
|
|
117
87
|
let changed = false;
|
|
118
88
|
const nextList = [...currentList];
|
|
119
|
-
|
|
120
89
|
// 首次创建时保留 main 作为默认
|
|
121
90
|
if (nextList.length === 0) {
|
|
122
91
|
nextList.push({ id: "main" });
|
|
123
92
|
existingIds.add("main");
|
|
124
93
|
changed = true;
|
|
125
94
|
}
|
|
126
|
-
|
|
127
95
|
if (!existingIds.has(agentId.toLowerCase())) {
|
|
128
96
|
nextList.push({ id: agentId });
|
|
129
97
|
changed = true;
|
|
130
98
|
}
|
|
131
|
-
|
|
132
99
|
if (changed) {
|
|
133
100
|
agentsObj.list = nextList;
|
|
134
101
|
}
|
|
135
|
-
|
|
136
102
|
return changed;
|
|
137
103
|
}
|
|
138
|
-
|
|
139
104
|
/**
|
|
140
105
|
* **ensureDynamicAgentListed (确保动态 Agent 已添加到 agents.list)**
|
|
141
106
|
*
|
|
@@ -146,40 +111,38 @@ function upsertAgentIdOnlyEntry(cfg: Record<string, unknown>, agentId: string):
|
|
|
146
111
|
* - 异步:不阻塞消息处理流程
|
|
147
112
|
*/
|
|
148
113
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
-
export async function ensureDynamicAgentListed(agentId
|
|
114
|
+
export async function ensureDynamicAgentListed(agentId, runtime) {
|
|
150
115
|
const normalizedId = String(agentId).trim().toLowerCase();
|
|
151
|
-
if (!normalizedId)
|
|
152
|
-
|
|
153
|
-
|
|
116
|
+
if (!normalizedId)
|
|
117
|
+
return;
|
|
118
|
+
if (ensuredDynamicAgentIds.has(normalizedId))
|
|
119
|
+
return;
|
|
154
120
|
const configRuntime = runtime?.config;
|
|
155
|
-
if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile)
|
|
156
|
-
|
|
121
|
+
if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile)
|
|
122
|
+
return;
|
|
157
123
|
ensureDynamicAgentWriteQueue = ensureDynamicAgentWriteQueue
|
|
158
124
|
.then(async () => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
})
|
|
125
|
+
if (ensuredDynamicAgentIds.has(normalizedId))
|
|
126
|
+
return;
|
|
127
|
+
const latestConfig = configRuntime.loadConfig();
|
|
128
|
+
if (!latestConfig || typeof latestConfig !== "object")
|
|
129
|
+
return;
|
|
130
|
+
const changed = upsertAgentIdOnlyEntry(latestConfig, normalizedId);
|
|
131
|
+
if (changed) {
|
|
132
|
+
await configRuntime.writeConfigFile(latestConfig);
|
|
133
|
+
}
|
|
134
|
+
ensuredDynamicAgentIds.add(normalizedId);
|
|
135
|
+
})
|
|
171
136
|
.catch((err) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
137
|
+
console.warn(`[wecom] 动态 Agent 添加失败: ${normalizedId}`, err);
|
|
138
|
+
});
|
|
175
139
|
await ensureDynamicAgentWriteQueue;
|
|
176
140
|
}
|
|
177
|
-
|
|
178
141
|
/**
|
|
179
142
|
* **resetEnsuredCache (重置已确保缓存)**
|
|
180
143
|
*
|
|
181
144
|
* 主要用于测试场景,重置内存中的缓存状态。
|
|
182
145
|
*/
|
|
183
|
-
export function resetEnsuredCache()
|
|
146
|
+
export function resetEnsuredCache() {
|
|
184
147
|
ensuredDynamicAgentIds.clear();
|
|
185
148
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { listWecomAccountIds, resolveWecomAccount, resolveWecomAccountConflict, } from "./config/index.js";
|
|
2
|
+
import { createAccountRuntime } from "./app/bootstrap.js";
|
|
3
|
+
import { registerAccountRuntime, unregisterAccountRuntime } from "./app/index.js";
|
|
4
|
+
import { WecomBotCapabilityService } from "./capability/bot/index.js";
|
|
5
|
+
import { WecomAgentIngressService } from "./capability/agent/index.js";
|
|
6
|
+
const accountRouteRegistry = new Map();
|
|
7
|
+
function logRegisteredRouteSummary(ctx, preferredOrder) {
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const orderedAccountIds = [
|
|
10
|
+
...preferredOrder.filter((accountId) => accountRouteRegistry.has(accountId)),
|
|
11
|
+
...Array.from(accountRouteRegistry.keys())
|
|
12
|
+
.filter((accountId) => !seen.has(accountId))
|
|
13
|
+
.sort((a, b) => a.localeCompare(b)),
|
|
14
|
+
].filter((accountId) => {
|
|
15
|
+
if (seen.has(accountId))
|
|
16
|
+
return false;
|
|
17
|
+
seen.add(accountId);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
const entries = orderedAccountIds
|
|
21
|
+
.map((accountId) => {
|
|
22
|
+
const routes = accountRouteRegistry.get(accountId);
|
|
23
|
+
if (!routes)
|
|
24
|
+
return undefined;
|
|
25
|
+
const botText = routes.botPaths.length > 0 ? routes.botPaths.join(", ") : "未启用";
|
|
26
|
+
const agentText = routes.agentPaths.length > 0 ? routes.agentPaths.join(", ") : "未启用";
|
|
27
|
+
return `accountId=${accountId}(Bot: ${botText};Agent: ${agentText})`;
|
|
28
|
+
})
|
|
29
|
+
.filter((entry) => Boolean(entry));
|
|
30
|
+
const summary = entries.length > 0 ? entries.join("; ") : "无";
|
|
31
|
+
ctx.log?.info(`[${ctx.account.accountId}] 已注册账号路由汇总:${summary}`);
|
|
32
|
+
}
|
|
33
|
+
function resolveExpectedRouteSummaryAccountIds(cfg) {
|
|
34
|
+
return listWecomAccountIds(cfg)
|
|
35
|
+
.filter((accountId) => {
|
|
36
|
+
const conflict = resolveWecomAccountConflict({ cfg, accountId });
|
|
37
|
+
if (conflict)
|
|
38
|
+
return false;
|
|
39
|
+
const account = resolveWecomAccount({ cfg, accountId });
|
|
40
|
+
if (!account.enabled || !account.configured)
|
|
41
|
+
return false;
|
|
42
|
+
return Boolean(account.bot?.configured || account.agent?.configured);
|
|
43
|
+
})
|
|
44
|
+
.sort((a, b) => a.localeCompare(b));
|
|
45
|
+
}
|
|
46
|
+
function waitForAbortSignal(abortSignal) {
|
|
47
|
+
if (abortSignal.aborted) {
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const onAbort = () => {
|
|
52
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
53
|
+
resolve();
|
|
54
|
+
};
|
|
55
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Keeps WeCom webhook targets registered for the account lifecycle.
|
|
60
|
+
* The promise only settles after gateway abort/reload signals shutdown.
|
|
61
|
+
*/
|
|
62
|
+
export async function monitorWecomProvider(ctx) {
|
|
63
|
+
const account = ctx.account;
|
|
64
|
+
const cfg = ctx.cfg;
|
|
65
|
+
const expectedRouteSummaryAccountIds = resolveExpectedRouteSummaryAccountIds(cfg);
|
|
66
|
+
const conflict = resolveWecomAccountConflict({
|
|
67
|
+
cfg,
|
|
68
|
+
accountId: account.accountId,
|
|
69
|
+
});
|
|
70
|
+
if (conflict) {
|
|
71
|
+
ctx.setStatus({
|
|
72
|
+
accountId: account.accountId,
|
|
73
|
+
running: false,
|
|
74
|
+
configured: false,
|
|
75
|
+
lastError: conflict.message,
|
|
76
|
+
});
|
|
77
|
+
throw new Error(conflict.message);
|
|
78
|
+
}
|
|
79
|
+
const bot = account.bot;
|
|
80
|
+
const agent = account.agent;
|
|
81
|
+
const botConfigured = Boolean(bot?.configured);
|
|
82
|
+
const agentConfigured = Boolean(agent?.configured);
|
|
83
|
+
if (!botConfigured && !agentConfigured) {
|
|
84
|
+
ctx.log?.warn(`[${account.accountId}] wecom not configured; channel is idle`);
|
|
85
|
+
ctx.setStatus({ accountId: account.accountId, running: false, configured: false });
|
|
86
|
+
await waitForAbortSignal(ctx.abortSignal);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const accountRuntime = createAccountRuntime(ctx);
|
|
90
|
+
registerAccountRuntime(accountRuntime);
|
|
91
|
+
const botPaths = [];
|
|
92
|
+
const agentPaths = [];
|
|
93
|
+
const runtimeEnv = {
|
|
94
|
+
log: (message) => ctx.log?.info(message),
|
|
95
|
+
error: (message) => ctx.log?.error(message),
|
|
96
|
+
};
|
|
97
|
+
const botService = new WecomBotCapabilityService(accountRuntime, cfg, runtimeEnv);
|
|
98
|
+
const agentIngress = new WecomAgentIngressService(accountRuntime, cfg, runtimeEnv);
|
|
99
|
+
try {
|
|
100
|
+
ctx.log?.info(`[${account.accountId}] wecom runtime start bot=${bot?.primaryTransport ?? "disabled"} agent=${agentConfigured ? "callback/api" : "disabled"}`);
|
|
101
|
+
const botRegistration = botService.start();
|
|
102
|
+
if (botRegistration) {
|
|
103
|
+
botPaths.push(...botRegistration.descriptors);
|
|
104
|
+
ctx.log?.info(`[${account.accountId}] wecom bot ${botRegistration.transport} started: ${botRegistration.descriptors.join(", ")}`);
|
|
105
|
+
}
|
|
106
|
+
const agentRegistration = agentIngress.start();
|
|
107
|
+
if (agentRegistration) {
|
|
108
|
+
agentPaths.push(...agentRegistration.descriptors);
|
|
109
|
+
ctx.log?.info(`[${account.accountId}] wecom agent ${agentRegistration.transport} started: ${agentRegistration.descriptors.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
accountRouteRegistry.set(account.accountId, { botPaths, agentPaths });
|
|
112
|
+
const shouldLogSummary = expectedRouteSummaryAccountIds.length <= 1 ||
|
|
113
|
+
expectedRouteSummaryAccountIds.every((accountId) => accountRouteRegistry.has(accountId));
|
|
114
|
+
if (shouldLogSummary) {
|
|
115
|
+
logRegisteredRouteSummary(ctx, expectedRouteSummaryAccountIds);
|
|
116
|
+
}
|
|
117
|
+
ctx.setStatus({
|
|
118
|
+
running: true,
|
|
119
|
+
configured: true,
|
|
120
|
+
webhookPath: botPaths[0] ?? agentPaths[0] ?? null,
|
|
121
|
+
lastStartAt: Date.now(),
|
|
122
|
+
...accountRuntime.buildRuntimeStatus(),
|
|
123
|
+
});
|
|
124
|
+
ctx.log?.info(`[${account.accountId}] runtime status health=${accountRuntime.buildRuntimeStatus().health} transports=${(accountRuntime.buildRuntimeStatus().transportSessions ?? []).join(" | ") || "none"}`);
|
|
125
|
+
await waitForAbortSignal(ctx.abortSignal);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
botService.stop();
|
|
129
|
+
agentIngress.stop();
|
|
130
|
+
accountRouteRegistry.delete(account.accountId);
|
|
131
|
+
unregisterAccountRuntime(account.accountId);
|
|
132
|
+
ctx.setStatus({
|
|
133
|
+
running: false,
|
|
134
|
+
lastStopAt: Date.now(),
|
|
135
|
+
...accountRuntime.buildRuntimeStatus(),
|
|
136
|
+
});
|
|
137
|
+
ctx.log?.info(`[${account.accountId}] wecom runtime stopped`);
|
|
138
|
+
}
|
|
139
|
+
}
|
package/dist/src/http.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { ProxyAgent, fetch as undiciFetch } from "undici";
|
|
2
|
+
const proxyDispatchers = new Map();
|
|
3
|
+
/**
|
|
4
|
+
* **getProxyDispatcher (获取代理 Dispatcher)**
|
|
5
|
+
*
|
|
6
|
+
* 缓存并复用 ProxyAgent,避免重复创建连接池。
|
|
7
|
+
*/
|
|
8
|
+
function getProxyDispatcher(proxyUrl) {
|
|
9
|
+
const existing = proxyDispatchers.get(proxyUrl);
|
|
10
|
+
if (existing)
|
|
11
|
+
return existing;
|
|
12
|
+
const created = new ProxyAgent(proxyUrl);
|
|
13
|
+
proxyDispatchers.set(proxyUrl, created);
|
|
14
|
+
return created;
|
|
15
|
+
}
|
|
16
|
+
function summarizeHttpTarget(input) {
|
|
17
|
+
try {
|
|
18
|
+
const url = typeof input === "string" ? new URL(input) : input;
|
|
19
|
+
return `${url.origin}${url.pathname}`;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return String(input);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mergeAbortSignal(params) {
|
|
26
|
+
const signals = [];
|
|
27
|
+
if (params.signal)
|
|
28
|
+
signals.push(params.signal);
|
|
29
|
+
if (params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0) {
|
|
30
|
+
signals.push(AbortSignal.timeout(params.timeoutMs));
|
|
31
|
+
}
|
|
32
|
+
if (!signals.length)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (signals.length === 1)
|
|
35
|
+
return signals[0];
|
|
36
|
+
return AbortSignal.any(signals);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* **wecomFetch (统一 HTTP 请求)**
|
|
40
|
+
*
|
|
41
|
+
* 基于 `undici` 的 fetch 封装,自动处理 ProxyAgent 和 Timeout。
|
|
42
|
+
* 所有对企业微信 API 的调用都应经过此函数。
|
|
43
|
+
*/
|
|
44
|
+
export async function wecomFetch(input, init, opts) {
|
|
45
|
+
const proxyUrl = opts?.proxyUrl?.trim() ?? "";
|
|
46
|
+
const dispatcher = proxyUrl ? getProxyDispatcher(proxyUrl) : undefined;
|
|
47
|
+
const startedAt = Date.now();
|
|
48
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
49
|
+
const target = summarizeHttpTarget(input);
|
|
50
|
+
const initSignal = init?.signal ?? undefined;
|
|
51
|
+
const signal = mergeAbortSignal({ signal: opts?.signal ?? initSignal, timeoutMs: opts?.timeoutMs });
|
|
52
|
+
const headers = new Headers(init?.headers ?? {});
|
|
53
|
+
if (!headers.has("User-Agent")) {
|
|
54
|
+
headers.set("User-Agent", "OpenClaw/2.0 (WeCom-Agent)");
|
|
55
|
+
}
|
|
56
|
+
const nextInit = {
|
|
57
|
+
...(init ?? {}),
|
|
58
|
+
...(signal ? { signal } : {}),
|
|
59
|
+
...(dispatcher ? { dispatcher } : {}),
|
|
60
|
+
headers,
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
console.log(`[wecom-http] request method=${method} target=${target} proxy=${proxyUrl || "none"} timeoutMs=${String(opts?.timeoutMs ?? "none")}`);
|
|
64
|
+
const response = await undiciFetch(input, nextInit);
|
|
65
|
+
console.log(`[wecom-http] response method=${method} target=${target} status=${response.status} durationMs=${Date.now() - startedAt}`);
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
70
|
+
console.error(`[wecom-http] timeout method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`);
|
|
71
|
+
}
|
|
72
|
+
else if (err instanceof Error && err.name === "TypeError" && err.message === "fetch failed") {
|
|
73
|
+
const cause = err.cause;
|
|
74
|
+
console.error(`[wecom-http] fetch failed method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}${cause ? ` cause=${String(cause)}` : ""}`);
|
|
75
|
+
}
|
|
76
|
+
else if (err instanceof Error && err.name === "AbortError") {
|
|
77
|
+
console.error(`[wecom-http] aborted method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`);
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* **readResponseBodyAsBuffer (读取响应 Body)**
|
|
84
|
+
*
|
|
85
|
+
* 将 Response Body 读取为 Buffer,支持最大字节限制以防止内存溢出。
|
|
86
|
+
* 适用于下载媒体文件等场景。
|
|
87
|
+
*/
|
|
88
|
+
export async function readResponseBodyAsBuffer(res, maxBytes) {
|
|
89
|
+
if (!res.body)
|
|
90
|
+
return Buffer.alloc(0);
|
|
91
|
+
const limit = maxBytes && Number.isFinite(maxBytes) && maxBytes > 0 ? maxBytes : undefined;
|
|
92
|
+
const chunks = [];
|
|
93
|
+
let total = 0;
|
|
94
|
+
const reader = res.body.getReader();
|
|
95
|
+
while (true) {
|
|
96
|
+
const { done, value } = await reader.read();
|
|
97
|
+
if (done)
|
|
98
|
+
break;
|
|
99
|
+
if (!value)
|
|
100
|
+
continue;
|
|
101
|
+
total += value.byteLength;
|
|
102
|
+
if (limit && total > limit) {
|
|
103
|
+
try {
|
|
104
|
+
await reader.cancel("body too large");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// ignore
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`response body too large (>${limit} bytes)`);
|
|
110
|
+
}
|
|
111
|
+
chunks.push(value);
|
|
112
|
+
}
|
|
113
|
+
return Buffer.concat(chunks.map((c) => Buffer.from(c)));
|
|
114
|
+
}
|
|
@@ -1,56 +1,49 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { decodeEncodingAESKey, pkcs7Unpad, WECOM_PKCS7_BLOCK_SIZE } from "./crypto.js";
|
|
3
|
-
import { readResponseBodyAsBuffer, wecomFetch
|
|
4
|
-
|
|
5
|
-
export type DecryptedWecomMedia = {
|
|
6
|
-
buffer: Buffer;
|
|
7
|
-
sourceContentType?: string;
|
|
8
|
-
sourceFilename?: string;
|
|
9
|
-
sourceUrl?: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function normalizeMime(contentType?: string | null): string | undefined {
|
|
3
|
+
import { readResponseBodyAsBuffer, wecomFetch } from "./http.js";
|
|
4
|
+
function normalizeMime(contentType) {
|
|
13
5
|
const raw = String(contentType ?? "").trim();
|
|
14
|
-
if (!raw)
|
|
6
|
+
if (!raw)
|
|
7
|
+
return undefined;
|
|
15
8
|
return raw.split(";")[0]?.trim().toLowerCase() || undefined;
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
function extractFilenameFromContentDisposition(disposition?: string | null): string | undefined {
|
|
10
|
+
function extractFilenameFromContentDisposition(disposition) {
|
|
19
11
|
const raw = String(disposition ?? "").trim();
|
|
20
|
-
if (!raw)
|
|
21
|
-
|
|
12
|
+
if (!raw)
|
|
13
|
+
return undefined;
|
|
22
14
|
const star = raw.match(/filename\*\s*=\s*([^;]+)/i);
|
|
23
15
|
if (star?.[1]) {
|
|
24
16
|
const v = star[1].trim().replace(/^UTF-8''/i, "").replace(/^"(.*)"$/, "$1");
|
|
25
17
|
try {
|
|
26
18
|
const decoded = decodeURIComponent(v);
|
|
27
|
-
if (decoded.trim())
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
if (decoded.trim())
|
|
20
|
+
return decoded.trim();
|
|
21
|
+
}
|
|
22
|
+
catch { /* ignore */ }
|
|
23
|
+
if (v.trim())
|
|
24
|
+
return v.trim();
|
|
30
25
|
}
|
|
31
|
-
|
|
32
26
|
const plain = raw.match(/filename\s*=\s*([^;]+)/i);
|
|
33
27
|
if (plain?.[1]) {
|
|
34
28
|
const v = plain[1].trim().replace(/^"(.*)"$/, "$1").trim();
|
|
35
|
-
if (v)
|
|
29
|
+
if (v)
|
|
30
|
+
return v;
|
|
36
31
|
}
|
|
37
32
|
return undefined;
|
|
38
33
|
}
|
|
39
|
-
|
|
40
34
|
/**
|
|
41
35
|
* **decryptWecomMedia (解密企业微信媒体文件)**
|
|
42
|
-
*
|
|
36
|
+
*
|
|
43
37
|
* 简易封装:直接传入 URL 和 AES Key 下载并解密。
|
|
44
38
|
* 企业微信媒体文件使用与消息体相同的 AES-256-CBC 加密,IV 为 AES Key 前16字节。
|
|
45
39
|
* 解密后需移除 PKCS#7 填充。
|
|
46
40
|
*/
|
|
47
|
-
export async function decryptWecomMedia(url
|
|
41
|
+
export async function decryptWecomMedia(url, encodingAESKey, maxBytes) {
|
|
48
42
|
return decryptWecomMediaWithHttp(url, encodingAESKey, { maxBytes });
|
|
49
43
|
}
|
|
50
|
-
|
|
51
44
|
/**
|
|
52
45
|
* **decryptWecomMediaWithHttp (解密企业微信媒体 - 高级)**
|
|
53
|
-
*
|
|
46
|
+
*
|
|
54
47
|
* 支持传递 HTTP 选项(如 Proxy、Timeout)。
|
|
55
48
|
* 流程:
|
|
56
49
|
* 1. 下载加密内容。
|
|
@@ -58,26 +51,17 @@ export async function decryptWecomMedia(url: string, encodingAESKey: string, max
|
|
|
58
51
|
* 3. AES-CBC 解密。
|
|
59
52
|
* 4. PKCS#7 去除填充。
|
|
60
53
|
*/
|
|
61
|
-
export async function decryptWecomMediaWithHttp(
|
|
62
|
-
url: string,
|
|
63
|
-
encodingAESKey: string,
|
|
64
|
-
params?: { maxBytes?: number; http?: WecomHttpOptions },
|
|
65
|
-
): Promise<Buffer> {
|
|
54
|
+
export async function decryptWecomMediaWithHttp(url, encodingAESKey, params) {
|
|
66
55
|
const decrypted = await decryptWecomMediaWithMeta(url, encodingAESKey, params);
|
|
67
56
|
return decrypted.buffer;
|
|
68
57
|
}
|
|
69
|
-
|
|
70
58
|
/**
|
|
71
59
|
* **decryptWecomMediaWithMeta (解密企业微信媒体并返回源信息)**
|
|
72
|
-
*
|
|
60
|
+
*
|
|
73
61
|
* 在返回解密结果的同时,保留下载响应中的元信息(content-type / filename / final url),
|
|
74
62
|
* 供上层更准确地推断文件后缀和 MIME。
|
|
75
63
|
*/
|
|
76
|
-
export async function decryptWecomMediaWithMeta(
|
|
77
|
-
url: string,
|
|
78
|
-
encodingAESKey: string,
|
|
79
|
-
params?: { maxBytes?: number; http?: WecomHttpOptions },
|
|
80
|
-
): Promise<DecryptedWecomMedia> {
|
|
64
|
+
export async function decryptWecomMediaWithMeta(url, encodingAESKey, params) {
|
|
81
65
|
// 1. Download encrypted content
|
|
82
66
|
const res = await wecomFetch(url, undefined, { ...params?.http, timeoutMs: params?.http?.timeoutMs ?? 15_000 });
|
|
83
67
|
if (!res.ok) {
|
|
@@ -87,11 +71,9 @@ export async function decryptWecomMediaWithMeta(
|
|
|
87
71
|
const sourceFilename = extractFilenameFromContentDisposition(res.headers.get("content-disposition"));
|
|
88
72
|
const sourceUrl = res.url || url;
|
|
89
73
|
const encryptedData = await readResponseBodyAsBuffer(res, params?.maxBytes);
|
|
90
|
-
|
|
91
74
|
// 2. Prepare Key and IV
|
|
92
75
|
const aesKey = decodeEncodingAESKey(encodingAESKey);
|
|
93
76
|
const iv = aesKey.subarray(0, 16);
|
|
94
|
-
|
|
95
77
|
// 3. Decrypt
|
|
96
78
|
const decipher = crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
|
|
97
79
|
decipher.setAutoPadding(false); // We handle padding manually
|
|
@@ -99,7 +81,6 @@ export async function decryptWecomMediaWithMeta(
|
|
|
99
81
|
decipher.update(encryptedData),
|
|
100
82
|
decipher.final(),
|
|
101
83
|
]);
|
|
102
|
-
|
|
103
84
|
// 4. Unpad
|
|
104
85
|
// Note: Unlike msg bodies, usually removing PKCS#7 padding is enough for media files.
|
|
105
86
|
// The Python SDK logic: pad_len = decrypted_data[-1]; decrypted_data = decrypted_data[:-pad_len]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LegacyOperationalEventStore } from "../observability/legacy-operational-event-store.js";
|
|
2
|
+
import { ActiveReplyStore } from "../store/active-reply-store.js";
|
|
3
|
+
import { StreamStore } from "../store/stream-batch-store.js";
|
|
4
|
+
import { LIMITS } from "./limits.js";
|
|
5
|
+
export { LIMITS, StreamStore, ActiveReplyStore };
|
|
6
|
+
class MonitorState {
|
|
7
|
+
streamStore = new StreamStore();
|
|
8
|
+
activeReplyStore = new ActiveReplyStore("multi");
|
|
9
|
+
operationalEvents = new LegacyOperationalEventStore();
|
|
10
|
+
pruneInterval;
|
|
11
|
+
startPruning(intervalMs = 60_000) {
|
|
12
|
+
if (this.pruneInterval)
|
|
13
|
+
return;
|
|
14
|
+
this.pruneInterval = setInterval(() => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
this.streamStore.prune(now);
|
|
17
|
+
this.activeReplyStore.prune(now);
|
|
18
|
+
this.operationalEvents.prune(now);
|
|
19
|
+
}, intervalMs);
|
|
20
|
+
}
|
|
21
|
+
stopPruning() {
|
|
22
|
+
if (this.pruneInterval) {
|
|
23
|
+
clearInterval(this.pruneInterval);
|
|
24
|
+
this.pruneInterval = undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export const monitorState = new MonitorState();
|