@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.en.md +181 -14
  3. package/README.md +201 -16
  4. package/docs/channels/wecom.md +137 -1
  5. package/openclaw.plugin.json +688 -6
  6. package/package.json +204 -4
  7. package/scripts/wecom-agent-selfcheck.mjs +775 -0
  8. package/scripts/wecom-bot-longconn-probe.mjs +582 -0
  9. package/scripts/wecom-bot-selfcheck.mjs +952 -0
  10. package/scripts/wecom-callback-matrix.mjs +224 -0
  11. package/scripts/wecom-doctor.mjs +1407 -0
  12. package/scripts/wecom-e2e-scenario.mjs +333 -0
  13. package/scripts/wecom-migrate.mjs +261 -0
  14. package/scripts/wecom-quickstart.mjs +1824 -0
  15. package/scripts/wecom-release-check.mjs +232 -0
  16. package/scripts/wecom-remote-e2e.mjs +310 -0
  17. package/scripts/wecom-selfcheck.mjs +1255 -0
  18. package/scripts/wecom-smoke.sh +74 -0
  19. package/src/core/delivery-router.js +21 -0
  20. package/src/core.js +619 -30
  21. package/src/wecom/account-config-core.js +27 -1
  22. package/src/wecom/account-config.js +19 -2
  23. package/src/wecom/agent-dispatch-executor.js +11 -0
  24. package/src/wecom/agent-dispatch-handlers.js +61 -8
  25. package/src/wecom/agent-inbound-guards.js +24 -0
  26. package/src/wecom/agent-inbound-processor.js +34 -2
  27. package/src/wecom/agent-late-reply-runtime.js +30 -2
  28. package/src/wecom/agent-text-sender.js +2 -0
  29. package/src/wecom/api-client-core.js +27 -19
  30. package/src/wecom/api-client-media.js +16 -7
  31. package/src/wecom/api-client-send-text.js +4 -0
  32. package/src/wecom/api-client-send-typed.js +4 -1
  33. package/src/wecom/api-client-senders.js +41 -3
  34. package/src/wecom/api-client.js +1 -0
  35. package/src/wecom/bot-dispatch-fallback.js +18 -3
  36. package/src/wecom/bot-dispatch-handlers.js +47 -10
  37. package/src/wecom/bot-inbound-dispatch-runtime.js +3 -0
  38. package/src/wecom/bot-inbound-executor-helpers.js +11 -1
  39. package/src/wecom/bot-inbound-executor.js +24 -0
  40. package/src/wecom/bot-inbound-guards.js +31 -1
  41. package/src/wecom/channel-config-schema.js +132 -0
  42. package/src/wecom/channel-plugin.js +348 -7
  43. package/src/wecom/command-handlers.js +102 -11
  44. package/src/wecom/command-status-text.js +206 -0
  45. package/src/wecom/doc-client.js +7 -1
  46. package/src/wecom/inbound-content-handler-file-video-link.js +4 -0
  47. package/src/wecom/inbound-content-handler-image-voice.js +6 -0
  48. package/src/wecom/inbound-content.js +5 -0
  49. package/src/wecom/installer-api.js +910 -0
  50. package/src/wecom/media-download.js +2 -2
  51. package/src/wecom/migration-diagnostics.js +816 -0
  52. package/src/wecom/network-config.js +91 -0
  53. package/src/wecom/observability-metrics.js +9 -3
  54. package/src/wecom/outbound-agent-delivery.js +313 -0
  55. package/src/wecom/outbound-agent-media-sender.js +37 -7
  56. package/src/wecom/outbound-agent-push.js +1 -0
  57. package/src/wecom/outbound-delivery.js +129 -12
  58. package/src/wecom/outbound-stream-msg-item.js +25 -2
  59. package/src/wecom/outbound-webhook-delivery.js +19 -0
  60. package/src/wecom/outbound-webhook-media.js +30 -6
  61. package/src/wecom/pending-reply-manager.js +143 -0
  62. package/src/wecom/plugin-account-policy-services.js +26 -0
  63. package/src/wecom/plugin-base-services.js +58 -0
  64. package/src/wecom/plugin-constants.js +1 -1
  65. package/src/wecom/plugin-delivery-inbound-services.js +25 -0
  66. package/src/wecom/plugin-processing-deps.js +7 -0
  67. package/src/wecom/plugin-route-runtime-deps.js +1 -0
  68. package/src/wecom/plugin-services.js +87 -0
  69. package/src/wecom/policy-resolvers.js +93 -20
  70. package/src/wecom/quickstart-metadata.js +1247 -0
  71. package/src/wecom/reasoning-visibility.js +104 -0
  72. package/src/wecom/register-runtime.js +10 -0
  73. package/src/wecom/reliable-delivery-persistence.js +138 -0
  74. package/src/wecom/reliable-delivery.js +642 -0
  75. package/src/wecom/reply-output-policy.js +171 -0
  76. package/src/wecom/text-inbound-scheduler.js +6 -1
  77. package/src/wecom/workspace-auto-sender.js +2 -0
