@dingxiang-me/openclaw-wechat 1.4.1 → 1.7.2

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 (47) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.en.md +89 -12
  3. package/README.md +103 -15
  4. package/docs/channels/wecom.md +28 -3
  5. package/openclaw.plugin.json +467 -10
  6. package/package.json +13 -2
  7. package/src/core/agent-routing.js +6 -0
  8. package/src/core.js +564 -35
  9. package/src/wecom/account-config-core.js +28 -8
  10. package/src/wecom/account-config.js +55 -0
  11. package/src/wecom/account-diagnostics.js +121 -0
  12. package/src/wecom/account-paths.js +39 -0
  13. package/src/wecom/agent-inbound-dispatch.js +9 -0
  14. package/src/wecom/agent-inbound-guards.js +24 -4
  15. package/src/wecom/agent-inbound-processor.js +27 -0
  16. package/src/wecom/agent-webhook-handler.js +11 -0
  17. package/src/wecom/bot-context.js +2 -1
  18. package/src/wecom/bot-dispatch-fallback.js +2 -1
  19. package/src/wecom/bot-inbound-content.js +73 -3
  20. package/src/wecom/bot-inbound-dispatch-runtime.js +2 -1
  21. package/src/wecom/bot-inbound-executor-helpers.js +56 -5
  22. package/src/wecom/bot-inbound-executor.js +19 -0
  23. package/src/wecom/bot-inbound-guards.js +36 -4
  24. package/src/wecom/bot-runtime-context.js +5 -3
  25. package/src/wecom/bot-webhook-dispatch.js +45 -12
  26. package/src/wecom/bot-webhook-handler.js +45 -13
  27. package/src/wecom/command-handlers.js +26 -0
  28. package/src/wecom/command-status-text.js +76 -7
  29. package/src/wecom/observability-metrics.js +133 -0
  30. package/src/wecom/outbound-agent-push.js +2 -1
  31. package/src/wecom/outbound-bot-card.js +103 -0
  32. package/src/wecom/outbound-delivery.js +92 -7
  33. package/src/wecom/outbound-response-delivery.js +10 -6
  34. package/src/wecom/outbound-webhook-delivery.js +42 -1
  35. package/src/wecom/plugin-account-policy-services.js +19 -0
  36. package/src/wecom/plugin-base-services.js +13 -0
  37. package/src/wecom/plugin-constants.js +1 -1
  38. package/src/wecom/plugin-delivery-inbound-services.js +8 -0
  39. package/src/wecom/plugin-processing-deps.js +4 -0
  40. package/src/wecom/plugin-route-runtime-deps.js +5 -0
  41. package/src/wecom/plugin-services.js +7 -0
  42. package/src/wecom/policy-resolvers.js +82 -5
  43. package/src/wecom/register-runtime.js +31 -2
  44. package/src/wecom/route-registration.js +173 -41
  45. package/src/wecom/runtime-utils.js +7 -2
  46. package/src/wecom/webhook-adapter.js +61 -0
  47. package/src/wecom/webhook-bot.js +26 -0
@@ -8,12 +8,16 @@ import { createWecomResponseUrlSender } from "./outbound-response-url.js";
8
8
  import { createWecomWebhookBotDeliverer } from "./outbound-webhook-delivery.js";
9
9
  import { createWecomWebhookBotMediaSender } from "./outbound-webhook-media.js";
10
10
  import { buildActiveStreamMsgItems } from "./outbound-stream-msg-item.js";
11
+ import { buildWecomBotCardPayload } from "./outbound-bot-card.js";
11
12
  import {
12
13
  resolveWebhookBotSendUrl,
13
14
  webhookSendFileBuffer,
14
15
  webhookSendImage,
16
+ webhookSendMarkdown,
17
+ webhookSendTemplateCard,
15
18
  webhookSendText,
16
19
  } from "./webhook-bot.js";
20
+ import { stat } from "node:fs/promises";
17
21
 
