@yanhaidao/wecom 2.4.160 → 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/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/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/changelog/v2.4.16.md +0 -19
- package/compat-single-account.md +0 -148
- package/index.test.ts +0 -38
- package/scripts/test-proxy.ts +0 -70
- package/src/accounts.ts +0 -34
- package/src/agent/api-client.upload.test.ts +0 -109
- package/src/agent/handler.event-filter.test.ts +0 -100
- package/src/agent/handler.ts +0 -1105
- package/src/agent/index.ts +0 -12
- 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 -147
- 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 -296
- package/src/config/derived-paths.test.ts +0 -111
- package/src/config/derived-paths.ts +0 -41
- package/src/config/index.ts +0 -26
- package/src/config/media.test.ts +0 -113
- package/src/config/media.ts +0 -139
- package/src/config/network.ts +0 -53
- 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 -90
- 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 -145
- 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.test.ts +0 -433
- package/src/transport/bot-webhook/inbound-normalizer.ts +0 -558
- package/src/transport/bot-webhook/inbound.ts +0 -5
- package/src/transport/bot-webhook/message-shape.ts +0 -92
- 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 -290
- package/src/transport/bot-ws/inbound.ts +0 -163
- 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 -70
- package/src/types/config.ts +0 -114
- 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 -189
- 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,112 @@
|
|
|
1
|
+
import { getPeerContextToken } from "../context-store.js";
|
|
2
|
+
import { buildWecomContextTarget } from "../target.js";
|
|
3
|
+
import { resolveRuntimeRoute } from "./routing-bridge.js";
|
|
4
|
+
import { registerWecomSourceSnapshot } from "./source-registry.js";
|
|
5
|
+
function readContextSessionId(ctx) {
|
|
6
|
+
const sessionId = "SessionId" in ctx ? ctx.SessionId : undefined;
|
|
7
|
+
return typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : undefined;
|
|
8
|
+
}
|
|
9
|
+
export async function prepareInboundSession(params) {
|
|
10
|
+
const { core, cfg, event, mediaService } = params;
|
|
11
|
+
const route = resolveRuntimeRoute({ core, cfg, event });
|
|
12
|
+
const source = event.transport === "bot-ws"
|
|
13
|
+
? "bot-ws"
|
|
14
|
+
: event.transport === "agent-callback"
|
|
15
|
+
? "agent-callback"
|
|
16
|
+
: undefined;
|
|
17
|
+
if (source) {
|
|
18
|
+
registerWecomSourceSnapshot({
|
|
19
|
+
accountId: event.accountId,
|
|
20
|
+
source,
|
|
21
|
+
messageId: event.messageId,
|
|
22
|
+
sessionKey: route.sessionKey,
|
|
23
|
+
peerKind: event.conversation.peerKind,
|
|
24
|
+
peerId: event.conversation.peerId,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, {
|
|
28
|
+
agentId: route.agentId,
|
|
29
|
+
});
|
|
30
|
+
const previousTimestamp = core.channel.session.readSessionUpdatedAt({
|
|
31
|
+
storePath,
|
|
32
|
+
sessionKey: route.sessionKey,
|
|
33
|
+
});
|
|
34
|
+
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
35
|
+
const body = core.channel.reply.formatAgentEnvelope({
|
|
36
|
+
channel: "WeCom",
|
|
37
|
+
from: `${event.conversation.peerKind}:${event.conversation.peerId}`,
|
|
38
|
+
previousTimestamp,
|
|
39
|
+
envelope: envelopeOptions,
|
|
40
|
+
body: event.text,
|
|
41
|
+
});
|
|
42
|
+
const firstAttachment = await mediaService.normalizeFirstAttachment(event);
|
|
43
|
+
const mediaPath = firstAttachment
|
|
44
|
+
? await mediaService.saveInboundAttachment(event, firstAttachment)
|
|
45
|
+
: undefined;
|
|
46
|
+
const defaultOriginatingTo = event.conversation.peerKind === "group"
|
|
47
|
+
? `wecom:group:${event.conversation.peerId}`
|
|
48
|
+
: `wecom:user:${event.conversation.peerId}`;
|
|
49
|
+
const contextToken = event.transport === "bot-ws"
|
|
50
|
+
? getPeerContextToken(event.accountId, event.conversation.peerId)
|
|
51
|
+
: undefined;
|
|
52
|
+
const originatingTo = contextToken
|
|
53
|
+
? buildWecomContextTarget(contextToken)
|
|
54
|
+
: defaultOriginatingTo;
|
|
55
|
+
const providerContext = event.transport === "bot-ws"
|
|
56
|
+
? {
|
|
57
|
+
// Bot WS inbound turns already have a live reply handle bound to the
|
|
58
|
+
// current req_id. Mark the current surface as WeCom so core final text
|
|
59
|
+
// stays on that handle and replaces the placeholder instead of being
|
|
60
|
+
// re-routed as a second active-push message.
|
|
61
|
+
Provider: "wecom",
|
|
62
|
+
Surface: "wecom",
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
Provider: "wecom",
|
|
66
|
+
};
|
|
67
|
+
const ctx = core.channel.reply.finalizeInboundContext({
|
|
68
|
+
Body: body,
|
|
69
|
+
RawBody: event.text,
|
|
70
|
+
CommandBody: event.text,
|
|
71
|
+
From: event.conversation.peerKind === "group"
|
|
72
|
+
? `wecom:group:${event.conversation.peerId}`
|
|
73
|
+
: `wecom:user:${event.conversation.senderId}`,
|
|
74
|
+
To: event.conversation.peerKind === "group"
|
|
75
|
+
? `wecom:group:${event.conversation.peerId}`
|
|
76
|
+
: `wecom:user:${event.conversation.peerId}`,
|
|
77
|
+
SessionKey: route.sessionKey,
|
|
78
|
+
AccountId: route.accountId,
|
|
79
|
+
ChatType: event.conversation.peerKind,
|
|
80
|
+
ConversationLabel: `${event.conversation.peerKind}:${event.conversation.peerId}`,
|
|
81
|
+
SenderName: event.senderName ?? event.conversation.senderId,
|
|
82
|
+
SenderId: event.conversation.senderId,
|
|
83
|
+
// Keep Originating* populated so explicit route-to-origin flows and message
|
|
84
|
+
// tools can still resolve the active peer context when needed.
|
|
85
|
+
...providerContext,
|
|
86
|
+
OriginatingChannel: "wecom",
|
|
87
|
+
OriginatingTo: originatingTo,
|
|
88
|
+
MessageSid: event.messageId,
|
|
89
|
+
CommandAuthorized: true,
|
|
90
|
+
MediaPath: mediaPath,
|
|
91
|
+
MediaUrl: mediaPath,
|
|
92
|
+
MediaType: firstAttachment?.contentType,
|
|
93
|
+
});
|
|
94
|
+
if (source) {
|
|
95
|
+
registerWecomSourceSnapshot({
|
|
96
|
+
accountId: event.accountId,
|
|
97
|
+
source,
|
|
98
|
+
messageId: event.messageId,
|
|
99
|
+
sessionKey: ctx.SessionKey ?? route.sessionKey,
|
|
100
|
+
sessionId: readContextSessionId(ctx),
|
|
101
|
+
peerKind: event.conversation.peerKind,
|
|
102
|
+
peerId: event.conversation.peerId,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
await core.channel.session.recordInboundSession({
|
|
106
|
+
storePath,
|
|
107
|
+
sessionKey: ctx.SessionKey ?? route.sessionKey,
|
|
108
|
+
ctx,
|
|
109
|
+
onRecordError: () => { },
|
|
110
|
+
});
|
|
111
|
+
return { route, ctx, storePath };
|
|
112
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
const MAX_MESSAGE_FACTS = 2048;
|
|
2
|
+
const MAX_SESSION_SNAPSHOTS = 1024;
|
|
3
|
+
const MAX_CONVERSATION_SNAPSHOTS = 1024;
|
|
4
|
+
const messageFacts = new Map();
|
|
5
|
+
const sessionSnapshotsByAccountKey = new Map();
|
|
6
|
+
const sessionSnapshotsByLooseKey = new Map();
|
|
7
|
+
const conversationSnapshotsByAccountKey = new Map();
|
|
8
|
+
const conversationSnapshotsByLooseKey = new Map();
|
|
9
|
+
function normalizeOptional(value) {
|
|
10
|
+
const trimmed = String(value ?? "").trim();
|
|
11
|
+
return trimmed || undefined;
|
|
12
|
+
}
|
|
13
|
+
function messageFactKey(accountId, messageId) {
|
|
14
|
+
return `${accountId}::${messageId}`;
|
|
15
|
+
}
|
|
16
|
+
function accountScopedSessionKey(accountId, kind, value) {
|
|
17
|
+
return `${accountId}::${kind}::${value}`;
|
|
18
|
+
}
|
|
19
|
+
function normalizePeerId(value) {
|
|
20
|
+
const trimmed = String(value ?? "").trim();
|
|
21
|
+
return trimmed ? trimmed.toLowerCase() : undefined;
|
|
22
|
+
}
|
|
23
|
+
function normalizePeerKind(value) {
|
|
24
|
+
const trimmed = String(value ?? "").trim().toLowerCase();
|
|
25
|
+
return trimmed === "direct" || trimmed === "group" ? trimmed : undefined;
|
|
26
|
+
}
|
|
27
|
+
function accountScopedConversationKey(accountId, peerKind, peerId) {
|
|
28
|
+
return `${accountId}::peer::${peerKind}::${peerId}`;
|
|
29
|
+
}
|
|
30
|
+
function pruneOldest(map, maxSize) {
|
|
31
|
+
while (map.size > maxSize) {
|
|
32
|
+
const oldestKey = map.keys().next().value;
|
|
33
|
+
if (!oldestKey)
|
|
34
|
+
return;
|
|
35
|
+
map.delete(oldestKey);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function writeSessionSnapshot(snapshot) {
|
|
39
|
+
const sessionKey = normalizeOptional(snapshot.sessionKey);
|
|
40
|
+
const sessionId = normalizeOptional(snapshot.sessionId);
|
|
41
|
+
if (sessionKey) {
|
|
42
|
+
sessionSnapshotsByAccountKey.set(accountScopedSessionKey(snapshot.accountId, "sessionKey", sessionKey), snapshot);
|
|
43
|
+
sessionSnapshotsByLooseKey.set(`sessionKey::${sessionKey}`, snapshot);
|
|
44
|
+
}
|
|
45
|
+
if (sessionId) {
|
|
46
|
+
sessionSnapshotsByAccountKey.set(accountScopedSessionKey(snapshot.accountId, "sessionId", sessionId), snapshot);
|
|
47
|
+
sessionSnapshotsByLooseKey.set(`sessionId::${sessionId}`, snapshot);
|
|
48
|
+
}
|
|
49
|
+
pruneOldest(sessionSnapshotsByAccountKey, MAX_SESSION_SNAPSHOTS * 2);
|
|
50
|
+
pruneOldest(sessionSnapshotsByLooseKey, MAX_SESSION_SNAPSHOTS * 2);
|
|
51
|
+
}
|
|
52
|
+
function writeConversationSnapshot(snapshot) {
|
|
53
|
+
const peerKind = normalizePeerKind(snapshot.peerKind);
|
|
54
|
+
const peerId = normalizePeerId(snapshot.peerId);
|
|
55
|
+
if (!peerKind || !peerId) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
conversationSnapshotsByAccountKey.set(accountScopedConversationKey(snapshot.accountId, peerKind, peerId), {
|
|
59
|
+
...snapshot,
|
|
60
|
+
peerKind,
|
|
61
|
+
peerId,
|
|
62
|
+
});
|
|
63
|
+
conversationSnapshotsByLooseKey.set(`peer::${peerKind}::${peerId}`, {
|
|
64
|
+
...snapshot,
|
|
65
|
+
peerKind,
|
|
66
|
+
peerId,
|
|
67
|
+
});
|
|
68
|
+
pruneOldest(conversationSnapshotsByAccountKey, MAX_CONVERSATION_SNAPSHOTS);
|
|
69
|
+
pruneOldest(conversationSnapshotsByLooseKey, MAX_CONVERSATION_SNAPSHOTS);
|
|
70
|
+
}
|
|
71
|
+
export function registerWecomSourceSnapshot(params) {
|
|
72
|
+
const accountId = normalizeOptional(params.accountId);
|
|
73
|
+
if (!accountId)
|
|
74
|
+
return;
|
|
75
|
+
const snapshot = {
|
|
76
|
+
accountId,
|
|
77
|
+
source: params.source,
|
|
78
|
+
recordedAt: Date.now(),
|
|
79
|
+
...(normalizeOptional(params.messageId)
|
|
80
|
+
? { messageId: normalizeOptional(params.messageId) }
|
|
81
|
+
: {}),
|
|
82
|
+
...(normalizeOptional(params.sessionKey)
|
|
83
|
+
? { sessionKey: normalizeOptional(params.sessionKey) }
|
|
84
|
+
: {}),
|
|
85
|
+
...(normalizeOptional(params.sessionId)
|
|
86
|
+
? { sessionId: normalizeOptional(params.sessionId) }
|
|
87
|
+
: {}),
|
|
88
|
+
...(normalizePeerKind(params.peerKind) ? { peerKind: normalizePeerKind(params.peerKind) } : {}),
|
|
89
|
+
...(normalizePeerId(params.peerId) ? { peerId: normalizePeerId(params.peerId) } : {}),
|
|
90
|
+
...(normalizeOptional(params.upstreamCorpId)
|
|
91
|
+
? { upstreamCorpId: normalizeOptional(params.upstreamCorpId) }
|
|
92
|
+
: {}),
|
|
93
|
+
};
|
|
94
|
+
if (snapshot.messageId) {
|
|
95
|
+
messageFacts.set(messageFactKey(accountId, snapshot.messageId), snapshot);
|
|
96
|
+
pruneOldest(messageFacts, MAX_MESSAGE_FACTS);
|
|
97
|
+
}
|
|
98
|
+
writeSessionSnapshot(snapshot);
|
|
99
|
+
writeConversationSnapshot(snapshot);
|
|
100
|
+
}
|
|
101
|
+
export function resolveWecomSourceSnapshot(params) {
|
|
102
|
+
const accountId = normalizeOptional(params.accountId);
|
|
103
|
+
const sessionKey = normalizeOptional(params.sessionKey);
|
|
104
|
+
const sessionId = normalizeOptional(params.sessionId);
|
|
105
|
+
const peerKind = normalizePeerKind(params.peerKind);
|
|
106
|
+
const peerId = normalizePeerId(params.peerId);
|
|
107
|
+
if (accountId && sessionKey) {
|
|
108
|
+
const scoped = sessionSnapshotsByAccountKey.get(accountScopedSessionKey(accountId, "sessionKey", sessionKey));
|
|
109
|
+
if (scoped)
|
|
110
|
+
return scoped;
|
|
111
|
+
}
|
|
112
|
+
if (accountId && sessionId) {
|
|
113
|
+
const scoped = sessionSnapshotsByAccountKey.get(accountScopedSessionKey(accountId, "sessionId", sessionId));
|
|
114
|
+
if (scoped)
|
|
115
|
+
return scoped;
|
|
116
|
+
}
|
|
117
|
+
if (sessionKey) {
|
|
118
|
+
const loose = sessionSnapshotsByLooseKey.get(`sessionKey::${sessionKey}`);
|
|
119
|
+
if (loose)
|
|
120
|
+
return loose;
|
|
121
|
+
}
|
|
122
|
+
if (sessionId) {
|
|
123
|
+
const loose = sessionSnapshotsByLooseKey.get(`sessionId::${sessionId}`);
|
|
124
|
+
if (loose)
|
|
125
|
+
return loose;
|
|
126
|
+
}
|
|
127
|
+
if (accountId && peerKind && peerId) {
|
|
128
|
+
const scoped = conversationSnapshotsByAccountKey.get(accountScopedConversationKey(accountId, peerKind, peerId));
|
|
129
|
+
if (scoped)
|
|
130
|
+
return scoped;
|
|
131
|
+
}
|
|
132
|
+
if (peerKind && peerId) {
|
|
133
|
+
const loose = conversationSnapshotsByLooseKey.get(`peer::${peerKind}::${peerId}`);
|
|
134
|
+
if (loose)
|
|
135
|
+
return loose;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
export function clearWecomSourceAccount(accountId) {
|
|
140
|
+
const normalized = normalizeOptional(accountId);
|
|
141
|
+
if (!normalized)
|
|
142
|
+
return;
|
|
143
|
+
for (const [key, value] of messageFacts) {
|
|
144
|
+
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
145
|
+
messageFacts.delete(key);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const [key, value] of sessionSnapshotsByAccountKey) {
|
|
149
|
+
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
150
|
+
sessionSnapshotsByAccountKey.delete(key);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const [key, value] of sessionSnapshotsByLooseKey) {
|
|
154
|
+
if (value.accountId === normalized) {
|
|
155
|
+
sessionSnapshotsByLooseKey.delete(key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const [key, value] of conversationSnapshotsByAccountKey) {
|
|
159
|
+
if (value.accountId === normalized || key.startsWith(`${normalized}::`)) {
|
|
160
|
+
conversationSnapshotsByAccountKey.delete(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const [key, value] of conversationSnapshotsByLooseKey) {
|
|
164
|
+
if (value.accountId === normalized) {
|
|
165
|
+
conversationSnapshotsByLooseKey.delete(key);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function isWecomBotWsSource(params) {
|
|
170
|
+
return resolveWecomSourceSnapshot(params)?.source === "bot-ws";
|
|
171
|
+
}
|
|
172
|
+
export function isWecomAgentSource(params) {
|
|
173
|
+
return resolveWecomSourceSnapshot(params)?.source === "agent-callback";
|
|
174
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getActiveBotWsReplyHandle, getAccountRuntime, getAccountRuntimeSnapshot, getBotWsPushHandle, getWecomRuntime, registerActiveBotWsReplyHandle, registerAccountRuntime, registerBotWsPushHandle, setWecomRuntime, unregisterActiveBotWsReplyHandle, unregisterBotWsPushHandle, unregisterAccountRuntime, } from "./app/index.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isWecomSenderAllowed, normalizeWecomAllowFromEntry } from "../domain/policies.js";
|
|
2
|
+
export async function resolveWecomCommandAuthorization(params) {
|
|
3
|
+
const { core, cfg, accountConfig, rawBody, senderUserId } = params;
|
|
4
|
+
const dmPolicy = (accountConfig.dm?.policy ?? "pairing");
|
|
5
|
+
const configAllowFrom = (accountConfig.dm?.allowFrom ?? []).map((v) => String(v));
|
|
6
|
+
const shouldComputeAuth = core.channel.commands.shouldComputeCommandAuthorized(rawBody, cfg);
|
|
7
|
+
// WeCom channel currently does NOT support the `openclaw pairing` CLI workflow
|
|
8
|
+
// ("Channel wecom does not support pairing"). So we must not rely on pairing
|
|
9
|
+
// store approvals for command authorization here.
|
|
10
|
+
//
|
|
11
|
+
// Policy semantics:
|
|
12
|
+
// - open: commands are allowed for everyone by default (unless higher-level access-groups deny).
|
|
13
|
+
// - allowlist: commands require allowFrom entries.
|
|
14
|
+
// - pairing: treated the same as allowlist for WeCom (since pairing CLI is unsupported).
|
|
15
|
+
const effectiveAllowFrom = dmPolicy === "open" ? ["*"] : configAllowFrom;
|
|
16
|
+
const senderAllowed = isWecomSenderAllowed(senderUserId, effectiveAllowFrom);
|
|
17
|
+
const allowAllConfigured = effectiveAllowFrom.some((entry) => normalizeWecomAllowFromEntry(entry) === "*");
|
|
18
|
+
const authorizerConfigured = allowAllConfigured || effectiveAllowFrom.length > 0;
|
|
19
|
+
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
|
20
|
+
const commandAuthorized = shouldComputeAuth
|
|
21
|
+
? core.channel.commands.resolveCommandAuthorizedFromAuthorizers({
|
|
22
|
+
useAccessGroups,
|
|
23
|
+
authorizers: [{ configured: authorizerConfigured, allowed: senderAllowed }],
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
return {
|
|
27
|
+
shouldComputeAuth,
|
|
28
|
+
dmPolicy,
|
|
29
|
+
senderAllowed,
|
|
30
|
+
authorizerConfigured,
|
|
31
|
+
commandAuthorized,
|
|
32
|
+
effectiveAllowFrom,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function buildWecomUnauthorizedCommandPrompt(params) {
|
|
36
|
+
const user = params.senderUserId || "unknown";
|
|
37
|
+
const policy = params.dmPolicy;
|
|
38
|
+
const scopeLabel = params.scope === "bot" ? "Bot(智能机器人)" : "Agent(自建应用)";
|
|
39
|
+
const dmPrefix = params.scope === "bot" ? "channels.wecom.bot.dm" : "channels.wecom.agent.dm";
|
|
40
|
+
const allowCmd = (value) => `openclaw config set ${dmPrefix}.allowFrom '${value}'`;
|
|
41
|
+
const policyCmd = (value) => `openclaw config set ${dmPrefix}.policy "${value}"`;
|
|
42
|
+
if (policy === "disabled") {
|
|
43
|
+
return [
|
|
44
|
+
`无权限执行命令(${scopeLabel} 已禁用:dm.policy=disabled)`,
|
|
45
|
+
`触发者:${user}`,
|
|
46
|
+
`管理员:${policyCmd("open")}(全放开)或 ${policyCmd("allowlist")}(白名单)`,
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
// WeCom 不支持 pairing CLI,因此这里统一给出“open / allowlist”两种明确的配置指令
|
|
50
|
+
return [
|
|
51
|
+
`无权限执行命令(入口:${scopeLabel},userid:${user})`,
|
|
52
|
+
`管理员全放开:${policyCmd("open")}`,
|
|
53
|
+
`管理员放行该用户:${policyCmd("allowlist")}`,
|
|
54
|
+
`然后设置白名单:${allowCmd(JSON.stringify([user]))}`,
|
|
55
|
+
`如果仍被拦截:检查 commands.useAccessGroups/访问组`,
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveWecomEgressProxyUrlFromNetwork } from "../config/index.js";
|
|
3
|
+
import { wecomFetch } from "../http.js";
|
|
4
|
+
function inferContentTypeFromFilePath(filePath) {
|
|
5
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
6
|
+
const mimeTypes = {
|
|
7
|
+
jpg: "image/jpeg",
|
|
8
|
+
jpeg: "image/jpeg",
|
|
9
|
+
png: "image/png",
|
|
10
|
+
gif: "image/gif",
|
|
11
|
+
webp: "image/webp",
|
|
12
|
+
bmp: "image/bmp",
|
|
13
|
+
mp3: "audio/mpeg",
|
|
14
|
+
wav: "audio/wav",
|
|
15
|
+
amr: "audio/amr",
|
|
16
|
+
mp4: "video/mp4",
|
|
17
|
+
pdf: "application/pdf",
|
|
18
|
+
doc: "application/msword",
|
|
19
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
20
|
+
xls: "application/vnd.ms-excel",
|
|
21
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
22
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
23
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
24
|
+
txt: "text/plain",
|
|
25
|
+
csv: "text/csv",
|
|
26
|
+
tsv: "text/tab-separated-values",
|
|
27
|
+
md: "text/markdown",
|
|
28
|
+
json: "application/json",
|
|
29
|
+
xml: "application/xml",
|
|
30
|
+
yaml: "application/yaml",
|
|
31
|
+
yml: "application/yaml",
|
|
32
|
+
zip: "application/zip",
|
|
33
|
+
rar: "application/vnd.rar",
|
|
34
|
+
"7z": "application/x-7z-compressed",
|
|
35
|
+
tar: "application/x-tar",
|
|
36
|
+
gz: "application/gzip",
|
|
37
|
+
tgz: "application/gzip",
|
|
38
|
+
rtf: "application/rtf",
|
|
39
|
+
odt: "application/vnd.oasis.opendocument.text",
|
|
40
|
+
};
|
|
41
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
42
|
+
}
|
|
43
|
+
export async function resolveOutboundMediaAsset(params) {
|
|
44
|
+
const { mediaUrl, network, timeoutMs = 30000 } = params;
|
|
45
|
+
if (/^https?:\/\//i.test(mediaUrl)) {
|
|
46
|
+
const response = await wecomFetch(mediaUrl, { method: "GET" }, {
|
|
47
|
+
proxyUrl: resolveWecomEgressProxyUrlFromNetwork(network),
|
|
48
|
+
timeoutMs,
|
|
49
|
+
});
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`Failed to download media: ${response.status}`);
|
|
52
|
+
}
|
|
53
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
54
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
55
|
+
const filename = path.basename(new URL(mediaUrl).pathname) || "media";
|
|
56
|
+
return { buffer, filename, contentType };
|
|
57
|
+
}
|
|
58
|
+
const fs = await import("node:fs/promises");
|
|
59
|
+
const buffer = await fs.readFile(mediaUrl);
|
|
60
|
+
return {
|
|
61
|
+
buffer,
|
|
62
|
+
filename: path.basename(mediaUrl),
|
|
63
|
+
contentType: inferContentTypeFromFilePath(mediaUrl),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { resolveWecomMediaMaxBytes } from "../config/index.js";
|
|
2
|
+
import { decryptWecomMediaWithMeta } from "../media.js";
|
|
3
|
+
export class WecomMediaService {
|
|
4
|
+
core;
|
|
5
|
+
cfg;
|
|
6
|
+
constructor(core, cfg) {
|
|
7
|
+
this.core = core;
|
|
8
|
+
this.cfg = cfg;
|
|
9
|
+
}
|
|
10
|
+
resolveInboundMaxBytes(accountId) {
|
|
11
|
+
return resolveWecomMediaMaxBytes(this.cfg, accountId);
|
|
12
|
+
}
|
|
13
|
+
async downloadRemoteMedia(params) {
|
|
14
|
+
const loaded = await this.core.channel.media.fetchRemoteMedia({
|
|
15
|
+
url: params.url,
|
|
16
|
+
maxBytes: params.maxBytes,
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
buffer: loaded.buffer,
|
|
20
|
+
contentType: loaded.contentType,
|
|
21
|
+
filename: loaded.fileName,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Download and decrypt WeCom AES-encrypted media.
|
|
26
|
+
* Bot-ws: each message carries a unique per-URL aeskey in the message body.
|
|
27
|
+
* Bot-webhook: uses the account-level EncodingAESKey.
|
|
28
|
+
* Both use AES-256-CBC with PKCS#7 padding (32-byte block), IV = key[:16].
|
|
29
|
+
*/
|
|
30
|
+
async downloadEncryptedMedia(params) {
|
|
31
|
+
const decrypted = await decryptWecomMediaWithMeta(params.url, params.aesKey, {
|
|
32
|
+
maxBytes: params.maxBytes,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
buffer: decrypted.buffer,
|
|
36
|
+
contentType: decrypted.sourceContentType,
|
|
37
|
+
filename: decrypted.sourceFilename,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async saveInboundAttachment(event, attachment) {
|
|
41
|
+
const maxBytes = this.resolveInboundMaxBytes(event.accountId);
|
|
42
|
+
const saved = await this.core.channel.media.saveMediaBuffer(attachment.buffer, attachment.contentType, "inbound", maxBytes, attachment.filename);
|
|
43
|
+
return saved.path;
|
|
44
|
+
}
|
|
45
|
+
async normalizeFirstAttachment(event) {
|
|
46
|
+
const first = event.attachments?.[0];
|
|
47
|
+
if (!first?.remoteUrl) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
// Keep fetch/decrypt/save on the same account-aware limit instead of falling back
|
|
51
|
+
// to the core media store default (5MB).
|
|
52
|
+
const maxBytes = this.resolveInboundMaxBytes(event.accountId);
|
|
53
|
+
// Bot-ws media is AES-encrypted; use decryption when aesKey is present
|
|
54
|
+
if (first.aesKey) {
|
|
55
|
+
return this.downloadEncryptedMedia({ url: first.remoteUrl, aesKey: first.aesKey, maxBytes });
|
|
56
|
+
}
|
|
57
|
+
return this.downloadRemoteMedia({ url: first.remoteUrl, maxBytes });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|