@@ -1,3 +1,5 @@
1
+ import { buildWecomApiUrl } from "./network-config.js";
2
+
1
3
  export function createWecomApiMediaClient({
2
4
  fetchWithRetry,
3
5
  getWecomAccessToken,
@@ -9,9 +11,12 @@ export function createWecomApiMediaClient({
9
11
  throw new Error("createWecomApiMediaClient: getWecomAccessToken is required");
10
12
  }
11
13
 
12
- async function uploadWecomMedia({ corpId, corpSecret, type, buffer, filename, logger, proxyUrl }) {
13
- const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger });
14
- const uploadUrl = `https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=${encodeURIComponent(accessToken)}&type=${encodeURIComponent(type)}`;
14
+ async function uploadWecomMedia({ corpId, corpSecret, type, buffer, filename, logger, proxyUrl, apiBaseUrl }) {
15
+ const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger, apiBaseUrl });
16
+ const uploadUrl = buildWecomApiUrl(
17
+ `/cgi-bin/media/upload?access_token=${encodeURIComponent(accessToken)}&type=${encodeURIComponent(type)}`,
18
+ { apiBaseUrl },
19
+ );
15
20
 
16
21
  const boundary = `----WecomMediaUpload${Date.now()}`;
17
22
  const header = Buffer.from(
@@ -30,6 +35,7 @@ export function createWecomApiMediaClient({
30
35
  "Content-Type": `multipart/form-data; boundary=${boundary}`,
31
36
  },
32
37
  body,
38
+ apiBaseUrl,
33
39
  },
34
40
  3,
35
41
  1000,
@@ -43,11 +49,14 @@ export function createWecomApiMediaClient({
43
49
  return json.media_id;
44
50
  }
45
51
 
46
- async function downloadWecomMedia({ corpId, corpSecret, mediaId, proxyUrl, logger }) {
47
- const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger });
48
- const mediaUrl = `https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=${encodeURIComponent(accessToken)}&media_id=${encodeURIComponent(mediaId)}`;
52
+ async function downloadWecomMedia({ corpId, corpSecret, mediaId, proxyUrl, logger, apiBaseUrl }) {
53
+ const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger, apiBaseUrl });
54
+ const mediaUrl = buildWecomApiUrl(
55
+ `/cgi-bin/media/get?access_token=${encodeURIComponent(accessToken)}&media_id=${encodeURIComponent(mediaId)}`,
56
+ { apiBaseUrl },
57
+ );
49
58
 
50
- const res = await fetchWithRetry(mediaUrl, {}, 3, 1000, { proxyUrl, logger });
59
+ const res = await fetchWithRetry(mediaUrl, { apiBaseUrl }, 3, 1000, { proxyUrl, logger });
51
60
  if (!res.ok) {
52
61
  throw new Error(`Failed to download media: ${res.status}`);
53
62
  }
@@ -42,6 +42,7 @@ export function createWecomTextSender({
42
42
  text,
43
43
  logger,
44
44
  proxyUrl,
45
+ apiBaseUrl,
45
46
  }) {
46
47
  const sendJson = await sendWecomTypedMessage({
47
48
  corpId,
@@ -57,6 +58,7 @@ export function createWecomTextSender({
57
58
  },
58
59
  logger,
59
60
  proxyUrl,
61
+ apiBaseUrl,
60
62
  errorPrefix: "",
61
63
  });
62
64
  const isAppChat = Boolean(chatId);
@@ -76,6 +78,7 @@ export function createWecomTextSender({
76
78
  text,
77
79
  logger,
78
80
  proxyUrl,
81
+ apiBaseUrl,
79
82
  }) {
80
83
  const targetKey = buildTargetKey({ corpId, agentId, toUser, toParty, toTag, chatId });
81
84
  return enqueueTargetSend(targetKey, async () => {
@@ -96,6 +99,7 @@ export function createWecomTextSender({
96
99
  text: chunks[i],
97
100
  logger,
98
101
  proxyUrl,
102
+ apiBaseUrl,
99
103
  });
100
104
  if (i < chunks.length - 1) {
101
105
  // eslint-disable-next-line no-await-in-loop
@@ -29,10 +29,11 @@ export function createWecomTypedMessageSender({
29
29
  payload,
30
30
  logger,
31
31
  proxyUrl,
32
+ apiBaseUrl,
32
33
  errorPrefix,
33
34
  }) {
34
35
  return apiLimiter.execute(async () => {
35
- const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger });
36
+ const accessToken = await getWecomAccessToken({ corpId, corpSecret, proxyUrl, logger, apiBaseUrl });
36
37
  const { sendUrl, body, isAppChat } = buildWecomMessageSendRequest({
37
38
  accessToken,
38
39
  agentId,
@@ -42,6 +43,7 @@ export function createWecomTypedMessageSender({
42
43
  chatId,
43
44
  msgType,
44
45
  payload,
46
+ apiBaseUrl,
45
47
  });
46
48
  const sendRes = await fetchWithRetry(
47
49
  sendUrl,
@@ -49,6 +51,7 @@ export function createWecomTypedMessageSender({
49
51
  method: "POST",
50
52
  headers: { "Content-Type": "application/json" },
51
53
  body: JSON.stringify(body),
54
+ apiBaseUrl,
52
55
  },
53
56
  3,
54
57
  1000,
@@ -24,7 +24,39 @@ export function createWecomApiSenders({
24
24
  sendWecomTypedMessage,
25
25
  });
26
26
 
27
- async function sendWecomImage({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl }) {
27
+ async function sendWecomMarkdown({
28
+ corpId,
29
+ corpSecret,
30
+ agentId,
31
+ toUser,
32
+ toParty,
33
+ toTag,
34
+ chatId,
35
+ content,
36
+ logger,
37
+ proxyUrl,
38
+ apiBaseUrl,
39
+ }) {
40
+ return sendWecomTypedMessage({
41
+ corpId,
42
+ corpSecret,
43
+ agentId,
44
+ toUser,
45
+ toParty,
46
+ toTag,
47
+ chatId,
48
+ msgType: "markdown",
49
+ payload: {
50
+ markdown: { content: String(content ?? "") },
51
+ },
52
+ logger,
53
+ proxyUrl,
54
+ apiBaseUrl,
55
+ errorPrefix: "WeCom markdown send failed",
56
+ });
57
+ }
58
+
59
+ async function sendWecomImage({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl, apiBaseUrl }) {
28
60
  return sendWecomTypedMessage({
29
61
  corpId,
30
62
  corpSecret,
@@ -39,6 +71,7 @@ export function createWecomApiSenders({
39
71
  },
40
72
  logger,
41
73
  proxyUrl,
74
+ apiBaseUrl,
42
75
  errorPrefix: "WeCom image send failed",
43
76
  });
44
77
  }
@@ -56,6 +89,7 @@ export function createWecomApiSenders({
56
89
  description,
57
90
  logger,
58
91
  proxyUrl,
92
+ apiBaseUrl,
59
93
  }) {
60
94
  const videoPayload = {
61
95
  media_id: mediaId,
@@ -76,11 +110,12 @@ export function createWecomApiSenders({
76
110
  },
77
111
  logger,
78
112
  proxyUrl,
113
+ apiBaseUrl,
79
114
  errorPrefix: "WeCom video send failed",
80
115
  });
81
116
  }
82
117
 
83
- async function sendWecomFile({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl }) {
118
+ async function sendWecomFile({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl, apiBaseUrl }) {
84
119
  return sendWecomTypedMessage({
85
120
  corpId,
86
121
  corpSecret,
@@ -95,11 +130,12 @@ export function createWecomApiSenders({
95
130
  },
96
131
  logger,
97
132
  proxyUrl,
133
+ apiBaseUrl,
98
134
  errorPrefix: "WeCom file send failed",
99
135
  });
100
136
  }
101
137
 
102
- async function sendWecomVoice({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl }) {
138
+ async function sendWecomVoice({ corpId, corpSecret, agentId, toUser, toParty, toTag, chatId, mediaId, logger, proxyUrl, apiBaseUrl }) {
103
139
  return sendWecomTypedMessage({
104
140
  corpId,
105
141
  corpSecret,
@@ -114,12 +150,14 @@ export function createWecomApiSenders({
114
150
  },
115
151
  logger,
116
152
  proxyUrl,
153
+ apiBaseUrl,
117
154
  errorPrefix: "WeCom voice send failed",
118
155
  });
119
156
  }
120
157
 
121
158
  return {
122
159
  sendWecomText,
160
+ sendWecomMarkdown,
123
161
  sendWecomImage,
124
162
  sendWecomVideo,
125
163
  sendWecomFile,
@@ -46,6 +46,7 @@ export function createWecomApiClient({
46
46
  getWecomAccessToken: core.getWecomAccessToken,
47
47
  buildWecomMessageSendRequest: core.buildWecomMessageSendRequest,
48
48
  sendWecomText: senders.sendWecomText,
49
+ sendWecomMarkdown: senders.sendWecomMarkdown,
49
50
  uploadWecomMedia: media.uploadWecomMedia,
50
51
  sendWecomImage: senders.sendWecomImage,
51
52
  sendWecomVideo: senders.sendWecomVideo,
@@ -1,4 +1,5 @@
1
1
  import { parseThinkingContent } from "./thinking-parser.js";
2
+ import { applyWecomReasoningPolicy } from "./reasoning-visibility.js";
2
3
 
3
4
  function assertFunction(name, value) {
4
5
  if (typeof value !== "function") {
@@ -6,13 +7,24 @@ function assertFunction(name, value) {
6
7
  }
7
8
  }
8
9
 
9
- export function buildWecomBotVisibleFallbackPayload(rawText = "", markdownToWecomText = (text) => String(text ?? "")) {
10
+ export function buildWecomBotVisibleFallbackPayload(
11
+ rawText = "",
12
+ markdownToWecomText = (text) => String(text ?? ""),
13
+ reasoningPolicy = {},
14
+ ) {
10
15
  const parsed = parseThinkingContent(rawText);
11
16
  const visibleContent = markdownToWecomText(parsed.visibleContent).trim();
12
17
  const thinkingContent = markdownToWecomText(parsed.thinkingContent).trim();
13
- return {
18
+ const resolved = applyWecomReasoningPolicy({
14
19
  text: visibleContent,
15
20
  thinkingContent,
21
+ policy: reasoningPolicy,
22
+ transport: "bot",
23
+ phase: "final",
24
+ });
25
+ return {
26
+ text: resolved.text,
27
+ thinkingContent: resolved.thinkingContent,
16
28
  };
17
29
  }
18
30
 
@@ -25,6 +37,7 @@ export async function handleWecomBotPostDispatchFallback({
25
37
  markdownToWecomText,
26
38
  safeDeliverReply,
27
39
  startLateReplyWatcher,
40
+ reasoningPolicy = {},
28
41
  } = {}) {
29
42
  if (!dispatchState || typeof dispatchState !== "object") {
30
43
  throw new Error("handleWecomBotPostDispatchFallback: dispatchState is required");
@@ -41,6 +54,7 @@ export async function handleWecomBotPostDispatchFallback({
41
54
  const { text: fallback, thinkingContent } = buildWecomBotVisibleFallbackPayload(
42
55
  dispatchState.blockText,
43
56
  markdownToWecomText,
57
+ reasoningPolicy,
44
58
  );
45
59
  if (fallback || thinkingContent) {
46
60
  await safeDeliverReply(
@@ -81,6 +95,7 @@ export async function handleWecomBotDispatchError({
81
95
  readTranscriptFallbackResult,
82
96
  safeDeliverReply,
83
97
  markTranscriptReplyDelivered,
98
+ reasoningPolicy = {},
84
99
  } = {}) {
85
100
  assertFunction("isDispatchTimeoutError", isDispatchTimeoutError);
86
101
  assertFunction("startLateReplyWatcher", startLateReplyWatcher);
@@ -92,7 +107,7 @@ export async function handleWecomBotDispatchError({
92
107
 
93
108
  api?.logger?.warn?.(`wecom(bot): processing failed: ${String(err?.message || err)}`);
94
109
  if (dispatchState && typeof dispatchState === "object" && dispatchState.streamFinished !== true) {
95
- const partialPayload = buildWecomBotVisibleFallbackPayload(dispatchState.blockText, markdownToWecomText);
110
+ const partialPayload = buildWecomBotVisibleFallbackPayload(dispatchState.blockText, markdownToWecomText, reasoningPolicy);
96
111
  if (partialPayload.text || partialPayload.thinkingContent) {
97
112
  const delivered = await safeDeliverReply(partialPayload, "timeout-partial-fallback");
98
113
  if (delivered) return true;
@@ -1,4 +1,6 @@
1
1
  import { parseThinkingContent } from "./thinking-parser.js";
2
+ import { applyWecomReasoningPolicy } from "./reasoning-visibility.js";
3
+ import { extractWecomReplyDirectives } from "./reply-output-policy.js";
2
4
 
3
5
  function assertFunction(name, value) {
4
6
  if (typeof value !== "function") {
@@ -30,6 +32,7 @@ export function createWecomBotDispatchHandlers({
30
32
  markdownToWecomText,
31
33
  isAgentFailureText,
32
34
  safeDeliverReply,
35
+ reasoningPolicy = {},
33
36
  } = {}) {
34
37
  if (!state || typeof state !== "object") {
35
38
  throw new Error("createWecomBotDispatchHandlers: state is required");
@@ -46,14 +49,46 @@ export function createWecomBotDispatchHandlers({
46
49
 
47
50
  const logger = api?.logger;
48
51
 
49
- const buildThinkingState = (rawText) => {
52
+ const buildThinkingState = (rawText, rawThinkingOverride = "") => {
50
53
  const parsed = parseThinkingContent(rawText);
51
54
  const visibleRaw = String(parsed.visibleContent ?? "").trim();
52
55
  const thinkingRaw = String(parsed.thinkingContent ?? "").trim();
56
+ const visibleText = visibleRaw ? markdownToWecomText(visibleRaw).trim() : "";
57
+ const thinkingContent = (thinkingRaw ? markdownToWecomText(thinkingRaw).trim() : "") || String(rawThinkingOverride ?? "").trim();
58
+ const rawThinkingContent = String(rawThinkingOverride ?? "").trim() || thinkingRaw;
59
+ const cleanedStreamPayload = applyWecomReasoningPolicy({
60
+ text: visibleText,
61
+ thinkingContent,
62
+ policy: reasoningPolicy,
63
+ transport: "bot",
64
+ phase: "stream",
65
+ });
66
+ const cleanedFinalPayload = applyWecomReasoningPolicy({
67
+ text: visibleText,
68
+ thinkingContent,
69
+ policy: reasoningPolicy,
70
+ transport: "bot",
71
+ phase: "final",
72
+ });
73
+ const rawFinalPayload = applyWecomReasoningPolicy({
74
+ text: visibleRaw,
75
+ thinkingContent: rawThinkingContent,
76
+ policy: reasoningPolicy,
77
+ transport: "bot",
78
+ phase: "final",
79
+ });
53
80
  return {
54
81
  rawText: String(rawText ?? ""),
55
- visibleText: visibleRaw ? markdownToWecomText(visibleRaw).trim() : "",
56
- thinkingContent: thinkingRaw ? markdownToWecomText(thinkingRaw).trim() : "",
82
+ streamPayload: {
83
+ ...cleanedStreamPayload,
84
+ text: extractWecomReplyDirectives(cleanedStreamPayload.text).text,
85
+ },
86
+ finalPayload: {
87
+ ...cleanedFinalPayload,
88
+ text: extractWecomReplyDirectives(cleanedFinalPayload.text).text,
89
+ rawText: String(rawFinalPayload.text ?? "").trim(),
90
+ rawThinkingContent: String(rawFinalPayload.thinkingContent ?? "").trim(),
91
+ },
57
92
  };
58
93
  };
59
94
 
@@ -74,10 +109,10 @@ export function createWecomBotDispatchHandlers({
74
109
  if (!payload?.text) return;
75
110
  state.blockText = normalizeWecomBotBlockText(state.blockText, payload.text);
76
111
  const blockState = buildThinkingState(state.blockText);
77
- updateBotStream(streamId, blockState.visibleText, {
112
+ updateBotStream(streamId, blockState.streamPayload.text, {
78
113
  append: false,
79
114
  finished: false,
80
- thinkingContent: blockState.thinkingContent,
115
+ thinkingContent: blockState.streamPayload.thinkingContent,
81
116
  });
82
117
  if (typeof pushWecomBotLongConnectionStreamUpdate === "function") {
83
118
  try {
@@ -85,9 +120,9 @@ export function createWecomBotDispatchHandlers({
85
120
  accountId,
86
121
  sessionId,
87
122
  streamId,
88
- content: blockState.visibleText,
123
+ content: blockState.streamPayload.text,
89
124
  finish: false,
90
- thinkingContent: blockState.thinkingContent,
125
+ thinkingContent: blockState.streamPayload.thinkingContent,
91
126
  });
92
127
  } catch (err) {
93
128
  logger?.warn?.(
@@ -104,11 +139,13 @@ export function createWecomBotDispatchHandlers({
104
139
  return;
105
140
  }
106
141
  const finalState = buildThinkingState(payload.text);
107
- if (finalState.visibleText || finalState.thinkingContent) {
142
+ if (finalState.finalPayload.text || finalState.finalPayload.thinkingContent) {
108
143
  state.streamFinished = await safeDeliverReply(
109
144
  {
110
- text: finalState.visibleText,
111
- thinkingContent: finalState.thinkingContent,
145
+ text: finalState.finalPayload.text,
146
+ thinkingContent: finalState.finalPayload.thinkingContent,
147
+ rawText: finalState.finalPayload.rawText,
148
+ rawThinkingContent: finalState.finalPayload.rawThinkingContent,
112
149
  },
113
150
  "final",
114
151
  );
@@ -35,6 +35,7 @@ export async function executeWecomBotDispatchRuntime({
35
35
  markdownToWecomText,
36
36
  isAgentFailureText,
37
37
  safeDeliverReply,
38
+ resolveWecomReasoningPolicy,
38
39
  markTranscriptReplyDelivered,
39
40
  ACTIVE_LATE_REPLY_WATCHERS,
40
41
  ensureTranscriptFallbackReader,
@@ -94,6 +95,7 @@ export async function executeWecomBotDispatchRuntime({
94
95
  markdownToWecomText,
95
96
  isAgentFailureText,
96
97
  safeDeliverReply,
98
+ reasoningPolicy: resolveWecomReasoningPolicy?.(api) ?? {},
97
99
  });
98
100
 
99
101
  await withTimeout(
@@ -129,6 +131,7 @@ export async function executeWecomBotDispatchRuntime({
129
131
  markdownToWecomText,
130
132
  safeDeliverReply,
131
133
  startLateReplyWatcher,
134
+ reasoningPolicy: resolveWecomReasoningPolicy?.(api) ?? {},
132
135
  });
133
136
 
134
137
  return {
@@ -90,6 +90,7 @@ export function assertWecomBotInboundFlowDeps({ api, ...deps } = {}) {
90
90
  "resolveWecomCommandPolicy",
91
91
  "resolveWecomAllowFromPolicy",
92
92
  "resolveWecomDmPolicy",
93
+ "resolveWecomReasoningPolicy",
93
94
  "isWecomSenderAllowed",
94
95
  "extractLeadingSlashCommand",
95
96
  "buildWecomBotHelpText",
@@ -117,6 +118,7 @@ export function createWecomBotInboundFlowState({
117
118
  api,
118
119
  accountId = "default",
119
120
  fromUser,
121
+ chatId = "",
120
122
  content,
121
123
  imageEntries,
122
124
  imageUrls,
@@ -172,7 +174,10 @@ export function createWecomBotInboundFlowState({
172
174
  content: String(quote.content ?? "").trim(),
173
175
  }
174
176
  : null,
175
- groupChatPolicy: normalizeWecomBotGroupChatPolicy(resolveWecomGroupChatPolicy(api), api?.logger),
177
+ groupChatPolicy: normalizeWecomBotGroupChatPolicy(
178
+ resolveWecomGroupChatPolicy(api, normalizedAccountId, {}, chatId),
179
+ api?.logger,
180
+ ),
176
181
  dynamicAgentPolicy: resolveWecomDynamicAgentPolicy(api),
177
182
  isAdminUser: false,
178
183
  };
@@ -203,7 +208,9 @@ export function createWecomBotSafeReplyHelpers({
203
208
  ? reply
204
209
  : { text: "" };
205
210
  const contentText = String(normalizedReply.text ?? "").trim();
211
+ const rawContentText = String(normalizedReply.rawText ?? contentText).trim();
206
212
  const thinkingContent = String(normalizedReply.thinkingContent ?? "").trim();
213
+ const rawThinkingContent = String(normalizedReply.rawThinkingContent ?? thinkingContent).trim();
207
214
  const replyMediaUrls = normalizeWecomBotOutboundMediaUrls(normalizedReply);
208
215
  if (!contentText && replyMediaUrls.length === 0 && !thinkingContent) return false;
209
216
  const result = await deliverBotReplyText({
@@ -214,7 +221,10 @@ export function createWecomBotSafeReplyHelpers({
214
221
  streamId,
215
222
  responseUrl,
216
223
  text: contentText,
224
+ rawText: rawContentText,
217
225
  thinkingContent,
226
+ rawThinkingContent,
227
+ routeAgentId: state.routedAgentId,
218
228
  mediaUrls: replyMediaUrls,
219
229
  mediaType: String(normalizedReply.mediaType ?? "").trim().toLowerCase() || undefined,
220
230
  reason,
@@ -45,6 +45,7 @@ export async function executeWecomBotInboundFlow(payload = {}) {
45
45
  resolveWecomCommandPolicy,
46
46
  resolveWecomAllowFromPolicy,
47
47
  resolveWecomDmPolicy,
48
+ resolveWecomReasoningPolicy,
48
49
  isWecomSenderAllowed,
49
50
  extractLeadingSlashCommand,
50
51
  buildWecomBotHelpText,
@@ -66,6 +67,8 @@ export async function executeWecomBotInboundFlow(payload = {}) {
66
67
  ensureTranscriptFallbackReader,
67
68
  resetWecomConversationSession,
68
69
  clearSessionStoreEntry,
70
+ markWecomReliableInboundActivity = () => null,
71
+ flushWecomSessionPendingReplies = async () => {},
69
72
  } = payload;
70
73
 
71
74
  assertWecomBotInboundFlowDeps({
@@ -77,6 +80,7 @@ export async function executeWecomBotInboundFlow(payload = {}) {
77
80
  api,
78
81
  accountId,
79
82
  fromUser,
83
+ chatId,
80
84
  content,
81
85
  imageEntries,
82
86
  imageUrls,
@@ -93,6 +97,12 @@ export async function executeWecomBotInboundFlow(payload = {}) {
93
97
  resolveWecomGroupChatPolicy,
94
98
  resolveWecomDynamicAgentPolicy,
95
99
  });
100
+ markWecomReliableInboundActivity({
101
+ mode: "bot",
102
+ accountId: state.accountId,
103
+ sessionId: state.baseSessionId,
104
+ fromUser,
105
+ });
96
106
  const { runtime, cfg } = state;
97
107
  const { safeFinishStream, safeDeliverReply } = createWecomBotSafeReplyHelpers({
98
108
  api,
@@ -129,10 +139,12 @@ export async function executeWecomBotInboundFlow(payload = {}) {
129
139
  api,
130
140
  accountId: state.accountId,
131
141
  fromUser,
142
+ chatId,
132
143
  isGroupChat,
133
144
  msgType,
134
145
  commandBody: state.commandBody,
135
146
  normalizedFromUser: state.normalizedFromUser,
147
+ groupChatPolicy: state.groupChatPolicy,
136
148
  resolveWecomCommandPolicy,
137
149
  resolveWecomAllowFromPolicy,
138
150
  resolveWecomDmPolicy,
@@ -231,6 +243,17 @@ export async function executeWecomBotInboundFlow(payload = {}) {
231
243
  const storePath = runtimeContext.storePath;
232
244
  const ctxPayload = runtimeContext.ctxPayload;
233
245
  const sessionRuntimeId = runtimeContext.sessionRuntimeId;
246
+ markWecomReliableInboundActivity({
247
+ mode: "bot",
248
+ accountId: state.accountId,
249
+ sessionId: state.sessionId,
250
+ fromUser,
251
+ });
252
+ await flushWecomSessionPendingReplies({
253
+ mode: "bot",
254
+ accountId: state.accountId,
255
+ sessionId: state.sessionId,
256
+ });
234
257
 
235
258
  const dispatchResult = await executeWecomBotDispatchRuntime({
236
259
  api,
@@ -255,6 +278,7 @@ export async function executeWecomBotInboundFlow(payload = {}) {
255
278
  markdownToWecomText,
256
279
  isAgentFailureText,
257
280
  safeDeliverReply,
281
+ resolveWecomReasoningPolicy,
258
282
  markTranscriptReplyDelivered,
259
283
  ACTIVE_LATE_REPLY_WATCHERS,
260
284
  ensureTranscriptFallbackReader,
@@ -59,10 +59,12 @@ export async function applyWecomBotCommandAndSenderGuard({
59
59
  api,
60
60
  accountId = "default",
61
61
  fromUser,
62
+ chatId = "",
62
63
  isGroupChat = false,
63
64
  msgType = "text",
64
65
  commandBody = "",
65
66
  normalizedFromUser = "",
67
+ groupChatPolicy = {},
66
68
  resolveWecomCommandPolicy,
67
69
  resolveWecomAllowFromPolicy,
68
70
  resolveWecomDmPolicy,
@@ -84,6 +86,30 @@ export async function applyWecomBotCommandAndSenderGuard({
84
86
  const dmPolicy = resolveWecomDmPolicy(api, accountId, {});
85
87
  const allowFromPolicy = resolveWecomAllowFromPolicy(api, accountId, {});
86
88
 
89
+ if (isGroupChat) {
90
+ const groupPolicyMode = String(groupChatPolicy?.policyMode ?? (groupChatPolicy?.enabled === false ? "deny" : "open"))
91
+ .trim()
92
+ .toLowerCase();
93
+ const groupSenderAllowed =
94
+ isAdminUser ||
95
+ groupPolicyMode === "open" ||
96
+ (groupPolicyMode !== "deny" &&
97
+ (groupChatPolicy.allowFrom.includes("*") ||
98
+ isWecomSenderAllowed({
99
+ senderId: normalizedFromUser,
100
+ allowFrom: groupChatPolicy.allowFrom,
101
+ })));
102
+ if (!groupSenderAllowed) {
103
+ return {
104
+ ok: false,
105
+ finishText: groupChatPolicy?.rejectMessage || "当前群聊发送者未授权,请联系管理员。",
106
+ commandBody: String(commandBody ?? ""),
107
+ isAdminUser,
108
+ commandPolicy,
109
+ };
110
+ }
111
+ }
112
+
87
113
  if (!isGroupChat) {
88
114
  const dmAccess = await resolveWecomDirectMessageAccess({
89
115
  api,
@@ -176,7 +202,11 @@ export async function applyWecomBotCommandAndSenderGuard({
176
202
  if (commandKey === "/status") {
177
203
  return {
178
204
  ok: false,
179
- finishText: buildWecomBotStatusText(api, fromUser),
205
+ finishText: buildWecomBotStatusText(api, fromUser, {
206
+ accountId,
207
+ chatId,
208
+ isGroupChat,
209
+ }),
180
210
  commandBody: nextCommandBody,
181
211
  isAdminUser,
182
212
  commandPolicy,