@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/README.md +10 -5
- package/changelog/v2.3.27.md +33 -0
- package/index.test.ts +5 -1
- package/package.json +17 -17
- package/src/app/index.ts +6 -3
- package/src/capability/mcp/tool.ts +7 -3
- package/src/channel.meta.test.ts +4 -0
- package/src/channel.ts +29 -59
- package/src/onboarding.test.ts +42 -24
- package/src/onboarding.ts +598 -553
- package/src/outbound.ts +17 -11
- package/src/transport/bot-ws/media.test.ts +8 -8
- package/src/transport/bot-ws/media.ts +51 -2
- package/src/transport/bot-ws/sdk-adapter.ts +6 -6
package/src/outbound.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ChannelOutboundAdapter
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 }:
|
|
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:
|
|
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
|
-
}:
|
|
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
|
-
|
|
9
|
+
fetchRemoteMedia: vi.fn(),
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
12
|
describe("uploadAndSendBotWsMedia", () => {
|
|
13
|
-
const
|
|
13
|
+
const fetchRemoteMediaMock = vi.mocked(fetchRemoteMedia);
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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 {
|
|
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
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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, {
|