18
22
  function assertFunction(name, fn) {
19
23
  if (typeof fn !== "function") {
@@ -27,6 +31,7 @@ export function createWecomBotReplyDeliverer({
27
31
  resolveWecomWebhookBotDeliveryPolicy,
28
32
  resolveWecomObservabilityPolicy,
29
33
  resolveWecomBotProxyConfig,
34
+ resolveWecomBotConfig,
30
35
  buildWecomBotSessionId,
31
36
  upsertBotResponseUrlCache,
32
37
  getBotResponseUrlCache,
@@ -43,6 +48,10 @@ export function createWecomBotReplyDeliverer({
43
48
  webhookSendTextFn = webhookSendText,
44
49
  webhookSendImageFn = webhookSendImage,
45
50
  webhookSendFileBufferFn = webhookSendFileBuffer,
51
+ extractWorkspacePathsFromText = () => [],
52
+ resolveWorkspacePathToHost = () => "",
53
+ recordDeliveryMetric = () => {},
54
+ statImpl = stat,
46
55
  fetchImpl = fetch,
47
56
  } = {}) {
48
57
  assertFunction("attachWecomProxyDispatcher", attachWecomProxyDispatcher);
@@ -50,6 +59,7 @@ export function createWecomBotReplyDeliverer({
50
59
  assertFunction("resolveWecomWebhookBotDeliveryPolicy", resolveWecomWebhookBotDeliveryPolicy);
51
60
  assertFunction("resolveWecomObservabilityPolicy", resolveWecomObservabilityPolicy);
52
61
  assertFunction("resolveWecomBotProxyConfig", resolveWecomBotProxyConfig);
62
+ assertFunction("resolveWecomBotConfig", resolveWecomBotConfig);
53
63
  assertFunction("buildWecomBotSessionId", buildWecomBotSessionId);
54
64
  assertFunction("upsertBotResponseUrlCache", upsertBotResponseUrlCache);
55
65
  assertFunction("getBotResponseUrlCache", getBotResponseUrlCache);
@@ -66,6 +76,43 @@ export function createWecomBotReplyDeliverer({
66
76
  assertFunction("webhookSendTextFn", webhookSendTextFn);
67
77
  assertFunction("webhookSendImageFn", webhookSendImageFn);
68
78
  assertFunction("webhookSendFileBufferFn", webhookSendFileBufferFn);
79
+ assertFunction("extractWorkspacePathsFromText", extractWorkspacePathsFromText);
80
+ assertFunction("resolveWorkspacePathToHost", resolveWorkspacePathToHost);
81
+ assertFunction("recordDeliveryMetric", recordDeliveryMetric);
82
+ assertFunction("statImpl", statImpl);
83
+
84
+ const inlineImageExts = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".heic", ".heif"]);
85
+
86
+ async function collectInlineWorkspaceImageMediaUrls({ text, routeAgentId }) {
87
+ const normalizedText = String(text ?? "");
88
+ const normalizedRouteAgentId = String(routeAgentId ?? "").trim();
89
+ if (!normalizedText || !normalizedRouteAgentId) return [];
90
+ const workspacePaths = extractWorkspacePathsFromText(normalizedText, 6);
91
+ if (!Array.isArray(workspacePaths) || workspacePaths.length === 0) return [];
92
+
93
+ const out = [];
94
+ const seen = new Set();
95
+ for (const workspacePath of workspacePaths) {
96
+ const hostPath = resolveWorkspacePathToHost({
97
+ workspacePath,
98
+ agentId: normalizedRouteAgentId,
99
+ });
100
+ const normalizedHostPath = String(hostPath ?? "").trim();
101
+ if (!normalizedHostPath || seen.has(normalizedHostPath)) continue;
102
+ const lower = normalizedHostPath.toLowerCase();
103
+ const ext = lower.includes(".") ? `.${lower.split(".").pop()}` : "";
104
+ if (!inlineImageExts.has(ext)) continue;
105
+ try {
106
+ const fileStat = await statImpl(normalizedHostPath);
107
+ if (!fileStat?.isFile?.()) continue;
108
+ seen.add(normalizedHostPath);
109
+ out.push(normalizedHostPath);
110
+ } catch {
111
+ // ignore non-existing paths
112
+ }
113
+ }
114
+ return out;
115
+ }
69
116
 
70
117
  const sendWebhookBotMediaBatch = createWecomWebhookBotMediaSender({
71
118
  resolveWebhookBotSendUrl: resolveWebhookBotSendUrlFn,
@@ -94,6 +141,8 @@ export function createWecomBotReplyDeliverer({
94
141
  attachWecomProxyDispatcher,
95
142
  resolveWebhookBotSendUrl: resolveWebhookBotSendUrlFn,
96
143
  webhookSendText: webhookSendTextFn,
144
+ webhookSendMarkdown,
145
+ webhookSendTemplateCard,
97
146
  sendWebhookBotMediaBatch,
98
147
  fetchImpl,
99
148
  });
@@ -109,30 +158,49 @@ export function createWecomBotReplyDeliverer({
109
158
  async function deliverBotReplyText({
110
159
  api,
111
160
  fromUser,
161
+ accountId = "default",
112
162
  sessionId,
113
163
  streamId,
114
164
  responseUrl,
115
165
  text,
166
+ routeAgentId = "",
116
167
  mediaUrl,
117
168
  mediaUrls,
118
169
  mediaType,
119
170
  reason = "reply",
120
171
  } = {}) {
172
+ const normalizedAccountId = String(accountId ?? "default").trim().toLowerCase() || "default";
121
173
  const fallbackPolicy = resolveWecomDeliveryFallbackPolicy(api);
122
174
  const webhookBotPolicy = resolveWecomWebhookBotDeliveryPolicy(api);
123
175
  const observabilityPolicy = resolveWecomObservabilityPolicy(api);
124
- const botProxyUrl = resolveWecomBotProxyConfig(api);
176
+ const botProxyUrl = resolveWecomBotProxyConfig(api, normalizedAccountId);
177
+ const botModeConfig = resolveWecomBotConfig(api, normalizedAccountId);
125
178
  const normalizedText = String(text ?? "").trim();
126
- const normalizedMediaUrls = normalizeWecomBotOutboundMediaUrls({ mediaUrl, mediaUrls });
127
- const mixedPayload = buildWecomBotMixedPayload({
179
+ const inlineWorkspaceMediaUrls = await collectInlineWorkspaceImageMediaUrls({
128
180
  text: normalizedText,
129
- mediaUrls: normalizedMediaUrls,
181
+ routeAgentId,
182
+ });
183
+ const normalizedMediaUrls = normalizeWecomBotOutboundMediaUrls({
184
+ mediaUrl,
185
+ mediaUrls: [...(Array.isArray(mediaUrls) ? mediaUrls : []), ...inlineWorkspaceMediaUrls],
186
+ });
187
+ const mixedPayload =
188
+ normalizedMediaUrls.length > 0
189
+ ? buildWecomBotMixedPayload({
190
+ text: normalizedText,
191
+ mediaUrls: normalizedMediaUrls,
192
+ })
193
+ : null;
194
+ const fallbackText = normalizedText || "已收到模型返回的媒体结果,请查看以下链接。";
195
+ const cardPayload = buildWecomBotCardPayload({
196
+ text: normalizedText || fallbackText,
197
+ cardPolicy: botModeConfig?.card,
198
+ hasMedia: normalizedMediaUrls.length > 0,
130
199
  });
131
200
  const mediaFallbackSuffix =
132
201
  normalizedMediaUrls.length > 0 ? `\n\n媒体链接:\n${normalizedMediaUrls.join("\n")}` : "";
133
- const fallbackText = normalizedText || "已收到模型返回的媒体结果,请查看以下链接。";
134
202
 
135
- const normalizedSessionId = String(sessionId ?? "").trim() || buildWecomBotSessionId(fromUser);
203
+ const normalizedSessionId = String(sessionId ?? "").trim() || buildWecomBotSessionId(fromUser, normalizedAccountId);
136
204
  const inlineResponseUrl = String(responseUrl ?? "").trim();
137
205
  if (inlineResponseUrl) {
138
206
  upsertBotResponseUrlCache({
@@ -166,6 +234,10 @@ export function createWecomBotReplyDeliverer({
166
234
  inlineResponseUrl,
167
235
  cachedResponseUrl,
168
236
  mixedPayload,
237
+ cardPayload:
238
+ botModeConfig?.card?.enabled === true && botModeConfig?.card?.responseUrlEnabled !== false
239
+ ? cardPayload
240
+ : null,
169
241
  content,
170
242
  fallbackText,
171
243
  logger: api.logger,
@@ -183,12 +255,15 @@ export function createWecomBotReplyDeliverer({
183
255
  normalizedText,
184
256
  normalizedMediaUrls,
185
257
  mediaType,
258
+ cardPayload,
259
+ cardPolicy: botModeConfig?.card ?? {},
186
260
  });
187
261
  },
188
262
  agent_push: async ({ text: content }) => {
189
263
  return deliverAgentPushReply({
190
264
  api,
191
265
  fromUser,
266
+ accountId: normalizedAccountId,
192
267
  content,
193
268
  fallbackText,
194
269
  mediaFallbackSuffix,
@@ -197,18 +272,28 @@ export function createWecomBotReplyDeliverer({
197
272
  },
198
273
  });
199
274
 
200
- return router.deliverText({
275
+ const deliveryResult = await router.deliverText({
201
276
  text: normalizedText || fallbackText,
202
277
  traceId,
203
278
  meta: {
204
279
  reason,
205
280
  fromUser,
281
+ accountId: normalizedAccountId,
206
282
  sessionId: normalizedSessionId,
207
283
  streamId: streamId || "",
208
284
  hasResponseUrl: Boolean(inlineResponseUrl || cachedResponseUrl?.url),
209
285
  mediaCount: normalizedMediaUrls.length,
286
+ botCardMode: botModeConfig?.card?.enabled ? botModeConfig.card.mode : "off",
210
287
  },
211
288
  });
289
+ recordDeliveryMetric({
290
+ layer: deliveryResult?.layer || "",
291
+ ok: deliveryResult?.ok === true,
292
+ finalStatus: deliveryResult?.finalStatus || "",
293
+ accountId: normalizedAccountId,
294
+ attempts: deliveryResult?.attempts,
295
+ });
296
+ return deliveryResult;
212
297
  }
213
298
 
214
299
  return {
@@ -16,6 +16,7 @@ export function createWecomResponseUrlDeliverer({
16
16
  inlineResponseUrl = "",
17
17
  cachedResponseUrl = null,
18
18
  mixedPayload = null,
19
+ cardPayload = null,
19
20
  content = "",
20
21
  fallbackText = "",
21
22
  logger,
@@ -29,12 +30,14 @@ export function createWecomResponseUrlDeliverer({
29
30
  if (cachedResponseUrl?.used) {
30
31
  return { ok: false, reason: "response-url-used" };
31
32
  }
32
- const payload = mixedPayload || {
33
- msgtype: "text",
34
- text: {
35
- content: content || fallbackText,
36
- },
37
- };
33
+ const payload =
34
+ mixedPayload ||
35
+ cardPayload || {
36
+ msgtype: "text",
37
+ text: {
38
+ content: content || fallbackText,
39
+ },
40
+ };
38
41
  const result = await sendWecomBotPayloadViaResponseUrl({
39
42
  responseUrl: targetUrl,
40
43
  payload,
@@ -48,6 +51,7 @@ export function createWecomResponseUrlDeliverer({
48
51
  meta: {
49
52
  status: result.status,
50
53
  errcode: result.errcode ?? 0,
54
+ msgtype: String(payload?.msgtype ?? "text").trim().toLowerCase() || "text",
51
55
  },
52
56
  };
53
57
  };
@@ -8,12 +8,16 @@ export function createWecomWebhookBotDeliverer({
8
8
  attachWecomProxyDispatcher,
9
9
  resolveWebhookBotSendUrl,
10
10
  webhookSendText,
11
+ webhookSendMarkdown,
12
+ webhookSendTemplateCard,
11
13
  sendWebhookBotMediaBatch,
12
14
  fetchImpl = fetch,
13
15
  } = {}) {
14
16
  assertFunction("attachWecomProxyDispatcher", attachWecomProxyDispatcher);
15
17
  assertFunction("resolveWebhookBotSendUrl", resolveWebhookBotSendUrl);
16
18
  assertFunction("webhookSendText", webhookSendText);
19
+ assertFunction("webhookSendMarkdown", webhookSendMarkdown);
20
+ assertFunction("webhookSendTemplateCard", webhookSendTemplateCard);
17
21
  assertFunction("sendWebhookBotMediaBatch", sendWebhookBotMediaBatch);
18
22
  assertFunction("fetchImpl", fetchImpl);
19
23
 
@@ -26,6 +30,8 @@ export function createWecomWebhookBotDeliverer({
26
30
  normalizedText = "",
27
31
  normalizedMediaUrls = [],
28
32
  mediaType,
33
+ cardPayload = null,
34
+ cardPolicy = {},
29
35
  } = {}) {
30
36
  if (!webhookBotPolicy?.enabled) {
31
37
  return { ok: false, reason: "webhook-bot-disabled" };
@@ -41,8 +47,42 @@ export function createWecomWebhookBotDeliverer({
41
47
  const dispatcher = attachWecomProxyDispatcher(sendUrl, {}, { proxyUrl: botProxyUrl, logger: api?.logger })?.dispatcher;
42
48
  const textPayload = `${content || fallbackText}`.trim();
43
49
  let sentAny = false;
50
+ let usedCardMode = "";
44
51
 
45
- if (textPayload && (normalizedText || normalizedMediaUrls.length === 0)) {
52
+ const cardEnabledForWebhook = cardPolicy?.enabled === true && cardPolicy?.webhookBotEnabled !== false;
53
+ const canTryCard = cardEnabledForWebhook && normalizedMediaUrls.length === 0 && cardPayload && typeof cardPayload === "object";
54
+ if (canTryCard) {
55
+ try {
56
+ const cardMsgType = String(cardPayload?.msgtype ?? "").trim().toLowerCase();
57
+ if (cardMsgType === "template_card" && cardPayload?.template_card) {
58
+ await webhookSendTemplateCard({
59
+ url: webhookBotPolicy?.url,
60
+ key: webhookBotPolicy?.key,
61
+ templateCard: cardPayload.template_card,
62
+ timeoutMs: webhookBotPolicy?.timeoutMs,
63
+ dispatcher,
64
+ fetchImpl,
65
+ });
66
+ sentAny = true;
67
+ usedCardMode = "template_card";
68
+ } else if (cardMsgType === "markdown" && cardPayload?.markdown?.content) {
69
+ await webhookSendMarkdown({
70
+ url: webhookBotPolicy?.url,
71
+ key: webhookBotPolicy?.key,
72
+ content: String(cardPayload.markdown.content ?? ""),
73
+ timeoutMs: webhookBotPolicy?.timeoutMs,
74
+ dispatcher,
75
+ fetchImpl,
76
+ });
77
+ sentAny = true;
78
+ usedCardMode = "markdown";
79
+ }
80
+ } catch (err) {
81
+ api?.logger?.warn?.(`wecom: webhook bot card send failed, fallback to text: ${String(err?.message || err)}`);
82
+ }
83
+ }
84
+
85
+ if (!sentAny && textPayload && (normalizedText || normalizedMediaUrls.length === 0)) {
46
86
  await webhookSendText({
47
87
  url: webhookBotPolicy?.url,
48
88
  key: webhookBotPolicy?.key,
@@ -86,6 +126,7 @@ export function createWecomWebhookBotDeliverer({
86
126
  meta: {
87
127
  mediaSent: mediaMeta.sentCount,
88
128
  mediaFailed: mediaMeta.failedCount,
129
+ cardMode: usedCardMode || undefined,
89
130
  },
90
131
  };
91
132
  };
@@ -12,9 +12,12 @@ import {
12
12
  pickAudioFileExtension,
13
13
  resolveVoiceTranscriptionConfig,
14
14
  resolveWecomAllowFromPolicyConfig,
15
+ resolveWecomBotModeAccountsConfig,
15
16
  resolveWecomBotModeConfig,
16
17
  resolveWecomCommandPolicyConfig,
17
18
  resolveWecomDebounceConfig,
19
+ resolveWecomDmPolicyConfig,
20
+ resolveWecomEventPolicyConfig,
18
21
  resolveWecomDeliveryFallbackConfig,
19
22
  resolveWecomDynamicAgentConfig,
20
23
  resolveWecomGroupChatConfig,
@@ -28,6 +31,7 @@ import {
28
31
  export function createWecomPluginAccountPolicyServices({
29
32
  processEnv = process.env,
30
33
  getGatewayRuntime,
34
+ getWecomObservabilityMetrics = () => ({}),
31
35
  normalizeWecomResolvedTarget,
32
36
  formatWecomTargetForLog,
33
37
  sendWecomWebhookText,
@@ -45,6 +49,7 @@ export function createWecomPluginAccountPolicyServices({
45
49
  normalizeAccountId,
46
50
  getWecomConfig,
47
51
  listWecomAccountIds,
52
+ listEnabledWecomAccounts,
48
53
  listWebhookTargetAliases,
49
54
  listAllWebhookTargetAliases,
50
55
  groupAccountsByWebhookPath,
@@ -66,10 +71,13 @@ export function createWecomPluginAccountPolicyServices({
66
71
  });
67
72
 
68
73
  const {
74
+ resolveWecomBotConfigs,
69
75
  resolveWecomBotConfig,
70
76
  resolveWecomBotProxyConfig,
71
77
  resolveWecomCommandPolicy,
72
78
  resolveWecomAllowFromPolicy,
79
+ resolveWecomDmPolicy,
80
+ resolveWecomEventPolicy,
73
81
  resolveWecomGroupChatPolicy,
74
82
  resolveWecomTextDebouncePolicy,
75
83
  resolveWecomReplyStreamingPolicy,
@@ -82,9 +90,12 @@ export function createWecomPluginAccountPolicyServices({
82
90
  getGatewayRuntime,
83
91
  normalizeAccountId,
84
92
  resolveWecomBotModeConfig,
93
+ resolveWecomBotModeAccountsConfig,
85
94
  resolveWecomProxyConfig,
86
95
  resolveWecomCommandPolicyConfig,
87
96
  resolveWecomAllowFromPolicyConfig,
97
+ resolveWecomDmPolicyConfig,
98
+ resolveWecomEventPolicyConfig,
88
99
  resolveWecomGroupChatConfig,
89
100
  resolveWecomDebounceConfig,
90
101
  resolveWecomStreamingConfig,
@@ -109,11 +120,14 @@ export function createWecomPluginAccountPolicyServices({
109
120
  sendWecomText,
110
121
  getWecomConfig,
111
122
  listWecomAccountIds,
123
+ listEnabledWecomAccounts,
112
124
  listWebhookTargetAliases,
113
125
  listAllWebhookTargetAliases,
114
126
  resolveWecomVoiceTranscriptionConfig,
115
127
  resolveWecomCommandPolicy,
116
128
  resolveWecomAllowFromPolicy,
129
+ resolveWecomDmPolicy,
130
+ resolveWecomEventPolicy,
117
131
  resolveWecomGroupChatPolicy,
118
132
  resolveWecomTextDebouncePolicy,
119
133
  resolveWecomReplyStreamingPolicy,
@@ -122,6 +136,7 @@ export function createWecomPluginAccountPolicyServices({
122
136
  resolveWecomWebhookBotDeliveryPolicy,
123
137
  resolveWecomDynamicAgentPolicy,
124
138
  resolveWecomBotConfig,
139
+ getWecomObservabilityMetrics,
125
140
  pluginVersion: PLUGIN_VERSION,
126
141
  });
127
142
 
@@ -129,14 +144,18 @@ export function createWecomPluginAccountPolicyServices({
129
144
  normalizeAccountId,
130
145
  getWecomConfig,
131
146
  listWecomAccountIds,
147
+ listEnabledWecomAccounts,
132
148
  listWebhookTargetAliases,
133
149
  listAllWebhookTargetAliases,
134
150
  groupAccountsByWebhookPath,
135
151
  WecomChannelPlugin,
136
152
  resolveWecomBotConfig,
153
+ resolveWecomBotConfigs,
137
154
  resolveWecomBotProxyConfig,
138
155
  resolveWecomCommandPolicy,
139
156
  resolveWecomAllowFromPolicy,
157
+ resolveWecomDmPolicy,
158
+ resolveWecomEventPolicy,
140
159
  resolveWecomGroupChatPolicy,
141
160
  resolveWecomTextDebouncePolicy,
142
161
  resolveWecomReplyStreamingPolicy,
@@ -13,6 +13,7 @@ import { createWecomBotEncryptedResponseBuilder } from "./bot-encrypted-response
13
13
  import { createWecomDefaultLimiters } from "./rate-limiter.js";
14
14
  import { createWecomMediaFetcher, normalizeOutboundMediaUrls, resolveWecomOutboundMediaTarget } from "./media-url-utils.js";
15
15
  import { createWecomOutboundSender } from "./outbound-sender.js";
16
+ import { createWecomObservabilityMetricsStore } from "./observability-metrics.js";
16
17
  import { createWecomRequestParsers } from "./request-parsers.js";
17
18
  import { createWecomTargetResolver } from "./target-utils.js";
18
19
  import { createDeliveredTranscriptReplyTracker } from "./transcript-utils.js";
@@ -38,6 +39,12 @@ export function createWecomPluginBaseServices({
38
39
  fetchImpl = fetch,
39
40
  proxyAgentCtor = ProxyAgent,
40
41
  } = {}) {
42
+ const {
43
+ recordInboundMetric,
44
+ recordDeliveryMetric,
45
+ recordRuntimeErrorMetric,
46
+ getWecomObservabilityMetrics,
47
+ } = createWecomObservabilityMetricsStore();
41
48
  const { markTranscriptReplyDelivered, hasTranscriptReplyBeenDelivered } = createDeliveredTranscriptReplyTracker({
42
49
  ttlMs: TRANSCRIPT_REPLY_CACHE_TTL_MS,
43
50
  });
@@ -146,6 +153,10 @@ export function createWecomPluginBaseServices({
146
153
  return {
147
154
  markTranscriptReplyDelivered,
148
155
  hasTranscriptReplyBeenDelivered,
156
+ recordInboundMetric,
157
+ recordDeliveryMetric,
158
+ recordRuntimeErrorMetric,
159
+ getWecomObservabilityMetrics,
149
160
  scheduleTempFileCleanup,
150
161
  setBotStreamExpireMs,
151
162
  resolveBotActiveStream,
@@ -185,6 +196,8 @@ export function createWecomPluginBaseServices({
185
196
  sendWecomWebhookMediaBatch,
186
197
  sendWecomOutboundMediaBatch,
187
198
  autoSendWorkspaceFilesFromReplyText,
199
+ extractWorkspacePathsFromText,
200
+ resolveWorkspacePathToHost,
188
201
  normalizeWecomResolvedTarget,
189
202
  formatWecomTargetForLog,
190
203
  };
@@ -1,5 +1,5 @@
1
1
  export const MAX_REQUEST_BODY_SIZE = 1024 * 1024;
2
- export const PLUGIN_VERSION = "0.5.3";
2
+ export const PLUGIN_VERSION = "1.7.1";
3
3
  export const WECOM_TEMP_DIR_NAME = "openclaw-wechat";
4
4
  export const WECOM_TEMP_FILE_RETENTION_MS = 30 * 60 * 1000;
5
5
  export const WECOM_MIN_FILE_SIZE = 5;
@@ -16,6 +16,7 @@ export function createWecomPluginDeliveryInboundServices({
16
16
  resolveWecomWebhookBotDeliveryPolicy,
17
17
  resolveWecomObservabilityPolicy,
18
18
  resolveWecomBotProxyConfig,
19
+ resolveWecomBotConfig,
19
20
  upsertBotResponseUrlCache,
20
21
  getBotResponseUrlCache,
21
22
  markBotResponseUrlUsed,
@@ -26,6 +27,9 @@ export function createWecomPluginDeliveryInboundServices({
26
27
  getWecomConfig,
27
28
  sendWecomText,
28
29
  fetchMediaFromUrl,
30
+ extractWorkspacePathsFromText,
31
+ resolveWorkspacePathToHost,
32
+ recordDeliveryMetric,
29
33
  downloadWecomMedia,
30
34
  resolveWecomVoiceTranscriptionConfig,
31
35
  transcribeInboundVoice,
@@ -48,6 +52,7 @@ export function createWecomPluginDeliveryInboundServices({
48
52
  resolveWecomWebhookBotDeliveryPolicy,
49
53
  resolveWecomObservabilityPolicy,
50
54
  resolveWecomBotProxyConfig,
55
+ resolveWecomBotConfig,
51
56
  buildWecomBotSessionId,
52
57
  upsertBotResponseUrlCache,
53
58
  getBotResponseUrlCache,
@@ -60,6 +65,9 @@ export function createWecomPluginDeliveryInboundServices({
60
65
  getWecomConfig,
61
66
  sendWecomText,
62
67
  fetchMediaFromUrl,
68
+ extractWorkspacePathsFromText,
69
+ resolveWorkspacePathToHost,
70
+ recordDeliveryMetric,
63
71
  });
64
72
 
65
73
  const { buildInboundContent } = createWecomInboundContentBuilder({
@@ -22,6 +22,8 @@ export function createPluginProcessingDeps(context = {}) {
22
22
  stripWecomGroupMentions: context.stripWecomGroupMentions,
23
23
  resolveWecomCommandPolicy: context.resolveWecomCommandPolicy,
24
24
  resolveWecomAllowFromPolicy: context.resolveWecomAllowFromPolicy,
25
+ resolveWecomDmPolicy: context.resolveWecomDmPolicy,
26
+ resolveWecomEventPolicy: context.resolveWecomEventPolicy,
25
27
  isWecomSenderAllowed: context.isWecomSenderAllowed,
26
28
  extractLeadingSlashCommand: context.extractLeadingSlashCommand,
27
29
  buildWecomBotHelpText: context.buildWecomBotHelpText,
@@ -54,6 +56,8 @@ export function createPluginProcessingDeps(context = {}) {
54
56
  stripWecomGroupMentions: context.stripWecomGroupMentions,
55
57
  resolveWecomCommandPolicy: context.resolveWecomCommandPolicy,
56
58
  resolveWecomAllowFromPolicy: context.resolveWecomAllowFromPolicy,
59
+ resolveWecomDmPolicy: context.resolveWecomDmPolicy,
60
+ resolveWecomEventPolicy: context.resolveWecomEventPolicy,
57
61
  isWecomSenderAllowed: context.isWecomSenderAllowed,
58
62
  sendWecomText: context.sendWecomText,
59
63
  extractLeadingSlashCommand: context.extractLeadingSlashCommand,
@@ -9,6 +9,7 @@ export function createPluginRouteRuntimeDeps(context = {}) {
9
9
  return {
10
10
  routeRegistrarDeps: {
11
11
  resolveWecomBotConfig: context.resolveWecomBotConfig,
12
+ resolveWecomBotConfigs: context.resolveWecomBotConfigs,
12
13
  normalizePluginHttpPath: context.normalizePluginHttpPath,
13
14
  ensureBotStreamCleanupTimer: context.ensureBotStreamCleanupTimer,
14
15
  cleanupExpiredBotStreams: context.cleanupExpiredBotStreams,
@@ -38,6 +39,8 @@ export function createPluginRouteRuntimeDeps(context = {}) {
38
39
  deliverBotReplyText: context.deliverBotReplyText,
39
40
  finishBotStream: context.finishBotStream,
40
41
  groupAccountsByWebhookPath: context.groupAccountsByWebhookPath,
42
+ recordInboundMetric: context.recordInboundMetric,
43
+ recordRuntimeErrorMetric: context.recordRuntimeErrorMetric,
41
44
  },
42
45
  registerRuntimeDeps: {
43
46
  setGatewayRuntime: context.setGatewayRuntime,
@@ -47,6 +50,8 @@ export function createPluginRouteRuntimeDeps(context = {}) {
47
50
  resolveWecomObservabilityPolicy: context.resolveWecomObservabilityPolicy,
48
51
  resolveWecomDynamicAgentPolicy: context.resolveWecomDynamicAgentPolicy,
49
52
  resolveWecomBotConfig: context.resolveWecomBotConfig,
53
+ resolveWecomBotConfigs: context.resolveWecomBotConfigs,
54
+ listEnabledWecomAccounts: context.listEnabledWecomAccounts,
50
55
  getWecomConfig: context.getWecomConfig,
51
56
  wecomChannelPlugin: context.wecomChannelPlugin,
52
57
  },
@@ -73,6 +73,7 @@ export function createWecomPluginServices({
73
73
  const accountPolicy = createWecomPluginAccountPolicyServices({
74
74
  processEnv,
75
75
  getGatewayRuntime: base.getGatewayRuntime,
76
+ getWecomObservabilityMetrics: base.getWecomObservabilityMetrics,
76
77
  normalizeWecomResolvedTarget: base.normalizeWecomResolvedTarget,
77
78
  formatWecomTargetForLog: base.formatWecomTargetForLog,
78
79
  sendWecomWebhookText: base.sendWecomWebhookText,
@@ -89,6 +90,7 @@ export function createWecomPluginServices({
89
90
  resolveWecomWebhookBotDeliveryPolicy: accountPolicy.resolveWecomWebhookBotDeliveryPolicy,
90
91
  resolveWecomObservabilityPolicy: accountPolicy.resolveWecomObservabilityPolicy,
91
92
  resolveWecomBotProxyConfig: accountPolicy.resolveWecomBotProxyConfig,
93
+ resolveWecomBotConfig: accountPolicy.resolveWecomBotConfig,
92
94
  upsertBotResponseUrlCache: base.upsertBotResponseUrlCache,
93
95
  getBotResponseUrlCache: base.getBotResponseUrlCache,
94
96
  markBotResponseUrlUsed: base.markBotResponseUrlUsed,
@@ -99,6 +101,9 @@ export function createWecomPluginServices({
99
101
  getWecomConfig: accountPolicy.getWecomConfig,
100
102
  sendWecomText: base.sendWecomText,
101
103
  fetchMediaFromUrl: base.fetchMediaFromUrl,
104
+ extractWorkspacePathsFromText: base.extractWorkspacePathsFromText,
105
+ resolveWorkspacePathToHost: base.resolveWorkspacePathToHost,
106
+ recordDeliveryMetric: base.recordDeliveryMetric,
102
107
  downloadWecomMedia: base.downloadWecomMedia,
103
108
  resolveWecomVoiceTranscriptionConfig: accountPolicy.resolveWecomVoiceTranscriptionConfig,
104
109
  transcribeInboundVoice: accountPolicy.transcribeInboundVoice,
@@ -108,6 +113,8 @@ export function createWecomPluginServices({
108
113
  detectImageContentTypeFromBuffer,
109
114
  decryptWecomMediaBuffer,
110
115
  pickImageFileExtension,
116
+ resolveWecomVoiceTranscriptionConfig: accountPolicy.resolveWecomVoiceTranscriptionConfig,
117
+ transcribeInboundVoice: accountPolicy.transcribeInboundVoice,
111
118
  inferFilenameFromMediaDownload,
112
119
  smartDecryptWecomFileBuffer,
113
120
  basename,