@dingxiang-me/openclaw-wechat 2.1.0 → 2.3.0
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/CHANGELOG.md +72 -0
- package/README.en.md +181 -14
- package/README.md +201 -16
- package/docs/channels/wecom.md +137 -1
- package/openclaw.plugin.json +688 -6
- package/package.json +204 -4
- package/scripts/wecom-agent-selfcheck.mjs +775 -0
- package/scripts/wecom-bot-longconn-probe.mjs +582 -0
- package/scripts/wecom-bot-selfcheck.mjs +952 -0
- package/scripts/wecom-callback-matrix.mjs +224 -0
- package/scripts/wecom-doctor.mjs +1407 -0
- package/scripts/wecom-e2e-scenario.mjs +333 -0
- package/scripts/wecom-migrate.mjs +261 -0
- package/scripts/wecom-quickstart.mjs +1824 -0
- package/scripts/wecom-release-check.mjs +232 -0
- package/scripts/wecom-remote-e2e.mjs +310 -0
- package/scripts/wecom-selfcheck.mjs +1255 -0
- package/scripts/wecom-smoke.sh +74 -0
- package/src/core/delivery-router.js +21 -0
- package/src/core.js +619 -30
- package/src/wecom/account-config-core.js +27 -1
- package/src/wecom/account-config.js +19 -2
- package/src/wecom/agent-dispatch-executor.js +11 -0
- package/src/wecom/agent-dispatch-handlers.js +61 -8
- package/src/wecom/agent-inbound-guards.js +24 -0
- package/src/wecom/agent-inbound-processor.js +34 -2
- package/src/wecom/agent-late-reply-runtime.js +30 -2
- package/src/wecom/agent-text-sender.js +2 -0
- package/src/wecom/api-client-core.js +27 -19
- package/src/wecom/api-client-media.js +16 -7
- package/src/wecom/api-client-send-text.js +4 -0
- package/src/wecom/api-client-send-typed.js +4 -1
- package/src/wecom/api-client-senders.js +41 -3
- package/src/wecom/api-client.js +1 -0
- package/src/wecom/bot-dispatch-fallback.js +18 -3
- package/src/wecom/bot-dispatch-handlers.js +47 -10
- package/src/wecom/bot-inbound-dispatch-runtime.js +3 -0
- package/src/wecom/bot-inbound-executor-helpers.js +11 -1
- package/src/wecom/bot-inbound-executor.js +24 -0
- package/src/wecom/bot-inbound-guards.js +31 -1
- package/src/wecom/channel-config-schema.js +132 -0
- package/src/wecom/channel-plugin.js +348 -7
- package/src/wecom/command-handlers.js +102 -11
- package/src/wecom/command-status-text.js +206 -0
- package/src/wecom/doc-client.js +7 -1
- package/src/wecom/inbound-content-handler-file-video-link.js +4 -0
- package/src/wecom/inbound-content-handler-image-voice.js +6 -0
- package/src/wecom/inbound-content.js +5 -0
- package/src/wecom/installer-api.js +910 -0
- package/src/wecom/media-download.js +2 -2
- package/src/wecom/migration-diagnostics.js +816 -0
- package/src/wecom/network-config.js +91 -0
- package/src/wecom/observability-metrics.js +9 -3
- package/src/wecom/outbound-agent-delivery.js +313 -0
- package/src/wecom/outbound-agent-media-sender.js +37 -7
- package/src/wecom/outbound-agent-push.js +1 -0
- package/src/wecom/outbound-delivery.js +129 -12
- package/src/wecom/outbound-stream-msg-item.js +25 -2
- package/src/wecom/outbound-webhook-delivery.js +19 -0
- package/src/wecom/outbound-webhook-media.js +30 -6
- package/src/wecom/pending-reply-manager.js +143 -0
- package/src/wecom/plugin-account-policy-services.js +26 -0
- package/src/wecom/plugin-base-services.js +58 -0
- package/src/wecom/plugin-constants.js +1 -1
- package/src/wecom/plugin-delivery-inbound-services.js +25 -0
- package/src/wecom/plugin-processing-deps.js +7 -0
- package/src/wecom/plugin-route-runtime-deps.js +1 -0
- package/src/wecom/plugin-services.js +87 -0
- package/src/wecom/policy-resolvers.js +93 -20
- package/src/wecom/quickstart-metadata.js +1247 -0
- package/src/wecom/reasoning-visibility.js +104 -0
- package/src/wecom/register-runtime.js +10 -0
- package/src/wecom/reliable-delivery-persistence.js +138 -0
- package/src/wecom/reliable-delivery.js +642 -0
- package/src/wecom/reply-output-policy.js +171 -0
- package/src/wecom/text-inbound-scheduler.js +6 -1
- package/src/wecom/workspace-auto-sender.js +2 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildDefaultAgentWebhookPath } from "./account-paths.js";
|
|
2
|
+
import { resolveWecomApiBaseUrl } from "./network-config.js";
|
|
2
3
|
|
|
3
4
|
export function asNumber(v, fallback = null) {
|
|
4
5
|
if (v == null) return fallback;
|
|
@@ -31,6 +32,7 @@ export function normalizeAccountConfig({ raw, accountId, normalizeWecomWebhookTa
|
|
|
31
32
|
const normalizedId = normalizeAccountId(accountId);
|
|
32
33
|
if (!raw || typeof raw !== "object") return null;
|
|
33
34
|
if (typeof normalizeWecomWebhookTargetMap !== "function") return null;
|
|
35
|
+
const networkConfig = raw.network && typeof raw.network === "object" ? raw.network : {};
|
|
34
36
|
|
|
35
37
|
const legacyAgent = raw.agent && typeof raw.agent === "object" ? raw.agent : {};
|
|
36
38
|
const hasLegacyAgentBlock = Object.keys(legacyAgent).length > 0;
|
|
@@ -53,7 +55,19 @@ export function normalizeAccountConfig({ raw, accountId, normalizeWecomWebhookTa
|
|
|
53
55
|
const defaultWebhookPath = buildDefaultAgentWebhookPath(normalizedId);
|
|
54
56
|
const webhookPath = String(raw.webhookPath ?? legacyAgent.webhookPath ?? defaultWebhookPath).trim() || defaultWebhookPath;
|
|
55
57
|
const name = pickFirstNonEmptyString(raw.name, normalizedId);
|
|
56
|
-
const outboundProxy = String(
|
|
58
|
+
const outboundProxy = String(
|
|
59
|
+
raw.outboundProxy ??
|
|
60
|
+
raw.proxyUrl ??
|
|
61
|
+
raw.proxy ??
|
|
62
|
+
networkConfig.egressProxyUrl ??
|
|
63
|
+
networkConfig.proxyUrl ??
|
|
64
|
+
networkConfig.proxy ??
|
|
65
|
+
"",
|
|
66
|
+
).trim();
|
|
67
|
+
const apiBaseUrl = resolveWecomApiBaseUrl({
|
|
68
|
+
accountConfig: raw,
|
|
69
|
+
accountId: normalizedId,
|
|
70
|
+
});
|
|
57
71
|
const webhooks = normalizeWecomWebhookTargetMap(raw.webhooks);
|
|
58
72
|
const allowFrom = raw.allowFrom ?? raw.dm?.allowFrom;
|
|
59
73
|
const allowFromRejectMessage = String(raw.allowFromRejectMessage ?? raw.rejectUnauthorizedMessage ?? "").trim();
|
|
@@ -74,6 +88,7 @@ export function normalizeAccountConfig({ raw, accountId, normalizeWecomWebhookTa
|
|
|
74
88
|
webhookPath,
|
|
75
89
|
name,
|
|
76
90
|
outboundProxy: outboundProxy || undefined,
|
|
91
|
+
apiBaseUrl: apiBaseUrl || undefined,
|
|
77
92
|
webhooks: Object.keys(webhooks).length > 0 ? webhooks : undefined,
|
|
78
93
|
allowFrom,
|
|
79
94
|
allowFromRejectMessage: allowFromRejectMessage || undefined,
|
|
@@ -108,11 +123,21 @@ export function readAccountConfigFromEnv({
|
|
|
108
123
|
const defaultWebhookPath = buildDefaultAgentWebhookPath(normalizedId);
|
|
109
124
|
const webhookPath = String(readVar("WEBHOOK_PATH") ?? defaultWebhookPath).trim() || defaultWebhookPath;
|
|
110
125
|
const outboundProxyRaw =
|
|
126
|
+
readVar("EGRESS_PROXY_URL") ??
|
|
111
127
|
readVar("PROXY") ??
|
|
112
128
|
(normalizedId === "default"
|
|
113
129
|
? requireEnv("HTTPS_PROXY")
|
|
114
130
|
: envVars?.WECOM_PROXY ?? requireEnv("WECOM_PROXY") ?? requireEnv("HTTPS_PROXY"));
|
|
115
131
|
const outboundProxy = String(outboundProxyRaw ?? "").trim();
|
|
132
|
+
const apiBaseUrl = resolveWecomApiBaseUrl({
|
|
133
|
+
envVars: {
|
|
134
|
+
...envVars,
|
|
135
|
+
[`${prefix}_API_BASE_URL`]: readVar("API_BASE_URL"),
|
|
136
|
+
...(normalizedId === "default" ? { WECOM_API_BASE_URL: readVar("API_BASE_URL") } : {}),
|
|
137
|
+
},
|
|
138
|
+
processEnv: {},
|
|
139
|
+
accountId: normalizedId,
|
|
140
|
+
});
|
|
116
141
|
const webhooks = normalizeWecomWebhookTargetMap(readVar("WEBHOOK_TARGETS"), readVar("WEBHOOKS"));
|
|
117
142
|
const allowFrom = readVar("ALLOW_FROM");
|
|
118
143
|
const allowFromRejectMessage = String(readVar("ALLOW_FROM_REJECT_MESSAGE") ?? "").trim();
|
|
@@ -130,6 +155,7 @@ export function readAccountConfigFromEnv({
|
|
|
130
155
|
callbackAesKey,
|
|
131
156
|
webhookPath,
|
|
132
157
|
outboundProxy: outboundProxy || undefined,
|
|
158
|
+
apiBaseUrl: apiBaseUrl || undefined,
|
|
133
159
|
webhooks: Object.keys(webhooks).length > 0 ? webhooks : undefined,
|
|
134
160
|
allowFrom,
|
|
135
161
|
allowFromRejectMessage: allowFromRejectMessage || undefined,
|
|
@@ -6,10 +6,14 @@ import {
|
|
|
6
6
|
readAccountConfigFromEnv,
|
|
7
7
|
} from "./account-config-core.js";
|
|
8
8
|
import { normalizePluginHttpPath } from "./http-path.js";
|
|
9
|
+
import { resolveWecomApiBaseUrl } from "./network-config.js";
|
|
9
10
|
|
|
10
|
-
const LEGACY_INLINE_ACCOUNT_RESERVED_KEYS = new Set([
|
|
11
|
+
export const LEGACY_INLINE_ACCOUNT_RESERVED_KEYS = new Set([
|
|
11
12
|
"name",
|
|
12
13
|
"enabled",
|
|
14
|
+
"botId",
|
|
15
|
+
"botid",
|
|
16
|
+
"secret",
|
|
13
17
|
"corpId",
|
|
14
18
|
"corpSecret",
|
|
15
19
|
"agentId",
|
|
@@ -21,9 +25,14 @@ const LEGACY_INLINE_ACCOUNT_RESERVED_KEYS = new Set([
|
|
|
21
25
|
"outboundProxy",
|
|
22
26
|
"proxyUrl",
|
|
23
27
|
"proxy",
|
|
28
|
+
"network",
|
|
29
|
+
"apiBaseUrl",
|
|
24
30
|
"webhooks",
|
|
25
31
|
"allowFrom",
|
|
26
32
|
"allowFromRejectMessage",
|
|
33
|
+
"groupPolicy",
|
|
34
|
+
"groupAllowFrom",
|
|
35
|
+
"groupAllowFromRejectMessage",
|
|
27
36
|
"rejectUnauthorizedMessage",
|
|
28
37
|
"adminUsers",
|
|
29
38
|
"commandAllowlist",
|
|
@@ -31,6 +40,7 @@ const LEGACY_INLINE_ACCOUNT_RESERVED_KEYS = new Set([
|
|
|
31
40
|
"commands",
|
|
32
41
|
"workspaceTemplate",
|
|
33
42
|
"groupChat",
|
|
43
|
+
"groups",
|
|
34
44
|
"dynamicAgent",
|
|
35
45
|
"dynamicAgents",
|
|
36
46
|
"dm",
|
|
@@ -48,7 +58,7 @@ const LEGACY_INLINE_ACCOUNT_RESERVED_KEYS = new Set([
|
|
|
48
58
|
"agent",
|
|
49
59
|
]);
|
|
50
60
|
|
|
51
|
-
function listLegacyInlineAccountEntries(channelConfig) {
|
|
61
|
+
export function listLegacyInlineAccountEntries(channelConfig) {
|
|
52
62
|
if (!channelConfig || typeof channelConfig !== "object") return [];
|
|
53
63
|
const entries = [];
|
|
54
64
|
for (const [rawKey, value] of Object.entries(channelConfig)) {
|
|
@@ -137,6 +147,13 @@ export function createWecomAccountRegistry({
|
|
|
137
147
|
processEnv,
|
|
138
148
|
accountId,
|
|
139
149
|
});
|
|
150
|
+
config.apiBaseUrl = resolveWecomApiBaseUrl({
|
|
151
|
+
channelConfig,
|
|
152
|
+
accountConfig: config,
|
|
153
|
+
envVars,
|
|
154
|
+
processEnv,
|
|
155
|
+
accountId,
|
|
156
|
+
});
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
wecomAccounts.clear();
|
|
@@ -29,6 +29,7 @@ export async function executeWecomAgentDispatchFlow({
|
|
|
29
29
|
corpSecret,
|
|
30
30
|
agentId,
|
|
31
31
|
proxyUrl = "",
|
|
32
|
+
apiBaseUrl = "",
|
|
32
33
|
tempPathsToCleanup = [],
|
|
33
34
|
resolveWecomReplyStreamingPolicy,
|
|
34
35
|
asNumber,
|
|
@@ -37,6 +38,7 @@ export async function executeWecomAgentDispatchFlow({
|
|
|
37
38
|
markdownToWecomText,
|
|
38
39
|
autoSendWorkspaceFilesFromReplyText,
|
|
39
40
|
sendWecomOutboundMediaBatch,
|
|
41
|
+
resolveWecomReasoningPolicy,
|
|
40
42
|
withTimeout,
|
|
41
43
|
isDispatchTimeoutError,
|
|
42
44
|
isAgentFailureText,
|
|
@@ -44,6 +46,7 @@ export async function executeWecomAgentDispatchFlow({
|
|
|
44
46
|
ensureLateReplyWatcherRunner,
|
|
45
47
|
ACTIVE_LATE_REPLY_WATCHERS,
|
|
46
48
|
sendTextToUser,
|
|
49
|
+
deliverAgentReply,
|
|
47
50
|
clearSessionStoreEntry,
|
|
48
51
|
} = {}) {
|
|
49
52
|
assertFunction("resolveWecomReplyStreamingPolicy", resolveWecomReplyStreamingPolicy);
|
|
@@ -92,9 +95,12 @@ export async function executeWecomAgentDispatchFlow({
|
|
|
92
95
|
lateReplyWatchMs,
|
|
93
96
|
lateReplyPollMs,
|
|
94
97
|
sendTextToUser,
|
|
98
|
+
deliverAgentReply,
|
|
95
99
|
ensureLateReplyWatcherRunner,
|
|
96
100
|
activeWatchers: ACTIVE_LATE_REPLY_WATCHERS,
|
|
97
101
|
clearSessionStoreEntry,
|
|
102
|
+
api,
|
|
103
|
+
fromUser,
|
|
98
104
|
logger: api?.logger,
|
|
99
105
|
});
|
|
100
106
|
const sendProgressNotice = lateReplyRuntime.sendProgressNotice;
|
|
@@ -115,21 +121,26 @@ export async function executeWecomAgentDispatchFlow({
|
|
|
115
121
|
api,
|
|
116
122
|
state: dispatchState,
|
|
117
123
|
streamingEnabled,
|
|
124
|
+
sessionId,
|
|
125
|
+
runtimeAccountId,
|
|
118
126
|
fromUser,
|
|
119
127
|
routedAgentId,
|
|
120
128
|
corpId,
|
|
121
129
|
corpSecret,
|
|
122
130
|
agentId,
|
|
123
131
|
proxyUrl,
|
|
132
|
+
apiBaseUrl,
|
|
124
133
|
flushStreamingBuffer,
|
|
125
134
|
sendFailureFallback,
|
|
126
135
|
sendTextToUser,
|
|
136
|
+
deliverAgentReply,
|
|
127
137
|
markdownToWecomText,
|
|
128
138
|
isAgentFailureText,
|
|
129
139
|
computeStreamingTailText,
|
|
130
140
|
autoSendWorkspaceFilesFromReplyText,
|
|
131
141
|
buildWorkspaceAutoSendHints,
|
|
132
142
|
sendWecomOutboundMediaBatch,
|
|
143
|
+
reasoningPolicy: resolveWecomReasoningPolicy?.(api) ?? {},
|
|
133
144
|
});
|
|
134
145
|
const dispatchResult = await withTimeout(
|
|
135
146
|
runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { applyWecomReasoningPolicy } from "./reasoning-visibility.js";
|
|
2
|
+
import { extractWecomReplyDirectives } from "./reply-output-policy.js";
|
|
3
|
+
import { parseThinkingContent } from "./thinking-parser.js";
|
|
4
|
+
|
|
1
5
|
function assertFunction(name, value) {
|
|
2
6
|
if (typeof value !== "function") {
|
|
3
7
|
throw new Error(`createWecomAgentDispatchHandlers: ${name} is required`);
|
|
@@ -16,21 +20,26 @@ export function createWecomAgentDispatchHandlers({
|
|
|
16
20
|
api,
|
|
17
21
|
state,
|
|
18
22
|
streamingEnabled = false,
|
|
23
|
+
sessionId = "",
|
|
24
|
+
runtimeAccountId = "default",
|
|
19
25
|
fromUser,
|
|
20
26
|
routedAgentId = "",
|
|
21
27
|
corpId = "",
|
|
22
28
|
corpSecret = "",
|
|
23
29
|
agentId = "",
|
|
24
30
|
proxyUrl = "",
|
|
31
|
+
apiBaseUrl = "",
|
|
25
32
|
flushStreamingBuffer,
|
|
26
33
|
sendFailureFallback,
|
|
27
34
|
sendTextToUser,
|
|
35
|
+
deliverAgentReply = null,
|
|
28
36
|
markdownToWecomText,
|
|
29
37
|
isAgentFailureText,
|
|
30
38
|
computeStreamingTailText,
|
|
31
39
|
autoSendWorkspaceFilesFromReplyText,
|
|
32
40
|
buildWorkspaceAutoSendHints,
|
|
33
41
|
sendWecomOutboundMediaBatch,
|
|
42
|
+
reasoningPolicy = {},
|
|
34
43
|
} = {}) {
|
|
35
44
|
if (!state || typeof state !== "object") {
|
|
36
45
|
throw new Error("createWecomAgentDispatchHandlers: state is required");
|
|
@@ -67,9 +76,13 @@ export function createWecomAgentDispatchHandlers({
|
|
|
67
76
|
}
|
|
68
77
|
if (info.kind === "block") {
|
|
69
78
|
if (payload.text) {
|
|
70
|
-
|
|
79
|
+
const blockParsed = parseThinkingContent(payload.text);
|
|
80
|
+
const blockVisibleText = extractWecomReplyDirectives(markdownToWecomText(blockParsed.visibleContent).trim()).text;
|
|
81
|
+
if (blockVisibleText) {
|
|
82
|
+
state.blockTextFallback = appendWecomAgentBlockFallback(state.blockTextFallback, blockVisibleText);
|
|
83
|
+
}
|
|
71
84
|
if (streamingEnabled) {
|
|
72
|
-
state.streamChunkBuffer +=
|
|
85
|
+
state.streamChunkBuffer += blockVisibleText || "";
|
|
73
86
|
await flushStreamingBuffer({ force: false, reason: "block" });
|
|
74
87
|
}
|
|
75
88
|
}
|
|
@@ -85,12 +98,31 @@ export function createWecomAgentDispatchHandlers({
|
|
|
85
98
|
return;
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
const parsedFinal = parseThinkingContent(payload.text);
|
|
102
|
+
const parsedRawFinal = parseThinkingContent(String(payload.rawText ?? payload.text ?? ""));
|
|
103
|
+
const reasoningPayload = applyWecomReasoningPolicy({
|
|
104
|
+
text: markdownToWecomText(parsedFinal.visibleContent).trim(),
|
|
105
|
+
thinkingContent: markdownToWecomText(parsedFinal.thinkingContent).trim(),
|
|
106
|
+
policy: reasoningPolicy,
|
|
107
|
+
transport: "agent",
|
|
108
|
+
phase: "final",
|
|
109
|
+
});
|
|
110
|
+
const rawReasoningPayload = applyWecomReasoningPolicy({
|
|
111
|
+
text: String(parsedRawFinal.visibleContent ?? "").trim(),
|
|
112
|
+
thinkingContent: String(payload.rawThinkingContent ?? parsedRawFinal.thinkingContent ?? "").trim(),
|
|
113
|
+
policy: reasoningPolicy,
|
|
114
|
+
transport: "agent",
|
|
115
|
+
phase: "final",
|
|
116
|
+
});
|
|
117
|
+
const effectiveFinalText = extractWecomReplyDirectives(String(reasoningPayload.text ?? "").trim()).text;
|
|
118
|
+
const richFinalText = extractWecomReplyDirectives(String(rawReasoningPayload.text ?? "").trim()).text;
|
|
119
|
+
|
|
88
120
|
logger?.info?.(`wecom: delivering ${info.kind} reply, length=${payload.text.length}`);
|
|
89
121
|
if (streamingEnabled) {
|
|
90
122
|
await flushStreamingBuffer({ force: true, reason: "final" });
|
|
91
123
|
await state.streamChunkSendChain;
|
|
92
124
|
if (state.streamChunkSentCount > 0) {
|
|
93
|
-
const finalText =
|
|
125
|
+
const finalText = effectiveFinalText;
|
|
94
126
|
const streamedText = markdownToWecomText(state.blockTextFallback).trim();
|
|
95
127
|
const tailText = computeStreamingTailText({ finalText, streamedText });
|
|
96
128
|
if (tailText) {
|
|
@@ -105,9 +137,9 @@ export function createWecomAgentDispatchHandlers({
|
|
|
105
137
|
}
|
|
106
138
|
|
|
107
139
|
if (!deliveredFinalText) {
|
|
108
|
-
const formattedReply =
|
|
140
|
+
const formattedReply = effectiveFinalText;
|
|
109
141
|
const workspaceAutoMedia = await autoSendWorkspaceFilesFromReplyText({
|
|
110
|
-
text: formattedReply,
|
|
142
|
+
text: String(payload.rawText ?? payload.text ?? formattedReply),
|
|
111
143
|
routeAgentId: routedAgentId,
|
|
112
144
|
corpId,
|
|
113
145
|
corpSecret,
|
|
@@ -115,12 +147,32 @@ export function createWecomAgentDispatchHandlers({
|
|
|
115
147
|
toUser: fromUser,
|
|
116
148
|
logger,
|
|
117
149
|
proxyUrl,
|
|
150
|
+
apiBaseUrl,
|
|
118
151
|
});
|
|
119
152
|
const workspaceHints = buildWorkspaceAutoSendHints(workspaceAutoMedia);
|
|
120
153
|
const finalReplyText = [formattedReply, ...workspaceHints].filter(Boolean).join("\n\n");
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
if (typeof deliverAgentReply === "function") {
|
|
155
|
+
const result = await deliverAgentReply({
|
|
156
|
+
api,
|
|
157
|
+
fromUser,
|
|
158
|
+
accountId: runtimeAccountId,
|
|
159
|
+
sessionId,
|
|
160
|
+
text: finalReplyText,
|
|
161
|
+
rawText: [richFinalText, ...workspaceHints].filter(Boolean).join("\n\n"),
|
|
162
|
+
rawThinkingContent: String(rawReasoningPayload.thinkingContent ?? "").trim(),
|
|
163
|
+
routeAgentId: routedAgentId,
|
|
164
|
+
reason: "final-reply",
|
|
165
|
+
});
|
|
166
|
+
state.hasDeliveredReply = true;
|
|
167
|
+
deliveredFinalText = result?.ok === true;
|
|
168
|
+
if (!result?.ok) {
|
|
169
|
+
logger?.warn?.(`wecom: final agent reply deferred to pending delivery session=${sessionId || "n/a"}`);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
await sendTextToUser(finalReplyText);
|
|
173
|
+
state.hasDeliveredReply = true;
|
|
174
|
+
deliveredFinalText = true;
|
|
175
|
+
}
|
|
124
176
|
logger?.info?.(`wecom: sent AI reply to ${fromUser}: ${finalReplyText.slice(0, 50)}...`);
|
|
125
177
|
}
|
|
126
178
|
}
|
|
@@ -136,6 +188,7 @@ export function createWecomAgentDispatchHandlers({
|
|
|
136
188
|
mediaType: payload.mediaType,
|
|
137
189
|
logger,
|
|
138
190
|
proxyUrl,
|
|
191
|
+
apiBaseUrl,
|
|
139
192
|
});
|
|
140
193
|
if (mediaResult.sentCount > 0) {
|
|
141
194
|
state.hasDeliveredReply = true;
|
|
@@ -70,6 +70,30 @@ export async function applyWecomAgentInboundGuards({
|
|
|
70
70
|
const dmPolicy = resolveWecomDmPolicy(api, resolvedAccountId, config);
|
|
71
71
|
const allowFromPolicy = resolveWecomAllowFromPolicy(api, resolvedAccountId, config);
|
|
72
72
|
|
|
73
|
+
if (isGroupChat) {
|
|
74
|
+
const groupPolicyMode = String(groupChatPolicy?.policyMode ?? (groupChatPolicy?.enabled === false ? "deny" : "open"))
|
|
75
|
+
.trim()
|
|
76
|
+
.toLowerCase();
|
|
77
|
+
const groupSenderAllowed =
|
|
78
|
+
isAdminUser ||
|
|
79
|
+
groupPolicyMode === "open" ||
|
|
80
|
+
(groupPolicyMode !== "deny" &&
|
|
81
|
+
(groupChatPolicy.allowFrom.includes("*") ||
|
|
82
|
+
isWecomSenderAllowed({
|
|
83
|
+
senderId: normalizedFromUser,
|
|
84
|
+
allowFrom: groupChatPolicy.allowFrom,
|
|
85
|
+
})));
|
|
86
|
+
if (!groupSenderAllowed) {
|
|
87
|
+
api?.logger?.warn?.(
|
|
88
|
+
`wecom: sender blocked by group policy account=${resolvedAccountId} chatId=${chatId || "unknown"} user=${normalizedFromUser} mode=${groupPolicyMode || "open"}`,
|
|
89
|
+
);
|
|
90
|
+
if (groupChatPolicy?.rejectMessage) {
|
|
91
|
+
await sendTextToUser(groupChatPolicy.rejectMessage);
|
|
92
|
+
}
|
|
93
|
+
return { ok: false, commandBody: nextCommandBody, isAdminUser };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
if (!isGroupChat) {
|
|
74
98
|
const dmAccess = await resolveWecomDirectMessageAccess({
|
|
75
99
|
api,
|
|
@@ -31,12 +31,14 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
31
31
|
resolveWecomAgentRoute,
|
|
32
32
|
seedDynamicAgentWorkspace,
|
|
33
33
|
resolveWecomReplyStreamingPolicy,
|
|
34
|
+
resolveWecomReasoningPolicy,
|
|
34
35
|
asNumber,
|
|
35
36
|
requireEnv,
|
|
36
37
|
getByteLength,
|
|
37
38
|
markdownToWecomText,
|
|
38
39
|
autoSendWorkspaceFilesFromReplyText,
|
|
39
40
|
sendWecomOutboundMediaBatch,
|
|
41
|
+
deliverAgentReply,
|
|
40
42
|
sleep,
|
|
41
43
|
resolveSessionTranscriptFilePath,
|
|
42
44
|
readTranscriptAppendedChunk,
|
|
@@ -50,6 +52,8 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
50
52
|
ACTIVE_LATE_REPLY_WATCHERS,
|
|
51
53
|
resetWecomConversationSession,
|
|
52
54
|
clearSessionStoreEntry,
|
|
55
|
+
markWecomReliableInboundActivity = () => null,
|
|
56
|
+
flushWecomSessionPendingReplies = async () => {},
|
|
53
57
|
} = deps;
|
|
54
58
|
|
|
55
59
|
let lateReplyWatcherRunner = null;
|
|
@@ -94,7 +98,7 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
94
98
|
|
|
95
99
|
const cfg = api.config;
|
|
96
100
|
const runtime = api.runtime;
|
|
97
|
-
const { corpId, corpSecret, agentId, outboundProxy: proxyUrl } = config;
|
|
101
|
+
const { corpId, corpSecret, agentId, outboundProxy: proxyUrl, apiBaseUrl } = config;
|
|
98
102
|
const sendTextToUser = createWecomAgentTextSender({
|
|
99
103
|
sendWecomText,
|
|
100
104
|
corpId,
|
|
@@ -103,17 +107,29 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
103
107
|
toUser: fromUser,
|
|
104
108
|
logger: api.logger,
|
|
105
109
|
proxyUrl,
|
|
110
|
+
apiBaseUrl,
|
|
106
111
|
});
|
|
107
112
|
|
|
108
113
|
try {
|
|
109
114
|
const baseSessionId = buildWecomSessionId(fromUser, config.accountId || accountId || "default");
|
|
115
|
+
markWecomReliableInboundActivity({
|
|
116
|
+
mode: "agent",
|
|
117
|
+
accountId: config.accountId || accountId || "default",
|
|
118
|
+
sessionId: baseSessionId,
|
|
119
|
+
fromUser,
|
|
120
|
+
});
|
|
110
121
|
let sessionId = baseSessionId;
|
|
111
122
|
let routedAgentId = "";
|
|
112
123
|
const normalizedFromUser = String(fromUser ?? "").trim().toLowerCase();
|
|
113
124
|
const fromAddress = `wecom:${normalizedFromUser}`;
|
|
114
125
|
const originalContent = content || "";
|
|
115
126
|
let commandBody = originalContent;
|
|
116
|
-
const groupChatPolicy = resolveWecomGroupChatPolicy(
|
|
127
|
+
const groupChatPolicy = resolveWecomGroupChatPolicy(
|
|
128
|
+
api,
|
|
129
|
+
config.accountId || accountId || "default",
|
|
130
|
+
config,
|
|
131
|
+
isGroupChat ? chatId : "",
|
|
132
|
+
);
|
|
117
133
|
const dynamicAgentPolicy = resolveWecomDynamicAgentPolicy(api);
|
|
118
134
|
api.logger.info?.(`wecom: processing ${msgType} message for session ${sessionId}${isGroupChat ? " (group)" : ""}`);
|
|
119
135
|
|
|
@@ -146,6 +162,7 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
146
162
|
agentId,
|
|
147
163
|
accountId: config.accountId || "default",
|
|
148
164
|
proxyUrl,
|
|
165
|
+
apiBaseUrl,
|
|
149
166
|
chatId,
|
|
150
167
|
isGroupChat,
|
|
151
168
|
},
|
|
@@ -206,6 +223,7 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
206
223
|
corpSecret,
|
|
207
224
|
agentId,
|
|
208
225
|
proxyUrl,
|
|
226
|
+
apiBaseUrl,
|
|
209
227
|
fromUser,
|
|
210
228
|
msgType,
|
|
211
229
|
baseText: msgType === "text" ? commandBody : originalContent,
|
|
@@ -255,6 +273,17 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
255
273
|
const storePath = runtimeContext.storePath;
|
|
256
274
|
const ctxPayload = runtimeContext.ctxPayload;
|
|
257
275
|
const runtimeAccountId = runtimeContext.accountId;
|
|
276
|
+
markWecomReliableInboundActivity({
|
|
277
|
+
mode: "agent",
|
|
278
|
+
accountId: runtimeAccountId,
|
|
279
|
+
sessionId,
|
|
280
|
+
fromUser,
|
|
281
|
+
});
|
|
282
|
+
await flushWecomSessionPendingReplies({
|
|
283
|
+
mode: "agent",
|
|
284
|
+
accountId: runtimeAccountId,
|
|
285
|
+
sessionId,
|
|
286
|
+
});
|
|
258
287
|
|
|
259
288
|
api.logger.info?.(`wecom: dispatching message via agent runtime for session ${sessionId}`);
|
|
260
289
|
await executeWecomAgentDispatchFlow({
|
|
@@ -272,14 +301,17 @@ export function createWecomAgentInboundProcessor(deps = {}) {
|
|
|
272
301
|
corpSecret,
|
|
273
302
|
agentId,
|
|
274
303
|
proxyUrl,
|
|
304
|
+
apiBaseUrl,
|
|
275
305
|
tempPathsToCleanup,
|
|
276
306
|
resolveWecomReplyStreamingPolicy,
|
|
307
|
+
resolveWecomReasoningPolicy,
|
|
277
308
|
asNumber,
|
|
278
309
|
requireEnv,
|
|
279
310
|
getByteLength,
|
|
280
311
|
markdownToWecomText,
|
|
281
312
|
autoSendWorkspaceFilesFromReplyText,
|
|
282
313
|
sendWecomOutboundMediaBatch,
|
|
314
|
+
deliverAgentReply,
|
|
283
315
|
withTimeout,
|
|
284
316
|
isDispatchTimeoutError,
|
|
285
317
|
isAgentFailureText,
|
|
@@ -13,6 +13,8 @@ function isTimeoutLikeReason(reason) {
|
|
|
13
13
|
|
|
14
14
|
export function createWecomAgentLateReplyRuntime({
|
|
15
15
|
dispatchState,
|
|
16
|
+
api,
|
|
17
|
+
fromUser,
|
|
16
18
|
sessionId,
|
|
17
19
|
msgId = "",
|
|
18
20
|
transcriptSessionId = "",
|
|
@@ -21,6 +23,7 @@ export function createWecomAgentLateReplyRuntime({
|
|
|
21
23
|
lateReplyWatchMs,
|
|
22
24
|
lateReplyPollMs,
|
|
23
25
|
sendTextToUser,
|
|
26
|
+
deliverAgentReply = null,
|
|
24
27
|
ensureLateReplyWatcherRunner,
|
|
25
28
|
activeWatchers,
|
|
26
29
|
clearSessionStoreEntry = null,
|
|
@@ -72,7 +75,19 @@ export function createWecomAgentLateReplyRuntime({
|
|
|
72
75
|
dispatchState.hasDeliveredReply = true;
|
|
73
76
|
const reasonText = String(reason ?? "unknown").slice(0, 160);
|
|
74
77
|
try {
|
|
75
|
-
|
|
78
|
+
const fallbackText = `抱歉,当前模型请求超时或网络不稳定,请稍后重试。\n故障信息: ${reasonText}`;
|
|
79
|
+
if (typeof deliverAgentReply === "function") {
|
|
80
|
+
await deliverAgentReply({
|
|
81
|
+
api,
|
|
82
|
+
fromUser,
|
|
83
|
+
accountId,
|
|
84
|
+
sessionId,
|
|
85
|
+
text: fallbackText,
|
|
86
|
+
reason: "late-timeout-fallback",
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
await sendTextToUser(fallbackText);
|
|
90
|
+
}
|
|
76
91
|
} finally {
|
|
77
92
|
await autoResetTimedOutSession(reasonText);
|
|
78
93
|
}
|
|
@@ -100,7 +115,20 @@ export function createWecomAgentLateReplyRuntime({
|
|
|
100
115
|
markDelivered: () => {
|
|
101
116
|
dispatchState.hasDeliveredReply = true;
|
|
102
117
|
},
|
|
103
|
-
sendText: async (text) =>
|
|
118
|
+
sendText: async (text) => {
|
|
119
|
+
if (typeof deliverAgentReply === "function") {
|
|
120
|
+
await deliverAgentReply({
|
|
121
|
+
api,
|
|
122
|
+
fromUser,
|
|
123
|
+
accountId,
|
|
124
|
+
sessionId,
|
|
125
|
+
text,
|
|
126
|
+
reason: "late-reply",
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
await sendTextToUser(text);
|
|
131
|
+
},
|
|
104
132
|
onFailureFallback: async (err) => sendFailureFallback(err),
|
|
105
133
|
}).finally(() => {
|
|
106
134
|
lateReplyWatcherPromise = null;
|
|
@@ -12,6 +12,7 @@ export function createWecomAgentTextSender({
|
|
|
12
12
|
toUser,
|
|
13
13
|
logger,
|
|
14
14
|
proxyUrl,
|
|
15
|
+
apiBaseUrl,
|
|
15
16
|
} = {}) {
|
|
16
17
|
assertFunction("sendWecomText", sendWecomText);
|
|
17
18
|
|
|
@@ -24,6 +25,7 @@ export function createWecomAgentTextSender({
|
|
|
24
25
|
text,
|
|
25
26
|
logger,
|
|
26
27
|
proxyUrl,
|
|
28
|
+
apiBaseUrl,
|
|
27
29
|
});
|
|
28
30
|
};
|
|
29
31
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildWecomApiUrl, isWecomApiUrl, normalizeWecomApiBaseUrl } from "./network-config.js";
|
|
2
|
+
|
|
1
3
|
export function createWecomApiClientCore({
|
|
2
4
|
fetchImpl = fetch,
|
|
3
5
|
proxyAgentCtor,
|
|
@@ -11,17 +13,6 @@ export function createWecomApiClientCore({
|
|
|
11
13
|
const proxyDispatcherCache = new Map();
|
|
12
14
|
const invalidProxyCache = new Set();
|
|
13
15
|
|
|
14
|
-
function isWecomApiUrl(url) {
|
|
15
|
-
const raw = typeof url === "string" ? url : String(url ?? "");
|
|
16
|
-
if (!raw) return false;
|
|
17
|
-
try {
|
|
18
|
-
const parsed = new URL(raw);
|
|
19
|
-
return parsed.hostname === "qyapi.weixin.qq.com";
|
|
20
|
-
} catch {
|
|
21
|
-
return raw.includes("qyapi.weixin.qq.com");
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
function isLikelyHttpProxyUrl(proxyUrl) {
|
|
26
17
|
return /^https?:\/\/\S+$/i.test(proxyUrl);
|
|
27
18
|
}
|
|
@@ -73,11 +64,11 @@ export function createWecomApiClientCore({
|
|
|
73
64
|
|
|
74
65
|
function attachWecomProxyDispatcher(url, options = {}, { proxyUrl, logger } = {}) {
|
|
75
66
|
const shouldForceProxy = options?.forceProxy === true;
|
|
76
|
-
if (!isWecomApiUrl(url) && !shouldForceProxy) return options;
|
|
67
|
+
if (!isWecomApiUrl(url, { apiBaseUrl: options?.apiBaseUrl }) && !shouldForceProxy) return options;
|
|
77
68
|
if (options?.dispatcher) return options;
|
|
78
69
|
const dispatcher = resolveWecomProxyDispatcher(proxyUrl, logger);
|
|
79
70
|
if (!dispatcher) return options;
|
|
80
|
-
const { forceProxy, ...restOptions } = options || {};
|
|
71
|
+
const { forceProxy, apiBaseUrl, ...restOptions } = options || {};
|
|
81
72
|
return {
|
|
82
73
|
...restOptions,
|
|
83
74
|
dispatcher,
|
|
@@ -120,8 +111,9 @@ export function createWecomApiClientCore({
|
|
|
120
111
|
throw lastError || new Error(`Fetch failed after ${maxRetries} retries`);
|
|
121
112
|
}
|
|
122
113
|
|
|
123
|
-
async function getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger }) {
|
|
124
|
-
const
|
|
114
|
+
async function getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger, apiBaseUrl }) {
|
|
115
|
+
const normalizedApiBaseUrl = normalizeWecomApiBaseUrl(apiBaseUrl);
|
|
116
|
+
const cacheKey = `${corpId}:${corpSecret}:${normalizedApiBaseUrl}`;
|
|
125
117
|
let cache = accessTokenCaches.get(cacheKey);
|
|
126
118
|
|
|
127
119
|
if (!cache) {
|
|
@@ -140,8 +132,17 @@ export function createWecomApiClientCore({
|
|
|
140
132
|
|
|
141
133
|
cache.refreshPromise = (async () => {
|
|
142
134
|
try {
|
|
143
|
-
const tokenUrl =
|
|
144
|
-
|
|
135
|
+
const tokenUrl = buildWecomApiUrl(
|
|
136
|
+
`/cgi-bin/gettoken?corpid=${encodeURIComponent(corpId)}&corpsecret=${encodeURIComponent(corpSecret)}`,
|
|
137
|
+
{ apiBaseUrl: normalizedApiBaseUrl },
|
|
138
|
+
);
|
|
139
|
+
const tokenRes = await fetchWithRetry(
|
|
140
|
+
tokenUrl,
|
|
141
|
+
{ apiBaseUrl: normalizedApiBaseUrl },
|
|
142
|
+
3,
|
|
143
|
+
1000,
|
|
144
|
+
{ proxyUrl, logger },
|
|
145
|
+
);
|
|
145
146
|
const tokenJson = await tokenRes.json();
|
|
146
147
|
if (!tokenJson?.access_token) {
|
|
147
148
|
throw new Error(`WeCom gettoken failed: ${JSON.stringify(tokenJson)}`);
|
|
@@ -166,6 +167,7 @@ export function createWecomApiClientCore({
|
|
|
166
167
|
chatId,
|
|
167
168
|
msgType,
|
|
168
169
|
payload,
|
|
170
|
+
apiBaseUrl,
|
|
169
171
|
}) {
|
|
170
172
|
const isAppChat = Boolean(chatId);
|
|
171
173
|
if (!isAppChat && !toUser && !toParty && !toTag) {
|
|
@@ -173,7 +175,10 @@ export function createWecomApiClientCore({
|
|
|
173
175
|
}
|
|
174
176
|
if (isAppChat) {
|
|
175
177
|
return {
|
|
176
|
-
sendUrl:
|
|
178
|
+
sendUrl: buildWecomApiUrl(
|
|
179
|
+
`/cgi-bin/appchat/send?access_token=${encodeURIComponent(accessToken)}`,
|
|
180
|
+
{ apiBaseUrl },
|
|
181
|
+
),
|
|
177
182
|
body: {
|
|
178
183
|
chatid: chatId,
|
|
179
184
|
msgtype: msgType,
|
|
@@ -184,7 +189,10 @@ export function createWecomApiClientCore({
|
|
|
184
189
|
};
|
|
185
190
|
}
|
|
186
191
|
return {
|
|
187
|
-
sendUrl:
|
|
192
|
+
sendUrl: buildWecomApiUrl(
|
|
193
|
+
`/cgi-bin/message/send?access_token=${encodeURIComponent(accessToken)}`,
|
|
194
|
+
{ apiBaseUrl },
|
|
195
|
+
),
|
|
188
196
|
body: {
|
|
189
197
|
touser: toUser,
|
|
190
198
|
toparty: toParty,
|