@yanhaidao/wecom 2.3.260 → 2.3.270

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/src/outbound.ts CHANGED
@@ -1,6 +1,5 @@
1
- import type { ChannelOutboundAdapter, ChannelOutboundContext } from "openclaw/plugin-sdk";
1
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-send-result";
2
2
  import { WecomAgentDeliveryService } from "./capability/agent/index.js";
3
- import { toWeComMarkdownV2 } from "./wecom_msg_adapter/markdown_adapter.js";
4
3
  import {
5
4
  resolveWecomMergedMediaLocalRoots,
6
5
  resolveWecomMediaMaxBytes,
@@ -16,9 +15,16 @@ import {
16
15
  } from "./runtime.js";
17
16
  import { resolveWecomSourceSnapshot } from "./runtime/source-registry.js";
18
17
  import { resolveScopedWecomTarget } from "./target.js";
18
+ import { toWeComMarkdownV2 } from "./wecom_msg_adapter/markdown_adapter.js";
19
+
20
+ type WecomOutboundBaseContext = Parameters<NonNullable<ChannelOutboundAdapter["sendText"]>>[0];
21
+ type WecomOutboundContext = WecomOutboundBaseContext & {
22
+ sessionKey?: string | null;
23
+ };
24
+ type WecomOutboundConfig = WecomOutboundContext["cfg"];
19
25
 
20
26
  function resolveOutboundAccountOrThrow(params: {
21
- cfg: ChannelOutboundContext["cfg"];
27
+ cfg: WecomOutboundConfig;
22
28
  accountId?: string | null;
23
29
  }) {
24
30
  const resolvedAccounts = resolveWecomAccounts(params.cfg);
@@ -46,7 +52,7 @@ function resolveOutboundAccountOrThrow(params: {
46
52
  }
47
53
 
48
54
  function resolveAgentConfigOrThrow(params: {
49
- cfg: ChannelOutboundContext["cfg"];
55
+ cfg: WecomOutboundConfig;
50
56
  accountId?: string | null;
51
57
  }) {
52
58
  const account = resolveOutboundAccountOrThrow(params).agent;
@@ -114,7 +120,7 @@ function resolveOutboundPeer(params: {
114
120
  }
115
121
 
116
122
  function shouldPreferBotWsOutbound(params: {
117
- cfg: ChannelOutboundContext["cfg"];
123
+ cfg: WecomOutboundConfig;
118
124
  accountId?: string | null;
119
125
  to: string | undefined;
120
126
  sessionKey?: string | null;
@@ -167,7 +173,7 @@ function markActiveBotWsReplyHandleActivity(params: {
167
173
  }
168
174
 
169
175
  async function sendTextViaBotWs(params: {
170
- cfg: ChannelOutboundContext["cfg"];
176
+ cfg: WecomOutboundConfig;
171
177
  accountId?: string | null;
172
178
  to: string | undefined;
173
179
  text: string;
@@ -210,7 +216,7 @@ async function sendTextViaBotWs(params: {
210
216
  }
211
217
 
212
218
  async function sendMediaViaBotWs(params: {
213
- cfg: ChannelOutboundContext["cfg"];
219
+ cfg: WecomOutboundConfig;
214
220
  accountId?: string | null;
215
221
  to: string | undefined;
216
222
  mediaUrl: string;
@@ -276,14 +282,14 @@ export const wecomOutbound: ChannelOutboundAdapter = {
276
282
  deliveryMode: "direct",
277
283
  chunkerMode: "text",
278
284
  textChunkLimit: 20480,
279
- chunker: (text, limit) => {
285
+ chunker: (text: string, limit: number) => {
280
286
  try {
281
287
  return getWecomRuntime().channel.text.chunkText(text, limit);
282
288
  } catch {
283
289
  return [text];
284
290
  }
285
291
  },
286
- sendText: async ({ cfg, to, text, accountId, sessionKey }: ChannelOutboundContext) => {
292
+ sendText: async ({ cfg, to, text, accountId, sessionKey }: WecomOutboundContext) => {
287
293
  // signal removed - not supported in current SDK
288
294
  // Defer Agent resolution until the Agent fallback path
289
295
  // sendTextViaBotWs() can already deliver without Agent mode
@@ -316,7 +322,7 @@ export const wecomOutbound: ChannelOutboundAdapter = {
316
322
  }
317
323
 
318
324
  let sentViaBotWs = false;
319
- let agent: any = null;
325
+ let agent: ReturnType<typeof resolveAgentConfigOrThrow> | null = null;
320
326
 
321
327
  try {
322
328
  sentViaBotWs = await sendTextViaBotWs({
@@ -362,7 +368,7 @@ export const wecomOutbound: ChannelOutboundAdapter = {
362
368
  accountId,
363
369
  mediaLocalRoots,
364
370
  sessionKey,
365
- }: ChannelOutboundContext) => {
371
+ }: WecomOutboundContext) => {
366
372
  // signal removed - not supported in current SDK
367
373
  if (!mediaUrl) {
368
374
  throw new Error("WeCom outbound requires mediaUrl.");
@@ -1,20 +1,20 @@
1
1
  import type { WSClient } from "@wecom/aibot-node-sdk";
2
+ import { fetchRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
2
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
- import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/media-runtime";
4
-
5
4
  import { uploadAndSendBotWsMedia } from "./media.js";
6
5
 
7
6
  vi.mock("openclaw/plugin-sdk/media-runtime", () => ({
7
+ assertLocalMediaAllowed: vi.fn(),
8
8
  detectMime: vi.fn(),
9
- loadOutboundMediaFromUrl: vi.fn(),
9
+ fetchRemoteMedia: vi.fn(),
10
10
  }));
11
11
 
12
12
  describe("uploadAndSendBotWsMedia", () => {
13
- const loadOutboundMediaFromUrlMock = vi.mocked(loadOutboundMediaFromUrl);
13
+ const fetchRemoteMediaMock = vi.mocked(fetchRemoteMedia);
14
14
 
15
15
  beforeEach(() => {
16
- loadOutboundMediaFromUrlMock.mockReset();
17
- loadOutboundMediaFromUrlMock.mockResolvedValue({
16
+ fetchRemoteMediaMock.mockReset();
17
+ fetchRemoteMediaMock.mockResolvedValue({
18
18
  buffer: Buffer.from("png"),
19
19
  contentType: "image/png",
20
20
  fileName: "sample.png",
@@ -34,9 +34,9 @@ describe("uploadAndSendBotWsMedia", () => {
34
34
  maxBytes: 42 * 1024 * 1024,
35
35
  });
36
36
 
37
- expect(loadOutboundMediaFromUrlMock).toHaveBeenCalledWith(
38
- "https://example.com/sample.png",
37
+ expect(fetchRemoteMediaMock).toHaveBeenCalledWith(
39
38
  expect.objectContaining({
39
+ url: "https://example.com/sample.png",
40
40
  maxBytes: 42 * 1024 * 1024,
41
41
  }),
42
42
  );
@@ -1,5 +1,12 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
1
4
  import type { WeComMediaType, WsFrameHeaders, WSClient } from "@wecom/aibot-node-sdk";
2
- import { detectMime, loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/media-runtime";
5
+ import {
6
+ assertLocalMediaAllowed,
7
+ detectMime,
8
+ fetchRemoteMedia,
9
+ } from "openclaw/plugin-sdk/media-runtime";
3
10
 
4
11
  const IMAGE_MAX_BYTES = 10 * 1024 * 1024;
5
12
  const VIDEO_MAX_BYTES = 10 * 1024 * 1024;
@@ -90,6 +97,47 @@ function extractFileName(
90
97
  return `media_${Date.now()}${mimeToExtension(contentType || "application/octet-stream")}`;
91
98
  }
92
99
 
100
+ function resolveLocalMediaPath(mediaUrl: string): string {
101
+ if (mediaUrl.startsWith("file://")) {
102
+ return fileURLToPath(mediaUrl);
103
+ }
104
+ return mediaUrl;
105
+ }
106
+
107
+ async function loadOutboundMediaFile(params: {
108
+ mediaUrl: string;
109
+ mediaLocalRoots?: readonly string[];
110
+ maxBytes: number;
111
+ }): Promise<{
112
+ buffer: Buffer;
113
+ contentType?: string;
114
+ fileName?: string;
115
+ }> {
116
+ if (/^https?:\/\//i.test(params.mediaUrl)) {
117
+ return await fetchRemoteMedia({
118
+ url: params.mediaUrl,
119
+ maxBytes: params.maxBytes,
120
+ filePathHint: params.mediaUrl,
121
+ });
122
+ }
123
+
124
+ const mediaPath = resolveLocalMediaPath(params.mediaUrl);
125
+ await assertLocalMediaAllowed(mediaPath, params.mediaLocalRoots);
126
+ const buffer = await readFile(mediaPath);
127
+ if (buffer.length > params.maxBytes) {
128
+ throw new Error(
129
+ `Media size ${(buffer.length / (1024 * 1024)).toFixed(2)}MB exceeds max ${(
130
+ params.maxBytes /
131
+ (1024 * 1024)
132
+ ).toFixed(2)}MB`,
133
+ );
134
+ }
135
+ return {
136
+ buffer,
137
+ fileName: path.basename(mediaPath),
138
+ };
139
+ }
140
+
93
141
  function applyFileSizeLimits(
94
142
  fileSize: number,
95
143
  detectedType: WeComMediaType,
@@ -160,7 +208,8 @@ async function resolveMediaFile(
160
208
  mediaLocalRoots?: readonly string[],
161
209
  maxBytes?: number,
162
210
  ): Promise<ResolvedMediaFile> {
163
- const result = await loadOutboundMediaFromUrl(mediaUrl, {
211
+ const result = await loadOutboundMediaFile({
212
+ mediaUrl,
164
213
  maxBytes: maxBytes ?? FILE_MAX_BYTES,
165
214
  mediaLocalRoots,
166
215
  });
@@ -51,11 +51,11 @@ export class BotWsSdkAdapter {
51
51
  registerBotWsPushHandle(this.runtime.account.accountId, {
52
52
  isConnected: () => client.isConnected,
53
53
  replyCommand: async ({ cmd, body, headers }) => {
54
- const result = await client.reply(
55
- { headers: headers ?? { req_id: generateReqId("wecom_ws") } },
56
- body ?? {},
57
- cmd,
58
- );
54
+ const replyHeaders = {
55
+ ...(headers ?? {}),
56
+ req_id: headers?.req_id ?? generateReqId("wecom_ws"),
57
+ };
58
+ const result = await client.reply({ headers: replyHeaders }, body ?? {}, cmd);
59
59
  this.runtime.touchTransportSession("bot-ws", {
60
60
  ownerId: this.ownerId,
61
61
  running: true,
@@ -64,7 +64,7 @@ export class BotWsSdkAdapter {
64
64
  lastOutboundAt: Date.now(),
65
65
  lastError: undefined,
66
66
  });
67
- return result as Record<string, unknown>;
67
+ return result as unknown as Record<string, unknown>;
68
68
  },
69
69
  sendMarkdown: async (chatId, content) => {
70
70
  await client.sendMessage(chatId, {