@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
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { WecomAgentDeliveryService } from "./capability/agent/index.js";
|
|
2
|
+
import { WecomUpstreamAgentDeliveryService } from "./capability/agent/upstream-delivery-service.js";
|
|
3
|
+
import { resolveWecomMergedMediaLocalRoots, resolveWecomMediaMaxBytes, resolveWecomAccount, resolveWecomAccountConflict, resolveWecomAccounts, } from "./config/index.js";
|
|
4
|
+
import { getAccountRuntime, getActiveBotWsReplyHandle, getBotWsPushHandle, getWecomRuntime, } from "./runtime.js";
|
|
5
|
+
import { getPeerUpstreamCorpId } from "./context-store.js";
|
|
6
|
+
import { resolveWecomSourceSnapshot } from "./runtime/source-registry.js";
|
|
7
|
+
import { resolveOutboundMediaAsset } from "./shared/media-asset.js";
|
|
8
|
+
import { resolveScopedWecomTarget } from "./target.js";
|
|
9
|
+
import { toWeComMarkdownV2 } from "./wecom_msg_adapter/markdown_adapter.js";
|
|
10
|
+
import { parseUpstreamAgentSessionTarget, createUpstreamAgentConfig, resolveUpstreamCorpConfig } from "./upstream/index.js";
|
|
11
|
+
function resolveOutboundContext(params) {
|
|
12
|
+
const rawTo = String(params.to ?? "").trim();
|
|
13
|
+
const fallbackAccountId = params.accountId?.trim();
|
|
14
|
+
const scoped = resolveScopedWecomTarget(params.to, fallbackAccountId);
|
|
15
|
+
const scopedAccountId = scoped?.accountId?.trim() || fallbackAccountId;
|
|
16
|
+
const peerId = scoped?.target.touser?.trim() || scoped?.target.chatid?.trim();
|
|
17
|
+
const peerKind = scoped?.target.chatid ? "group" : scoped?.target.touser ? "direct" : undefined;
|
|
18
|
+
const source = scopedAccountId
|
|
19
|
+
? resolveWecomSourceSnapshot({
|
|
20
|
+
accountId: scopedAccountId,
|
|
21
|
+
sessionKey: params.sessionKey,
|
|
22
|
+
peerKind,
|
|
23
|
+
peerId,
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
const peerUpstreamCorpId = scopedAccountId && peerKind === "direct" && peerId
|
|
27
|
+
? getPeerUpstreamCorpId(scopedAccountId, peerId)?.trim()
|
|
28
|
+
: undefined;
|
|
29
|
+
return {
|
|
30
|
+
rawTo,
|
|
31
|
+
explicitAgentTarget: isExplicitAgentTarget(params.to),
|
|
32
|
+
scopedAccountId,
|
|
33
|
+
peerKind,
|
|
34
|
+
peerId,
|
|
35
|
+
source,
|
|
36
|
+
peerUpstreamCorpId,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function logOutboundDecision(params) {
|
|
40
|
+
const resolved = resolveOutboundContext(params);
|
|
41
|
+
const runtimeAccountId = resolved.scopedAccountId || params.accountId?.trim();
|
|
42
|
+
const logger = runtimeAccountId ? getAccountRuntime(runtimeAccountId)?.log.info : undefined;
|
|
43
|
+
logger?.(`[wecom-outbound] ${params.phase} rawTo=${resolved.rawTo || "N/A"} scopedAccount=${resolved.scopedAccountId ?? "N/A"} ` +
|
|
44
|
+
`peer=${resolved.peerKind && resolved.peerId ? `${resolved.peerKind}:${resolved.peerId}` : "N/A"} ` +
|
|
45
|
+
`explicitAgent=${String(resolved.explicitAgentTarget)} source=${resolved.source?.source ?? "none"} ` +
|
|
46
|
+
`sourceUpstreamCorpId=${resolved.source?.upstreamCorpId ?? "none"} peerUpstreamCorpId=${resolved.peerUpstreamCorpId ?? "none"} ` +
|
|
47
|
+
`sessionKey=${params.sessionKey?.trim() || "N/A"} textLen=${String(params.textLen ?? 0)} ` +
|
|
48
|
+
`mediaUrl=${params.mediaUrl ?? "N/A"}${params.extra ? ` ${params.extra}` : ""}`);
|
|
49
|
+
}
|
|
50
|
+
function resolveOutboundAccountOrThrow(params) {
|
|
51
|
+
const resolvedAccounts = resolveWecomAccounts(params.cfg);
|
|
52
|
+
const conflictAccountId = params.accountId?.trim() || resolvedAccounts.defaultAccountId;
|
|
53
|
+
const conflict = resolveWecomAccountConflict({
|
|
54
|
+
cfg: params.cfg,
|
|
55
|
+
accountId: conflictAccountId,
|
|
56
|
+
});
|
|
57
|
+
if (conflict) {
|
|
58
|
+
throw new Error(conflict.message);
|
|
59
|
+
}
|
|
60
|
+
const requestedAccountId = params.accountId?.trim();
|
|
61
|
+
if (requestedAccountId) {
|
|
62
|
+
if (!resolvedAccounts.accounts[requestedAccountId]) {
|
|
63
|
+
throw new Error(`WeCom outbound account "${requestedAccountId}" not found. Configure channels.wecom.accounts.${requestedAccountId} or use an existing accountId.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return resolveWecomAccount({
|
|
67
|
+
cfg: params.cfg,
|
|
68
|
+
accountId: params.accountId,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function resolveAgentConfigOrThrow(params) {
|
|
72
|
+
const account = resolveOutboundAccountOrThrow(params).agent;
|
|
73
|
+
if (!account?.apiConfigured) {
|
|
74
|
+
throw new Error(`WeCom outbound requires Agent mode for account=${params.accountId ?? "default"}. Configure channels.wecom.accounts.<accountId>.agent (or legacy channels.wecom.agent).`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof account.agentId !== "number" || !Number.isFinite(account.agentId)) {
|
|
77
|
+
throw new Error(`WeCom outbound requires channels.wecom.accounts.<accountId>.agent.agentId (or legacy channels.wecom.agent.agentId) for account=${params.accountId ?? account.accountId}.`);
|
|
78
|
+
}
|
|
79
|
+
// 注意:不要在日志里输出 corpSecret 等敏感信息
|
|
80
|
+
getAccountRuntime(account.accountId)?.log.info?.(`[wecom-outbound] Using agent config: accountId=${account.accountId}, corpId=${account.corpId}, agentId=${account.agentId}`);
|
|
81
|
+
return account;
|
|
82
|
+
}
|
|
83
|
+
function isExplicitAgentTarget(raw) {
|
|
84
|
+
return /^wecom-agent(?:-upstream)?:/i.test(String(raw ?? "").trim());
|
|
85
|
+
}
|
|
86
|
+
function isAgentConversationTarget(params) {
|
|
87
|
+
if (isExplicitAgentTarget(params.to)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const fallbackAccountId = params.accountId?.trim();
|
|
91
|
+
const scoped = resolveScopedWecomTarget(params.to, fallbackAccountId);
|
|
92
|
+
const resolvedAccountId = scoped?.accountId?.trim() || fallbackAccountId;
|
|
93
|
+
if (!resolvedAccountId) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const peerId = scoped?.target.touser?.trim() || scoped?.target.chatid?.trim();
|
|
97
|
+
const peerKind = scoped?.target.chatid ? "group" : scoped?.target.touser ? "direct" : undefined;
|
|
98
|
+
const source = resolveWecomSourceSnapshot({
|
|
99
|
+
accountId: resolvedAccountId,
|
|
100
|
+
sessionKey: params.sessionKey,
|
|
101
|
+
peerKind,
|
|
102
|
+
peerId,
|
|
103
|
+
});
|
|
104
|
+
return source?.source === "agent-callback";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 解析上下游目标,返回解析后的信息或 undefined
|
|
108
|
+
*/
|
|
109
|
+
function resolveUpstreamTarget(params) {
|
|
110
|
+
const parsedExplicit = parseUpstreamAgentSessionTarget(params.to ?? "");
|
|
111
|
+
const isExplicitUpstreamTarget = Boolean(parsedExplicit);
|
|
112
|
+
const parsed = (() => {
|
|
113
|
+
if (parsedExplicit) {
|
|
114
|
+
return parsedExplicit;
|
|
115
|
+
}
|
|
116
|
+
const fallbackAccountId = params.accountId?.trim();
|
|
117
|
+
const scoped = resolveScopedWecomTarget(params.to, fallbackAccountId);
|
|
118
|
+
const toUser = scoped?.target.touser?.trim();
|
|
119
|
+
const resolvedAccountId = scoped?.accountId?.trim() || fallbackAccountId;
|
|
120
|
+
if (!toUser || !resolvedAccountId) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const source = resolveWecomSourceSnapshot({
|
|
124
|
+
accountId: resolvedAccountId,
|
|
125
|
+
sessionKey: params.sessionKey,
|
|
126
|
+
peerKind: "direct",
|
|
127
|
+
peerId: toUser,
|
|
128
|
+
});
|
|
129
|
+
const upstreamCorpId = source?.upstreamCorpId?.trim() || getPeerUpstreamCorpId(resolvedAccountId, toUser)?.trim();
|
|
130
|
+
if (!upstreamCorpId) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
accountId: resolvedAccountId,
|
|
135
|
+
upstreamCorpId,
|
|
136
|
+
userId: toUser,
|
|
137
|
+
};
|
|
138
|
+
})();
|
|
139
|
+
if (!parsed) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
const { accountId, upstreamCorpId, userId } = parsed;
|
|
143
|
+
const account = resolveOutboundAccountOrThrow({ cfg: params.cfg, accountId });
|
|
144
|
+
if (!account.agent?.apiConfigured) {
|
|
145
|
+
if (isExplicitUpstreamTarget) {
|
|
146
|
+
throw new Error(`WeCom upstream outbound requires Agent mode for account=${accountId}.`);
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
// 查找上下游配置
|
|
151
|
+
const upstreamConfig = resolveUpstreamCorpConfig({
|
|
152
|
+
upstreamCorpId,
|
|
153
|
+
upstreamCorps: account.agent.config.upstreamCorps,
|
|
154
|
+
});
|
|
155
|
+
if (!upstreamConfig) {
|
|
156
|
+
if (isExplicitUpstreamTarget) {
|
|
157
|
+
throw new Error(`WeCom upstream outbound: no upstream corp config found for corpId=${upstreamCorpId}. ` +
|
|
158
|
+
`Please configure channels.wecom.accounts.${accountId}.agent.upstreamCorps with corpId=${upstreamCorpId}.`);
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
// 创建上下游 Agent 配置
|
|
163
|
+
// 注意:使用下游企业的 corpId 和 agentId,但保持主企业的 corpSecret
|
|
164
|
+
const upstreamAgent = createUpstreamAgentConfig({
|
|
165
|
+
baseAgent: account.agent,
|
|
166
|
+
upstreamCorpId,
|
|
167
|
+
upstreamAgentId: upstreamConfig.agentId,
|
|
168
|
+
});
|
|
169
|
+
return { upstreamAgent, primaryAgent: account.agent, toUser: userId };
|
|
170
|
+
}
|
|
171
|
+
function resolveBotWsChatTarget(params) {
|
|
172
|
+
const scoped = resolveScopedWecomTarget(params.to, params.accountId);
|
|
173
|
+
if (!scoped) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (scoped.accountId && scoped.accountId !== params.accountId) {
|
|
177
|
+
throw new Error(`WeCom outbound account mismatch: target belongs to account=${scoped.accountId}, current account=${params.accountId}.`);
|
|
178
|
+
}
|
|
179
|
+
if (scoped.target.chatid) {
|
|
180
|
+
return scoped.target.chatid;
|
|
181
|
+
}
|
|
182
|
+
if (scoped.target.touser) {
|
|
183
|
+
return scoped.target.touser;
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
function resolveOutboundPeer(params) {
|
|
188
|
+
const scoped = resolveScopedWecomTarget(params.to, params.accountId);
|
|
189
|
+
if (!scoped) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
if (scoped.accountId && scoped.accountId !== params.accountId) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
if (scoped.target.chatid) {
|
|
196
|
+
return { peerKind: "group", peerId: scoped.target.chatid };
|
|
197
|
+
}
|
|
198
|
+
if (scoped.target.touser) {
|
|
199
|
+
return { peerKind: "direct", peerId: scoped.target.touser };
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
function shouldPreferBotWsOutbound(params) {
|
|
204
|
+
const account = resolveOutboundAccountOrThrow({
|
|
205
|
+
cfg: params.cfg,
|
|
206
|
+
accountId: params.accountId,
|
|
207
|
+
});
|
|
208
|
+
const peer = resolveOutboundPeer({
|
|
209
|
+
to: params.to,
|
|
210
|
+
accountId: account.accountId,
|
|
211
|
+
});
|
|
212
|
+
const source = resolveWecomSourceSnapshot({
|
|
213
|
+
accountId: account.accountId,
|
|
214
|
+
sessionKey: params.sessionKey,
|
|
215
|
+
peerKind: peer?.peerKind,
|
|
216
|
+
peerId: peer?.peerId,
|
|
217
|
+
});
|
|
218
|
+
const pinnedToAgent = source?.source === "agent-callback";
|
|
219
|
+
const pinnedToBotWs = source?.source === "bot-ws";
|
|
220
|
+
return {
|
|
221
|
+
preferred: !isExplicitAgentTarget(params.to) &&
|
|
222
|
+
!pinnedToAgent &&
|
|
223
|
+
Boolean(account.bot?.configured &&
|
|
224
|
+
account.bot.wsConfigured &&
|
|
225
|
+
(pinnedToBotWs || account.bot.primaryTransport === "ws")),
|
|
226
|
+
accountId: account.accountId,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function markActiveBotWsReplyHandleActivity(params) {
|
|
230
|
+
const peer = resolveOutboundPeer({
|
|
231
|
+
to: params.to,
|
|
232
|
+
accountId: params.accountId,
|
|
233
|
+
});
|
|
234
|
+
const handle = getActiveBotWsReplyHandle({
|
|
235
|
+
accountId: params.accountId,
|
|
236
|
+
sessionKey: params.sessionKey,
|
|
237
|
+
peerKind: peer?.peerKind,
|
|
238
|
+
peerId: peer?.peerId,
|
|
239
|
+
});
|
|
240
|
+
handle?.markExternalActivity?.();
|
|
241
|
+
}
|
|
242
|
+
async function sendTextViaBotWs(params) {
|
|
243
|
+
const { preferred, accountId } = shouldPreferBotWsOutbound(params);
|
|
244
|
+
if (!preferred) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
const chatId = resolveBotWsChatTarget({
|
|
248
|
+
to: params.to,
|
|
249
|
+
accountId,
|
|
250
|
+
});
|
|
251
|
+
if (!chatId) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
const handle = getBotWsPushHandle(accountId);
|
|
255
|
+
if (!handle) {
|
|
256
|
+
throw new Error(`WeCom outbound account=${accountId} is configured for Bot WS active push, but no live WS runtime is registered.`);
|
|
257
|
+
}
|
|
258
|
+
if (!handle.isConnected()) {
|
|
259
|
+
throw new Error(`WeCom outbound account=${accountId} is configured for Bot WS active push, but the WS transport is not connected.`);
|
|
260
|
+
}
|
|
261
|
+
const markdownText = toWeComMarkdownV2(params.text);
|
|
262
|
+
console.log(`[wecom-outbound] Sending Bot WS active message to target=${String(params.to ?? "")} chatId=${chatId} (len=${markdownText.length})`);
|
|
263
|
+
await handle.sendMarkdown(chatId, markdownText);
|
|
264
|
+
markActiveBotWsReplyHandleActivity({
|
|
265
|
+
accountId,
|
|
266
|
+
sessionKey: params.sessionKey,
|
|
267
|
+
to: params.to,
|
|
268
|
+
});
|
|
269
|
+
console.log(`[wecom-outbound] Successfully sent Bot WS active message to ${chatId}`);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
async function sendMediaViaBotWs(params) {
|
|
273
|
+
const { preferred, accountId } = shouldPreferBotWsOutbound(params);
|
|
274
|
+
if (!preferred) {
|
|
275
|
+
return { attempted: false, sent: false };
|
|
276
|
+
}
|
|
277
|
+
const chatId = resolveBotWsChatTarget({
|
|
278
|
+
to: params.to,
|
|
279
|
+
accountId,
|
|
280
|
+
});
|
|
281
|
+
if (!chatId) {
|
|
282
|
+
return { attempted: false, sent: false };
|
|
283
|
+
}
|
|
284
|
+
const handle = getBotWsPushHandle(accountId);
|
|
285
|
+
if (!handle) {
|
|
286
|
+
throw new Error(`WeCom outbound account=${accountId} is configured for Bot WS active push, but no live WS runtime is registered.`);
|
|
287
|
+
}
|
|
288
|
+
if (!handle.isConnected()) {
|
|
289
|
+
throw new Error(`WeCom outbound account=${accountId} is configured for Bot WS active push, but the WS transport is not connected.`);
|
|
290
|
+
}
|
|
291
|
+
console.log(`[wecom-outbound] Sending Bot WS media to target=${String(params.to ?? "")} chatId=${chatId} media=${params.mediaUrl}`);
|
|
292
|
+
const effectiveMediaLocalRoots = resolveWecomMergedMediaLocalRoots({
|
|
293
|
+
cfg: params.cfg,
|
|
294
|
+
baseRoots: params.mediaLocalRoots,
|
|
295
|
+
});
|
|
296
|
+
const result = await handle.sendMedia({
|
|
297
|
+
chatId,
|
|
298
|
+
mediaUrl: params.mediaUrl,
|
|
299
|
+
text: params.text,
|
|
300
|
+
mediaLocalRoots: effectiveMediaLocalRoots,
|
|
301
|
+
maxBytes: resolveWecomMediaMaxBytes(params.cfg, accountId),
|
|
302
|
+
});
|
|
303
|
+
if (result.ok) {
|
|
304
|
+
markActiveBotWsReplyHandleActivity({
|
|
305
|
+
accountId,
|
|
306
|
+
sessionKey: params.sessionKey,
|
|
307
|
+
to: params.to,
|
|
308
|
+
});
|
|
309
|
+
console.log(`[wecom-outbound] Successfully sent Bot WS media to ${chatId}`);
|
|
310
|
+
return { attempted: true, sent: true };
|
|
311
|
+
}
|
|
312
|
+
const reason = result.rejectReason || result.error || "unknown";
|
|
313
|
+
console.warn(`[wecom-outbound] Bot WS media failed for ${chatId}: ${reason}`);
|
|
314
|
+
return { attempted: true, sent: false, reason };
|
|
315
|
+
}
|
|
316
|
+
export const wecomOutbound = {
|
|
317
|
+
deliveryMode: "direct",
|
|
318
|
+
chunkerMode: "text",
|
|
319
|
+
textChunkLimit: 20480,
|
|
320
|
+
chunker: (text, limit) => {
|
|
321
|
+
try {
|
|
322
|
+
return getWecomRuntime().channel.text.chunkText(text, limit);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return [text];
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
sendText: async ({ cfg, to, text, accountId, sessionKey }) => {
|
|
329
|
+
// signal removed - not supported in current SDK
|
|
330
|
+
// Defer Agent resolution until the Agent fallback path
|
|
331
|
+
// sendTextViaBotWs() can already deliver without Agent mode
|
|
332
|
+
// 体验优化:/new /reset 的“New session started”回执在 OpenClaw 核心里是英文固定文案,
|
|
333
|
+
// 且通过 routeReply 走 wecom outbound(Agent 主动发送)。
|
|
334
|
+
// 在 WeCom“双模式”场景下,这会造成:
|
|
335
|
+
// - 用户在 Bot 会话发 /new,但却收到一条 Agent 私信回执(双重回复/错会话)。
|
|
336
|
+
// 因此:
|
|
337
|
+
// - Bot 会话目标:抑制该回执(Bot 会话里由 wecom 插件补中文回执)。
|
|
338
|
+
// - Agent 会话目标(wecom-agent:):允许发送,但改写成中文。
|
|
339
|
+
let outgoingText = text;
|
|
340
|
+
const trimmed = String(outgoingText ?? "").trim();
|
|
341
|
+
logOutboundDecision({
|
|
342
|
+
phase: "sendText:start",
|
|
343
|
+
to,
|
|
344
|
+
accountId,
|
|
345
|
+
sessionKey,
|
|
346
|
+
textLen: trimmed.length,
|
|
347
|
+
});
|
|
348
|
+
const isAgentSessionTarget = isAgentConversationTarget({ to, accountId, sessionKey });
|
|
349
|
+
const looksLikeNewSessionAck = /new session started/i.test(trimmed) && /model:/i.test(trimmed);
|
|
350
|
+
if (looksLikeNewSessionAck) {
|
|
351
|
+
if (!isAgentSessionTarget) {
|
|
352
|
+
logOutboundDecision({
|
|
353
|
+
phase: "sendText:suppress-new-session-ack",
|
|
354
|
+
to,
|
|
355
|
+
accountId,
|
|
356
|
+
sessionKey,
|
|
357
|
+
textLen: trimmed.length,
|
|
358
|
+
});
|
|
359
|
+
// Suppress ack without agent resolution
|
|
360
|
+
return { channel: "wecom", messageId: `suppressed-${Date.now()}`, timestamp: Date.now() };
|
|
361
|
+
}
|
|
362
|
+
const modelLabel = (() => {
|
|
363
|
+
const m = trimmed.match(/model:\s*([^\n()]+)\s*/i);
|
|
364
|
+
return m?.[1]?.trim();
|
|
365
|
+
})();
|
|
366
|
+
const rewritten = modelLabel ? `✅ 已开启新会话(模型:${modelLabel})` : "✅ 已开启新会话。";
|
|
367
|
+
outgoingText = rewritten;
|
|
368
|
+
logOutboundDecision({
|
|
369
|
+
phase: "sendText:rewrite-new-session-ack",
|
|
370
|
+
to,
|
|
371
|
+
accountId,
|
|
372
|
+
sessionKey,
|
|
373
|
+
textLen: outgoingText.length,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
let sentViaBotWs = false;
|
|
377
|
+
let agent = null;
|
|
378
|
+
let upstreamTarget;
|
|
379
|
+
try {
|
|
380
|
+
// 首先检查是否是上下游用户
|
|
381
|
+
upstreamTarget = resolveUpstreamTarget({ to, cfg, accountId, sessionKey });
|
|
382
|
+
if (upstreamTarget) {
|
|
383
|
+
logOutboundDecision({
|
|
384
|
+
phase: "sendText:path-upstream",
|
|
385
|
+
to,
|
|
386
|
+
accountId,
|
|
387
|
+
sessionKey,
|
|
388
|
+
textLen: outgoingText.length,
|
|
389
|
+
extra: `resolvedUser=${upstreamTarget.toUser} corpId=${upstreamTarget.upstreamAgent.corpId}`,
|
|
390
|
+
});
|
|
391
|
+
// 上下游用户使用专门的 DeliveryService 发送
|
|
392
|
+
getAccountRuntime(upstreamTarget.upstreamAgent.accountId)?.log.info?.(`[wecom-outbound] Sending text to upstream target corpId=${upstreamTarget.upstreamAgent.corpId} (len=${outgoingText.length})`);
|
|
393
|
+
const deliveryService = new WecomUpstreamAgentDeliveryService(upstreamTarget.upstreamAgent, upstreamTarget.primaryAgent);
|
|
394
|
+
await deliveryService.sendText({
|
|
395
|
+
to,
|
|
396
|
+
text: outgoingText,
|
|
397
|
+
});
|
|
398
|
+
return {
|
|
399
|
+
channel: "wecom",
|
|
400
|
+
messageId: `upstream-agent-${Date.now()}`,
|
|
401
|
+
timestamp: Date.now(),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
sentViaBotWs = await sendTextViaBotWs({
|
|
405
|
+
cfg,
|
|
406
|
+
accountId,
|
|
407
|
+
to,
|
|
408
|
+
text: outgoingText,
|
|
409
|
+
sessionKey,
|
|
410
|
+
});
|
|
411
|
+
if (!sentViaBotWs) {
|
|
412
|
+
// Defer Agent resolution until needed for fallback
|
|
413
|
+
agent = resolveAgentConfigOrThrow({ cfg, accountId });
|
|
414
|
+
logOutboundDecision({
|
|
415
|
+
phase: "sendText:path-agent",
|
|
416
|
+
to,
|
|
417
|
+
accountId: agent.accountId,
|
|
418
|
+
sessionKey,
|
|
419
|
+
textLen: outgoingText.length,
|
|
420
|
+
});
|
|
421
|
+
getAccountRuntime(agent.accountId)?.log.info?.(`[wecom-outbound] Sending text to target=${String(to ?? "")} (len=${outgoingText.length})`);
|
|
422
|
+
const deliveryService = new WecomAgentDeliveryService(agent);
|
|
423
|
+
await deliveryService.sendText({
|
|
424
|
+
to,
|
|
425
|
+
text: outgoingText,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
logOutboundDecision({
|
|
430
|
+
phase: "sendText:path-bot-ws",
|
|
431
|
+
to,
|
|
432
|
+
accountId,
|
|
433
|
+
sessionKey,
|
|
434
|
+
textLen: outgoingText.length,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
console.error(`[wecom-outbound] FAILED to send: ${err instanceof Error ? err.message : String(err)}`);
|
|
440
|
+
if (agent) {
|
|
441
|
+
getAccountRuntime(agent.accountId)?.log.error?.(`[wecom-outbound] Failed to send text to ${String(to ?? "")}: ${err instanceof Error ? err.message : String(err)}`);
|
|
442
|
+
}
|
|
443
|
+
throw err;
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
channel: "wecom",
|
|
447
|
+
messageId: `${sentViaBotWs ? "bot-ws" : "agent"}-${Date.now()}`,
|
|
448
|
+
timestamp: Date.now(),
|
|
449
|
+
};
|
|
450
|
+
},
|
|
451
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, accountId, mediaLocalRoots, sessionKey, }) => {
|
|
452
|
+
// signal removed - not supported in current SDK
|
|
453
|
+
if (!mediaUrl) {
|
|
454
|
+
throw new Error("WeCom outbound requires mediaUrl.");
|
|
455
|
+
}
|
|
456
|
+
logOutboundDecision({
|
|
457
|
+
phase: "sendMedia:start",
|
|
458
|
+
to,
|
|
459
|
+
accountId,
|
|
460
|
+
sessionKey,
|
|
461
|
+
textLen: String(text ?? "").trim().length,
|
|
462
|
+
mediaUrl,
|
|
463
|
+
});
|
|
464
|
+
// 首先检查是否是上下游用户
|
|
465
|
+
const upstreamTarget = resolveUpstreamTarget({ to, cfg, accountId, sessionKey });
|
|
466
|
+
if (upstreamTarget) {
|
|
467
|
+
logOutboundDecision({
|
|
468
|
+
phase: "sendMedia:path-upstream",
|
|
469
|
+
to,
|
|
470
|
+
accountId,
|
|
471
|
+
sessionKey,
|
|
472
|
+
textLen: String(text ?? "").trim().length,
|
|
473
|
+
mediaUrl,
|
|
474
|
+
extra: `resolvedUser=${upstreamTarget.toUser} corpId=${upstreamTarget.upstreamAgent.corpId}`,
|
|
475
|
+
});
|
|
476
|
+
getAccountRuntime(upstreamTarget.upstreamAgent.accountId)?.log.info?.(`[wecom-outbound] Sending media to upstream target corpId=${upstreamTarget.upstreamAgent.corpId} (filename=${mediaUrl})`);
|
|
477
|
+
const { buffer, contentType, filename } = await resolveOutboundMediaAsset({
|
|
478
|
+
mediaUrl,
|
|
479
|
+
network: upstreamTarget.upstreamAgent.network,
|
|
480
|
+
});
|
|
481
|
+
const deliveryService = new WecomUpstreamAgentDeliveryService(upstreamTarget.upstreamAgent, upstreamTarget.primaryAgent);
|
|
482
|
+
await deliveryService.sendMedia({
|
|
483
|
+
to,
|
|
484
|
+
text,
|
|
485
|
+
buffer,
|
|
486
|
+
filename,
|
|
487
|
+
contentType,
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
channel: "wecom",
|
|
491
|
+
messageId: `upstream-agent-media-${Date.now()}`,
|
|
492
|
+
timestamp: Date.now(),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const botWs = await sendMediaViaBotWs({
|
|
496
|
+
cfg,
|
|
497
|
+
accountId,
|
|
498
|
+
to,
|
|
499
|
+
text,
|
|
500
|
+
mediaUrl,
|
|
501
|
+
mediaLocalRoots,
|
|
502
|
+
sessionKey,
|
|
503
|
+
});
|
|
504
|
+
if (botWs.sent) {
|
|
505
|
+
logOutboundDecision({
|
|
506
|
+
phase: "sendMedia:path-bot-ws",
|
|
507
|
+
to,
|
|
508
|
+
accountId,
|
|
509
|
+
sessionKey,
|
|
510
|
+
textLen: String(text ?? "").trim().length,
|
|
511
|
+
mediaUrl,
|
|
512
|
+
});
|
|
513
|
+
return {
|
|
514
|
+
channel: "wecom",
|
|
515
|
+
messageId: `bot-ws-media-${Date.now()}`,
|
|
516
|
+
timestamp: Date.now(),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (botWs.attempted) {
|
|
520
|
+
throw new Error(`WeCom Bot WS media delivery failed for ${String(to ?? "")}: ${botWs.reason ?? "unknown"}`);
|
|
521
|
+
}
|
|
522
|
+
const agent = resolveAgentConfigOrThrow({ cfg, accountId });
|
|
523
|
+
logOutboundDecision({
|
|
524
|
+
phase: "sendMedia:path-agent",
|
|
525
|
+
to,
|
|
526
|
+
accountId: agent.accountId,
|
|
527
|
+
sessionKey,
|
|
528
|
+
textLen: String(text ?? "").trim().length,
|
|
529
|
+
mediaUrl,
|
|
530
|
+
});
|
|
531
|
+
const deliveryService = new WecomAgentDeliveryService(agent);
|
|
532
|
+
const { buffer, contentType, filename } = await resolveOutboundMediaAsset({
|
|
533
|
+
mediaUrl,
|
|
534
|
+
network: agent.network,
|
|
535
|
+
});
|
|
536
|
+
console.log(`[wecom-outbound] Sending media to ${String(to ?? "")} (filename=${filename}, contentType=${contentType})`);
|
|
537
|
+
try {
|
|
538
|
+
await deliveryService.sendMedia({
|
|
539
|
+
to,
|
|
540
|
+
text,
|
|
541
|
+
buffer,
|
|
542
|
+
filename,
|
|
543
|
+
contentType,
|
|
544
|
+
});
|
|
545
|
+
console.log(`[wecom-outbound] Successfully sent media to ${String(to ?? "")}`);
|
|
546
|
+
}
|
|
547
|
+
catch (err) {
|
|
548
|
+
console.error(`[wecom-outbound] Failed to send media to ${String(to ?? "")}:`, err);
|
|
549
|
+
throw err;
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
channel: "wecom",
|
|
553
|
+
messageId: `${botWs.attempted ? "agent-fallback-media" : "agent-media"}-${Date.now()}`,
|
|
554
|
+
timestamp: Date.now(),
|
|
555
|
+
};
|
|
556
|
+
},
|
|
557
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { prepareInboundSession } from "./session-manager.js";
|
|
2
|
+
import { dispatchRuntimeReply } from "./reply-orchestrator.js";
|
|
3
|
+
import { buildRawEnvelopeSummary } from "../observability/raw-envelope-log.js";
|
|
4
|
+
import { registerActiveBotWsReplyHandle, unregisterActiveBotWsReplyHandle } from "../runtime.js";
|
|
5
|
+
export async function dispatchInboundEvent(params) {
|
|
6
|
+
const { core, cfg, store, auditLog, mediaService, event, replyHandle } = params;
|
|
7
|
+
if (!store.markInboundSeen(event)) {
|
|
8
|
+
auditLog.appendOperational({
|
|
9
|
+
accountId: event.accountId,
|
|
10
|
+
transport: event.transport,
|
|
11
|
+
category: "duplicate-inbound",
|
|
12
|
+
messageId: event.messageId,
|
|
13
|
+
summary: buildRawEnvelopeSummary(event),
|
|
14
|
+
raw: event.raw,
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
auditLog.appendInbound({
|
|
19
|
+
accountId: event.accountId,
|
|
20
|
+
transport: event.transport,
|
|
21
|
+
messageId: event.messageId,
|
|
22
|
+
summary: buildRawEnvelopeSummary(event),
|
|
23
|
+
raw: event.raw,
|
|
24
|
+
});
|
|
25
|
+
store.writeReplyContext(event.messageId, event.replyContext);
|
|
26
|
+
const session = await prepareInboundSession({
|
|
27
|
+
core,
|
|
28
|
+
cfg,
|
|
29
|
+
event,
|
|
30
|
+
mediaService,
|
|
31
|
+
});
|
|
32
|
+
const sessionKey = session.ctx.SessionKey ?? session.route.sessionKey;
|
|
33
|
+
registerActiveBotWsReplyHandle({
|
|
34
|
+
accountId: event.accountId,
|
|
35
|
+
sessionKey,
|
|
36
|
+
peerKind: event.conversation.peerKind,
|
|
37
|
+
peerId: event.conversation.peerId,
|
|
38
|
+
handle: replyHandle,
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
await dispatchRuntimeReply({
|
|
42
|
+
core,
|
|
43
|
+
cfg,
|
|
44
|
+
session,
|
|
45
|
+
replyHandle,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
unregisterActiveBotWsReplyHandle({
|
|
50
|
+
accountId: event.accountId,
|
|
51
|
+
sessionKey,
|
|
52
|
+
peerKind: event.conversation.peerKind,
|
|
53
|
+
peerId: event.conversation.peerId,
|
|
54
|
+
handle: replyHandle,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export async function dispatchReplyPayload(params) {
|
|
2
|
+
await params.replyHandle.deliver(params.payload, { kind: params.kind });
|
|
3
|
+
}
|
|
4
|
+
export async function dispatchRuntimeReply(params) {
|
|
5
|
+
const { core, cfg, session, replyHandle } = params;
|
|
6
|
+
const result = await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
7
|
+
ctx: session.ctx,
|
|
8
|
+
cfg,
|
|
9
|
+
replyOptions: replyHandle.context.transport === "bot-ws"
|
|
10
|
+
? {
|
|
11
|
+
// WS bot replies should emit block updates instead of waiting for a final-only flush.
|
|
12
|
+
disableBlockStreaming: false,
|
|
13
|
+
}
|
|
14
|
+
: undefined,
|
|
15
|
+
dispatcherOptions: {
|
|
16
|
+
deliver: async (payload, info) => {
|
|
17
|
+
await dispatchReplyPayload({
|
|
18
|
+
replyHandle,
|
|
19
|
+
payload,
|
|
20
|
+
kind: info?.kind === "final" ? "final" : "block",
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
onError: async (error) => {
|
|
24
|
+
await replyHandle.fail?.(error);
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (replyHandle.context.transport === "bot-ws" &&
|
|
29
|
+
result &&
|
|
30
|
+
result.queuedFinal !== true &&
|
|
31
|
+
(result.counts?.block ?? 0) > 0) {
|
|
32
|
+
await dispatchReplyPayload({
|
|
33
|
+
replyHandle,
|
|
34
|
+
payload: { text: "" },
|
|
35
|
+
kind: "final",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ensureDynamicAgentListed, generateAgentId, shouldUseDynamicAgent, } from "../dynamic-agent.js";
|
|
2
|
+
export function resolveRuntimeRoute(params) {
|
|
3
|
+
const route = params.core.channel.routing.resolveAgentRoute({
|
|
4
|
+
cfg: params.cfg,
|
|
5
|
+
channel: "wecom",
|
|
6
|
+
accountId: params.event.accountId,
|
|
7
|
+
peer: {
|
|
8
|
+
kind: params.event.conversation.peerKind,
|
|
9
|
+
id: params.event.conversation.peerId,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
const chatType = params.event.conversation.peerKind === "group" ? "group" : "dm";
|
|
13
|
+
const useDynamicAgent = shouldUseDynamicAgent({
|
|
14
|
+
chatType,
|
|
15
|
+
senderId: params.event.conversation.senderId,
|
|
16
|
+
config: params.cfg,
|
|
17
|
+
});
|
|
18
|
+
if (!useDynamicAgent) {
|
|
19
|
+
return route;
|
|
20
|
+
}
|
|
21
|
+
const targetAgentId = generateAgentId(chatType, params.event.conversation.peerId, params.event.accountId);
|
|
22
|
+
route.agentId = targetAgentId;
|
|
23
|
+
route.sessionKey = `agent:${targetAgentId}:wecom:${params.event.accountId}:${chatType}:${params.event.conversation.peerId}`;
|
|
24
|
+
ensureDynamicAgentListed(targetAgentId, params.core).catch(() => { });
|
|
25
|
+
return route;
|
|
26
|
+
}
|