ylib-wecom-openclaw-plugin 2026.4.29

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 (180) hide show
  1. package/README.md +596 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +99 -0
  4. package/dist/src/accounts.d.ts +57 -0
  5. package/dist/src/accounts.js +247 -0
  6. package/dist/src/agent/api-client.d.ts +95 -0
  7. package/dist/src/agent/api-client.js +425 -0
  8. package/dist/src/agent/handler.d.ts +64 -0
  9. package/dist/src/agent/handler.js +731 -0
  10. package/dist/src/agent/index.d.ts +5 -0
  11. package/dist/src/agent/index.js +21 -0
  12. package/dist/src/agent/webhook.d.ts +25 -0
  13. package/dist/src/agent/webhook.js +294 -0
  14. package/dist/src/agent/xml.d.ts +21 -0
  15. package/dist/src/agent/xml.js +43 -0
  16. package/dist/src/channel.d.ts +5 -0
  17. package/dist/src/channel.js +815 -0
  18. package/dist/src/chat-queue.d.ts +31 -0
  19. package/dist/src/chat-queue.js +53 -0
  20. package/dist/src/config-schema.d.ts +587 -0
  21. package/dist/src/config-schema.js +146 -0
  22. package/dist/src/const.d.ts +128 -0
  23. package/dist/src/const.js +168 -0
  24. package/dist/src/dm-policy.d.ts +29 -0
  25. package/dist/src/dm-policy.js +146 -0
  26. package/dist/src/dynamic-agent.d.ts +37 -0
  27. package/dist/src/dynamic-agent.js +67 -0
  28. package/dist/src/dynamic-routing.d.ts +65 -0
  29. package/dist/src/dynamic-routing.js +62 -0
  30. package/dist/src/endpoint-dispatch.d.ts +54 -0
  31. package/dist/src/endpoint-dispatch.js +967 -0
  32. package/dist/src/endpoint-event-adapter.d.ts +15 -0
  33. package/dist/src/endpoint-event-adapter.js +427 -0
  34. package/dist/src/group-policy.d.ts +30 -0
  35. package/dist/src/group-policy.js +126 -0
  36. package/dist/src/http.d.ts +27 -0
  37. package/dist/src/http.js +168 -0
  38. package/dist/src/im-runtime-telemetry.d.ts +25 -0
  39. package/dist/src/im-runtime-telemetry.js +68 -0
  40. package/dist/src/interface.d.ts +192 -0
  41. package/dist/src/interface.js +5 -0
  42. package/dist/src/markdown-chunk.d.ts +1 -0
  43. package/dist/src/markdown-chunk.js +396 -0
  44. package/dist/src/mcp/index.d.ts +6 -0
  45. package/dist/src/mcp/index.js +28 -0
  46. package/dist/src/mcp/interceptors/biz-error.d.ts +11 -0
  47. package/dist/src/mcp/interceptors/biz-error.js +73 -0
  48. package/dist/src/mcp/interceptors/doc-auth-error.d.ts +10 -0
  49. package/dist/src/mcp/interceptors/doc-auth-error.js +235 -0
  50. package/dist/src/mcp/interceptors/index.d.ts +35 -0
  51. package/dist/src/mcp/interceptors/index.js +143 -0
  52. package/dist/src/mcp/interceptors/msg-media.d.ts +11 -0
  53. package/dist/src/mcp/interceptors/msg-media.js +201 -0
  54. package/dist/src/mcp/interceptors/smartpage-create.d.ts +30 -0
  55. package/dist/src/mcp/interceptors/smartpage-create.js +252 -0
  56. package/dist/src/mcp/interceptors/smartpage-export.d.ts +17 -0
  57. package/dist/src/mcp/interceptors/smartpage-export.js +135 -0
  58. package/dist/src/mcp/interceptors/smartsheet-upload.d.ts +22 -0
  59. package/dist/src/mcp/interceptors/smartsheet-upload.js +388 -0
  60. package/dist/src/mcp/interceptors/types.d.ts +64 -0
  61. package/dist/src/mcp/interceptors/types.js +8 -0
  62. package/dist/src/mcp/schema.d.ts +11 -0
  63. package/dist/src/mcp/schema.js +115 -0
  64. package/dist/src/mcp/tool.d.ts +63 -0
  65. package/dist/src/mcp/tool.js +318 -0
  66. package/dist/src/mcp/transport.d.ts +94 -0
  67. package/dist/src/mcp/transport.js +702 -0
  68. package/dist/src/media-handler.d.ts +55 -0
  69. package/dist/src/media-handler.js +306 -0
  70. package/dist/src/media-uploader.d.ts +142 -0
  71. package/dist/src/media-uploader.js +446 -0
  72. package/dist/src/message-parser.d.ts +104 -0
  73. package/dist/src/message-parser.js +232 -0
  74. package/dist/src/message-sender.d.ts +54 -0
  75. package/dist/src/message-sender.js +210 -0
  76. package/dist/src/monitor.d.ts +69 -0
  77. package/dist/src/monitor.js +1846 -0
  78. package/dist/src/onboarding.d.ts +8 -0
  79. package/dist/src/onboarding.js +248 -0
  80. package/dist/src/openclaw-compat.d.ts +148 -0
  81. package/dist/src/openclaw-compat.js +839 -0
  82. package/dist/src/proactive-markdown-send.d.ts +14 -0
  83. package/dist/src/proactive-markdown-send.js +205 -0
  84. package/dist/src/reqid-store.d.ts +23 -0
  85. package/dist/src/reqid-store.js +136 -0
  86. package/dist/src/runtime.d.ts +2 -0
  87. package/dist/src/runtime.js +7 -0
  88. package/dist/src/shared/command-auth.d.ts +23 -0
  89. package/dist/src/shared/command-auth.js +112 -0
  90. package/dist/src/shared/xml-parser.d.ts +46 -0
  91. package/dist/src/shared/xml-parser.js +228 -0
  92. package/dist/src/state-dir-resolve.d.ts +2 -0
  93. package/dist/src/state-dir-resolve.js +33 -0
  94. package/dist/src/state-manager.d.ts +115 -0
  95. package/dist/src/state-manager.js +413 -0
  96. package/dist/src/target.d.ts +35 -0
  97. package/dist/src/target.js +71 -0
  98. package/dist/src/template-card-manager.d.ts +55 -0
  99. package/dist/src/template-card-manager.js +316 -0
  100. package/dist/src/template-card-parser.d.ts +37 -0
  101. package/dist/src/template-card-parser.js +672 -0
  102. package/dist/src/timeout.d.ts +20 -0
  103. package/dist/src/timeout.js +57 -0
  104. package/dist/src/types/account.d.ts +29 -0
  105. package/dist/src/types/account.js +5 -0
  106. package/dist/src/types/config.d.ts +98 -0
  107. package/dist/src/types/config.js +8 -0
  108. package/dist/src/types/constants.d.ts +42 -0
  109. package/dist/src/types/constants.js +45 -0
  110. package/dist/src/types/index.d.ts +7 -0
  111. package/dist/src/types/index.js +17 -0
  112. package/dist/src/types/message.d.ts +238 -0
  113. package/dist/src/types/message.js +6 -0
  114. package/dist/src/utils.d.ts +148 -0
  115. package/dist/src/utils.js +92 -0
  116. package/dist/src/version.d.ts +2 -0
  117. package/dist/src/version.js +28 -0
  118. package/dist/src/webhook/command-auth.d.ts +47 -0
  119. package/dist/src/webhook/command-auth.js +137 -0
  120. package/dist/src/webhook/gateway.d.ts +36 -0
  121. package/dist/src/webhook/gateway.js +297 -0
  122. package/dist/src/webhook/handler.d.ts +19 -0
  123. package/dist/src/webhook/handler.js +481 -0
  124. package/dist/src/webhook/helpers.d.ts +157 -0
  125. package/dist/src/webhook/helpers.js +936 -0
  126. package/dist/src/webhook/http.d.ts +27 -0
  127. package/dist/src/webhook/http.js +168 -0
  128. package/dist/src/webhook/index.d.ts +11 -0
  129. package/dist/src/webhook/index.js +43 -0
  130. package/dist/src/webhook/media.d.ts +30 -0
  131. package/dist/src/webhook/media.js +152 -0
  132. package/dist/src/webhook/monitor.d.ts +59 -0
  133. package/dist/src/webhook/monitor.js +1672 -0
  134. package/dist/src/webhook/state.d.ts +220 -0
  135. package/dist/src/webhook/state.js +568 -0
  136. package/dist/src/webhook/target.d.ts +41 -0
  137. package/dist/src/webhook/target.js +165 -0
  138. package/dist/src/webhook/types.d.ts +348 -0
  139. package/dist/src/webhook/types.js +36 -0
  140. package/dist/src/webhook/video-frame.d.ts +13 -0
  141. package/dist/src/webhook/video-frame.js +108 -0
  142. package/openclaw.plugin.json +19 -0
  143. package/package.json +96 -0
  144. package/schema.json +534 -0
  145. package/scripts/generate-schema.mjs +33 -0
  146. package/skills/wecom-contact/SKILL.md +162 -0
  147. package/skills/wecom-doc/SKILL.md +162 -0
  148. package/skills/wecom-doc/references/create-doc.md +56 -0
  149. package/skills/wecom-doc/references/edit-doc-content.md +68 -0
  150. package/skills/wecom-doc/references/get-doc-content.md +88 -0
  151. package/skills/wecom-doc/references/smartpage-create.md +125 -0
  152. package/skills/wecom-doc/references/smartpage-export.md +160 -0
  153. package/skills/wecom-meeting/SKILL.md +441 -0
  154. package/skills/wecom-meeting/references/example-full.md +30 -0
  155. package/skills/wecom-meeting/references/example-reminder.md +46 -0
  156. package/skills/wecom-meeting/references/example-security.md +22 -0
  157. package/skills/wecom-meeting/references/response-get-meeting-info.md +148 -0
  158. package/skills/wecom-msg/SKILL.md +157 -0
  159. package/skills/wecom-msg/references/api-get-messages.md +93 -0
  160. package/skills/wecom-msg/references/api-get-msg-chat-list.md +58 -0
  161. package/skills/wecom-msg/references/api-get-msg-media.md +44 -0
  162. package/skills/wecom-msg/references/api-send-message.md +39 -0
  163. package/skills/wecom-preflight/SKILL.md +141 -0
  164. package/skills/wecom-schedule/SKILL.md +161 -0
  165. package/skills/wecom-schedule/references/api-check-availability.md +56 -0
  166. package/skills/wecom-schedule/references/api-create-schedule.md +38 -0
  167. package/skills/wecom-schedule/references/api-get-schedule-detail.md +81 -0
  168. package/skills/wecom-schedule/references/api-update-schedule.md +32 -0
  169. package/skills/wecom-schedule/references/ref-reminders.md +24 -0
  170. package/skills/wecom-send-media/SKILL.md +68 -0
  171. package/skills/wecom-send-template-card/SKILL.md +157 -0
  172. package/skills/wecom-send-template-card/references/api-template-card-types.md +358 -0
  173. package/skills/wecom-smartsheet/SKILL.md +164 -0
  174. package/skills/wecom-smartsheet/references/smartsheet-cell-value-formats.md +163 -0
  175. package/skills/wecom-smartsheet/references/smartsheet-field-types.md +44 -0
  176. package/skills/wecom-smartsheet/references/smartsheet-get-records.md +96 -0
  177. package/skills/wecom-smartsheet/references/webhook-examples.md +185 -0
  178. package/skills/wecom-smartsheet/references/webhook-fallback.md +184 -0
  179. package/skills/wecom-todo/SKILL.md +392 -0
  180. package/skills/wecom-todo/examples/workflows.md +163 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 企业微信媒体(图片)下载和保存模块
3
+ *
4
+ * 负责下载、检测格式、保存图片到本地,包含超时保护
5
+ */
6
+ import type { OpenClawConfig } from "ylib-openclaw/plugin-sdk";
7
+ import type { RuntimeEnv } from "ylib-openclaw/plugin-sdk";
8
+ import type { WSClient } from "@wecom/aibot-node-sdk";
9
+ import type { ResolvedWeComAccount } from "./utils.js";
10
+ /**
11
+ * 附件超过 OpenClaw 配置的 `agents.defaults.mediaMaxMb` 上限时抛出。
12
+ *
13
+ * 本错误由插件层主动判定并抛出,不依赖 OpenClaw 核心层错误消息的字符串匹配,
14
+ * 上层(monitor)可通过 `instanceof MediaOversizeError` 精确识别并向用户提示。
15
+ */
16
+ export declare class MediaOversizeError extends Error {
17
+ readonly kind: "image" | "file";
18
+ readonly filename?: string;
19
+ readonly sizeBytes: number;
20
+ readonly maxBytes: number;
21
+ constructor(params: {
22
+ kind: "image" | "file";
23
+ filename?: string;
24
+ sizeBytes: number;
25
+ maxBytes: number;
26
+ });
27
+ }
28
+ /**
29
+ * 下载并保存所有图片到本地,每张图片的下载带超时保护
30
+ */
31
+ export declare function downloadAndSaveImages(params: {
32
+ imageUrls: string[];
33
+ imageAesKeys?: Map<string, string>;
34
+ account: ResolvedWeComAccount;
35
+ config: OpenClawConfig;
36
+ runtime: RuntimeEnv;
37
+ wsClient: WSClient;
38
+ }): Promise<Array<{
39
+ path: string;
40
+ contentType?: string;
41
+ }>>;
42
+ /**
43
+ * 下载并保存所有文件到本地,每个文件的下载带超时保护
44
+ */
45
+ export declare function downloadAndSaveFiles(params: {
46
+ fileUrls: string[];
47
+ fileAesKeys?: Map<string, string>;
48
+ account: ResolvedWeComAccount;
49
+ config: OpenClawConfig;
50
+ runtime: RuntimeEnv;
51
+ wsClient: WSClient;
52
+ }): Promise<Array<{
53
+ path: string;
54
+ contentType?: string;
55
+ }>>;
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ /**
3
+ * 企业微信媒体(图片)下载和保存模块
4
+ *
5
+ * 负责下载、检测格式、保存图片到本地,包含超时保护
6
+ */
7
+ var __extends = (this && this.__extends) || (function () {
8
+ var extendStatics = function (d, b) {
9
+ extendStatics = Object.setPrototypeOf ||
10
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
11
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
12
+ return extendStatics(d, b);
13
+ };
14
+ return function (d, b) {
15
+ extendStatics(d, b);
16
+ function __() { this.constructor = d; }
17
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
18
+ };
19
+ })();
20
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
21
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
+ return new (P || (P = Promise))(function (resolve, reject) {
23
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
27
+ });
28
+ };
29
+ var __generator = (this && this.__generator) || function (thisArg, body) {
30
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
31
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
32
+ function verb(n) { return function (v) { return step([n, v]); }; }
33
+ function step(op) {
34
+ if (f) throw new TypeError("Generator is already executing.");
35
+ while (_) try {
36
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
37
+ if (y = 0, t) op = [op[0] & 2, t.value];
38
+ switch (op[0]) {
39
+ case 0: case 1: t = op; break;
40
+ case 4: _.label++; return { value: op[1], done: false };
41
+ case 5: _.label++; y = op[1]; op = [0]; continue;
42
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
43
+ default:
44
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
45
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
46
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
47
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
48
+ if (t[2]) _.ops.pop();
49
+ _.trys.pop(); continue;
50
+ }
51
+ op = body.call(thisArg, _);
52
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
53
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
54
+ }
55
+ };
56
+ exports.__esModule = true;
57
+ exports.downloadAndSaveFiles = exports.downloadAndSaveImages = exports.MediaOversizeError = void 0;
58
+ var file_type_1 = require("file-type");
59
+ var runtime_js_1 = require("./runtime.js");
60
+ var const_js_1 = require("./const.js");
61
+ var timeout_js_1 = require("./timeout.js");
62
+ // ============================================================================
63
+ // 媒体超限错误
64
+ // ============================================================================
65
+ /**
66
+ * 附件超过 OpenClaw 配置的 `agents.defaults.mediaMaxMb` 上限时抛出。
67
+ *
68
+ * 本错误由插件层主动判定并抛出,不依赖 OpenClaw 核心层错误消息的字符串匹配,
69
+ * 上层(monitor)可通过 `instanceof MediaOversizeError` 精确识别并向用户提示。
70
+ */
71
+ var MediaOversizeError = /** @class */ (function (_super) {
72
+ __extends(MediaOversizeError, _super);
73
+ function MediaOversizeError(params) {
74
+ var _this = _super.call(this, "Media oversize: kind=" + params.kind + ", size=" + params.sizeBytes + ", max=" + params.maxBytes +
75
+ (params.filename ? ", filename=" + params.filename : "")) || this;
76
+ _this.name = "MediaOversizeError";
77
+ _this.kind = params.kind;
78
+ _this.filename = params.filename;
79
+ _this.sizeBytes = params.sizeBytes;
80
+ _this.maxBytes = params.maxBytes;
81
+ return _this;
82
+ }
83
+ return MediaOversizeError;
84
+ }(Error));
85
+ exports.MediaOversizeError = MediaOversizeError;
86
+ // ============================================================================
87
+ // 图片格式检测辅助函数(基于 file-type 包)
88
+ // ============================================================================
89
+ /**
90
+ * 检查 Buffer 是否为有效的图片格式
91
+ */
92
+ function isImageBuffer(data) {
93
+ var _a;
94
+ return __awaiter(this, void 0, void 0, function () {
95
+ var type;
96
+ return __generator(this, function (_b) {
97
+ switch (_b.label) {
98
+ case 0: return [4 /*yield*/, file_type_1.fileTypeFromBuffer(data)];
99
+ case 1:
100
+ type = _b.sent();
101
+ return [2 /*return*/, (_a = type === null || type === void 0 ? void 0 : type.mime.startsWith("image/")) !== null && _a !== void 0 ? _a : false];
102
+ }
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * 检测 Buffer 的图片内容类型
108
+ */
109
+ function detectImageContentType(data) {
110
+ return __awaiter(this, void 0, void 0, function () {
111
+ var type;
112
+ return __generator(this, function (_a) {
113
+ switch (_a.label) {
114
+ case 0: return [4 /*yield*/, file_type_1.fileTypeFromBuffer(data)];
115
+ case 1:
116
+ type = _a.sent();
117
+ if (type === null || type === void 0 ? void 0 : type.mime.startsWith("image/")) {
118
+ return [2 /*return*/, type.mime];
119
+ }
120
+ return [2 /*return*/, "application/octet-stream"];
121
+ }
122
+ });
123
+ });
124
+ }
125
+ // ============================================================================
126
+ // 图片下载和保存
127
+ // ============================================================================
128
+ /**
129
+ * 下载并保存所有图片到本地,每张图片的下载带超时保护
130
+ */
131
+ function downloadAndSaveImages(params) {
132
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
133
+ return __awaiter(this, void 0, void 0, function () {
134
+ var imageUrls, config, runtime, wsClient, core, mediaList, _i, imageUrls_1, imageUrl, mediaMaxMb, maxBytes, imageBuffer, imageContentType, originalFilename, imageAesKey, result, sdkError_1, fetched, isValidImage, saved, err_1;
135
+ return __generator(this, function (_o) {
136
+ switch (_o.label) {
137
+ case 0:
138
+ imageUrls = params.imageUrls, config = params.config, runtime = params.runtime, wsClient = params.wsClient;
139
+ core = runtime_js_1.getWeComRuntime();
140
+ mediaList = [];
141
+ _i = 0, imageUrls_1 = imageUrls;
142
+ _o.label = 1;
143
+ case 1:
144
+ if (!(_i < imageUrls_1.length)) return [3 /*break*/, 13];
145
+ imageUrl = imageUrls_1[_i];
146
+ _o.label = 2;
147
+ case 2:
148
+ _o.trys.push([2, 11, , 12]);
149
+ (_a = runtime.log) === null || _a === void 0 ? void 0 : _a.call(runtime, "[wecom] Downloading image: url=" + imageUrl);
150
+ mediaMaxMb = (_d = (_c = (_b = config.agents) === null || _b === void 0 ? void 0 : _b.defaults) === null || _c === void 0 ? void 0 : _c.mediaMaxMb) !== null && _d !== void 0 ? _d : const_js_1.DEFAULT_MEDIA_MAX_MB;
151
+ maxBytes = mediaMaxMb * 1024 * 1024;
152
+ imageBuffer = void 0;
153
+ imageContentType = void 0;
154
+ originalFilename = void 0;
155
+ imageAesKey = (_e = params.imageAesKeys) === null || _e === void 0 ? void 0 : _e.get(imageUrl);
156
+ _o.label = 3;
157
+ case 3:
158
+ _o.trys.push([3, 6, , 9]);
159
+ return [4 /*yield*/, timeout_js_1.withTimeout(wsClient.downloadFile(imageUrl, imageAesKey), const_js_1.IMAGE_DOWNLOAD_TIMEOUT_MS, "Image download timed out: " + imageUrl)];
160
+ case 4:
161
+ result = _o.sent();
162
+ imageBuffer = result.buffer;
163
+ originalFilename = result.filename;
164
+ return [4 /*yield*/, detectImageContentType(imageBuffer)];
165
+ case 5:
166
+ imageContentType = _o.sent();
167
+ (_f = runtime.log) === null || _f === void 0 ? void 0 : _f.call(runtime, "[wecom] Image downloaded: size=" + imageBuffer.length + ", contentType=" + imageContentType + ", filename=" + (originalFilename !== null && originalFilename !== void 0 ? originalFilename : '(none)'));
168
+ return [3 /*break*/, 9];
169
+ case 6:
170
+ sdkError_1 = _o.sent();
171
+ // 如果 SDK 方法失败,回退到原有方式(带超时保护)
172
+ (_g = runtime.log) === null || _g === void 0 ? void 0 : _g.call(runtime, "[wecom] SDK download failed, fallback: " + String(sdkError_1));
173
+ return [4 /*yield*/, timeout_js_1.withTimeout(core.channel.media.fetchRemoteMedia({ url: imageUrl }), const_js_1.IMAGE_DOWNLOAD_TIMEOUT_MS, "Manual image download timed out: " + imageUrl)];
174
+ case 7:
175
+ fetched = _o.sent();
176
+ (_h = runtime.log) === null || _h === void 0 ? void 0 : _h.call(runtime, "[wecom] Image fetched: contentType=" + fetched.contentType + ", size=" + fetched.buffer.length);
177
+ imageBuffer = fetched.buffer;
178
+ imageContentType = (_j = fetched.contentType) !== null && _j !== void 0 ? _j : "application/octet-stream";
179
+ return [4 /*yield*/, isImageBuffer(fetched.buffer)];
180
+ case 8:
181
+ isValidImage = _o.sent();
182
+ if (!isValidImage) {
183
+ (_k = runtime.log) === null || _k === void 0 ? void 0 : _k.call(runtime, "[wecom] WARN: Downloaded data is not a valid image format");
184
+ }
185
+ return [3 /*break*/, 9];
186
+ case 9:
187
+ // 大小校验由插件层主动进行,超限抛出 MediaOversizeError,由 monitor 统一提示用户。
188
+ // 不再把 maxBytes 传给核心层 saveMediaBuffer,避免重复校验产生无结构化错误。
189
+ if (imageBuffer.length > maxBytes) {
190
+ throw new MediaOversizeError({
191
+ kind: "image",
192
+ filename: originalFilename,
193
+ sizeBytes: imageBuffer.length,
194
+ maxBytes: maxBytes
195
+ });
196
+ }
197
+ return [4 /*yield*/, core.channel.media.saveMediaBuffer(imageBuffer, imageContentType, "inbound", maxBytes, originalFilename)];
198
+ case 10:
199
+ saved = _o.sent();
200
+ mediaList.push({ path: saved.path, contentType: saved.contentType });
201
+ (_l = runtime.log) === null || _l === void 0 ? void 0 : _l.call(runtime, "[wecom][plugin] Image saved: path=" + saved.path + ", contentType=" + saved.contentType);
202
+ return [3 /*break*/, 12];
203
+ case 11:
204
+ err_1 = _o.sent();
205
+ // 媒体超限错误需要上抛给 monitor,用于向用户发送明确的提示文案。
206
+ if (err_1 instanceof MediaOversizeError) {
207
+ throw err_1;
208
+ }
209
+ (_m = runtime.error) === null || _m === void 0 ? void 0 : _m.call(runtime, "[wecom] Failed to download image: " + String(err_1));
210
+ return [3 /*break*/, 12];
211
+ case 12:
212
+ _i++;
213
+ return [3 /*break*/, 1];
214
+ case 13: return [2 /*return*/, mediaList];
215
+ }
216
+ });
217
+ });
218
+ }
219
+ exports.downloadAndSaveImages = downloadAndSaveImages;
220
+ /**
221
+ * 下载并保存所有文件到本地,每个文件的下载带超时保护
222
+ */
223
+ function downloadAndSaveFiles(params) {
224
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
225
+ return __awaiter(this, void 0, void 0, function () {
226
+ var fileUrls, config, runtime, wsClient, core, mediaList, _i, fileUrls_1, fileUrl, mediaMaxMb, maxBytes, fileBuffer, fileContentType, originalFilename, fileAesKey, result, type, sdkError_2, fetched, saved, err_2;
227
+ return __generator(this, function (_o) {
228
+ switch (_o.label) {
229
+ case 0:
230
+ fileUrls = params.fileUrls, config = params.config, runtime = params.runtime, wsClient = params.wsClient;
231
+ core = runtime_js_1.getWeComRuntime();
232
+ mediaList = [];
233
+ _i = 0, fileUrls_1 = fileUrls;
234
+ _o.label = 1;
235
+ case 1:
236
+ if (!(_i < fileUrls_1.length)) return [3 /*break*/, 12];
237
+ fileUrl = fileUrls_1[_i];
238
+ _o.label = 2;
239
+ case 2:
240
+ _o.trys.push([2, 10, , 11]);
241
+ (_a = runtime.log) === null || _a === void 0 ? void 0 : _a.call(runtime, "[wecom] Downloading file: url=" + fileUrl);
242
+ mediaMaxMb = (_d = (_c = (_b = config.agents) === null || _b === void 0 ? void 0 : _b.defaults) === null || _c === void 0 ? void 0 : _c.mediaMaxMb) !== null && _d !== void 0 ? _d : const_js_1.DEFAULT_MEDIA_MAX_MB;
243
+ maxBytes = mediaMaxMb * 1024 * 1024;
244
+ fileBuffer = void 0;
245
+ fileContentType = void 0;
246
+ originalFilename = void 0;
247
+ fileAesKey = (_e = params.fileAesKeys) === null || _e === void 0 ? void 0 : _e.get(fileUrl);
248
+ _o.label = 3;
249
+ case 3:
250
+ _o.trys.push([3, 6, , 8]);
251
+ return [4 /*yield*/, timeout_js_1.withTimeout(wsClient.downloadFile(fileUrl, fileAesKey), const_js_1.FILE_DOWNLOAD_TIMEOUT_MS, "File download timed out: " + fileUrl)];
252
+ case 4:
253
+ result = _o.sent();
254
+ fileBuffer = result.buffer;
255
+ originalFilename = result.filename;
256
+ return [4 /*yield*/, file_type_1.fileTypeFromBuffer(fileBuffer)];
257
+ case 5:
258
+ type = _o.sent();
259
+ fileContentType = (_f = type === null || type === void 0 ? void 0 : type.mime) !== null && _f !== void 0 ? _f : "application/octet-stream";
260
+ (_g = runtime.log) === null || _g === void 0 ? void 0 : _g.call(runtime, "[wecom] File downloaded: size=" + fileBuffer.length + ", contentType=" + fileContentType + ", filename=" + (originalFilename !== null && originalFilename !== void 0 ? originalFilename : '(none)'));
261
+ return [3 /*break*/, 8];
262
+ case 6:
263
+ sdkError_2 = _o.sent();
264
+ // 如果 SDK 方法失败,回退到 fetchRemoteMedia(带超时保护)
265
+ (_h = runtime.log) === null || _h === void 0 ? void 0 : _h.call(runtime, "[wecom] SDK file download failed, fallback: " + String(sdkError_2));
266
+ return [4 /*yield*/, timeout_js_1.withTimeout(core.channel.media.fetchRemoteMedia({ url: fileUrl }), const_js_1.FILE_DOWNLOAD_TIMEOUT_MS, "Manual file download timed out: " + fileUrl)];
267
+ case 7:
268
+ fetched = _o.sent();
269
+ (_j = runtime.log) === null || _j === void 0 ? void 0 : _j.call(runtime, "[wecom] File fetched: contentType=" + fetched.contentType + ", size=" + fetched.buffer.length);
270
+ fileBuffer = fetched.buffer;
271
+ fileContentType = (_k = fetched.contentType) !== null && _k !== void 0 ? _k : "application/octet-stream";
272
+ return [3 /*break*/, 8];
273
+ case 8:
274
+ // 大小校验由插件层主动进行,超限抛出 MediaOversizeError,由 monitor 统一提示用户。
275
+ // 不再把 maxBytes 传给核心层 saveMediaBuffer,避免重复校验产生无结构化错误。
276
+ if (fileBuffer.length > maxBytes) {
277
+ throw new MediaOversizeError({
278
+ kind: "file",
279
+ filename: originalFilename,
280
+ sizeBytes: fileBuffer.length,
281
+ maxBytes: maxBytes
282
+ });
283
+ }
284
+ return [4 /*yield*/, core.channel.media.saveMediaBuffer(fileBuffer, fileContentType, "inbound", maxBytes, originalFilename)];
285
+ case 9:
286
+ saved = _o.sent();
287
+ mediaList.push({ path: saved.path, contentType: saved.contentType });
288
+ (_l = runtime.log) === null || _l === void 0 ? void 0 : _l.call(runtime, "[wecom][plugin] File saved: path=" + saved.path + ", contentType=" + saved.contentType);
289
+ return [3 /*break*/, 11];
290
+ case 10:
291
+ err_2 = _o.sent();
292
+ // 媒体超限错误需要上抛给 monitor,用于向用户发送明确的提示文案。
293
+ if (err_2 instanceof MediaOversizeError) {
294
+ throw err_2;
295
+ }
296
+ (_m = runtime.error) === null || _m === void 0 ? void 0 : _m.call(runtime, "[wecom] Failed to download file: " + String(err_2));
297
+ return [3 /*break*/, 11];
298
+ case 11:
299
+ _i++;
300
+ return [3 /*break*/, 1];
301
+ case 12: return [2 /*return*/, mediaList];
302
+ }
303
+ });
304
+ });
305
+ }
306
+ exports.downloadAndSaveFiles = downloadAndSaveFiles;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * 企业微信出站媒体上传工具模块
3
+ *
4
+ * 负责:
5
+ * - 从 mediaUrl 加载文件 buffer(远程 URL 或本地路径均支持)
6
+ * - 检测 MIME 类型并映射为企微媒体类型
7
+ * - 文件大小检查与降级策略
8
+ */
9
+ /// <reference types="node" />
10
+ import type { WeComMediaType, WSClient, WsFrameHeaders } from "@wecom/aibot-node-sdk";
11
+ /** 媒体文件解析结果 */
12
+ export interface ResolvedMedia {
13
+ /** 文件数据 */
14
+ buffer: Buffer;
15
+ /** 检测到的 MIME 类型 */
16
+ contentType: string;
17
+ /** 文件名(从 URL 提取或默认生成) */
18
+ fileName: string;
19
+ }
20
+ /** 文件大小检查结果 */
21
+ export interface FileSizeCheckResult {
22
+ /** 最终确定的企微媒体类型(可能被降级) */
23
+ finalType: WeComMediaType;
24
+ /** 是否需要拒绝(超过绝对限制) */
25
+ shouldReject: boolean;
26
+ /** 拒绝原因(仅 shouldReject=true 时有值) */
27
+ rejectReason?: string;
28
+ /** 是否发生了降级 */
29
+ downgraded: boolean;
30
+ /** 降级说明(仅 downgraded=true 时有值) */
31
+ downgradeNote?: string;
32
+ }
33
+ /**
34
+ * 根据 MIME 类型检测企微媒体类型
35
+ *
36
+ * @param mimeType - MIME 类型字符串
37
+ * @returns 企微媒体类型
38
+ */
39
+ export declare function detectWeComMediaType(mimeType: string): WeComMediaType;
40
+ /**
41
+ * 从 mediaUrl 加载媒体文件
42
+ *
43
+ * 支持远程 URL(http/https)和本地路径(file:// 或绝对路径),
44
+ * 利用 openclaw plugin-sdk 的 loadOutboundMediaFromUrl 统一处理。
45
+ *
46
+ * @param mediaUrl - 媒体文件的 URL 或本地路径
47
+ * @param mediaLocalRoots - 允许读取本地文件的安全白名单目录
48
+ * @returns 解析后的媒体文件信息
49
+ */
50
+ export declare function resolveMediaFile(mediaUrl: string, mediaLocalRoots?: readonly string[], gatewayToken?: string, gatewayBaseUrl?: string): Promise<ResolvedMedia>;
51
+ /**
52
+ * 检查文件大小并执行降级策略
53
+ *
54
+ * 降级规则:
55
+ * - voice 非 AMR 格式 → 降级为 file(企微后台仅支持 AMR)
56
+ * - image 超过 10MB → 降级为 file
57
+ * - video 超过 10MB → 降级为 file
58
+ * - voice 超过 2MB → 降级为 file
59
+ * - file 超过 20MB → 拒绝发送
60
+ *
61
+ * @param fileSize - 文件大小(字节)
62
+ * @param detectedType - 检测到的企微媒体类型
63
+ * @param contentType - 文件的 MIME 类型(用于语音格式校验)
64
+ * @returns 大小检查结果
65
+ */
66
+ export declare function applyFileSizeLimits(fileSize: number, detectedType: WeComMediaType, contentType?: string): FileSizeCheckResult;
67
+ /** uploadAndSendMedia 的参数 */
68
+ export interface UploadAndSendMediaOptions {
69
+ /** WSClient 实例 */
70
+ wsClient: WSClient;
71
+ /** 媒体文件的 URL 或本地路径 */
72
+ mediaUrl: string;
73
+ /** 目标会话 ID(用于 aibot_send_msg 主动发送) */
74
+ chatId: string;
75
+ /** 允许读取本地文件的安全白名单目录 */
76
+ mediaLocalRoots?: readonly string[];
77
+ /** 下载受保护远程资源时使用的 gateway token(Bearer) */
78
+ gatewayToken?: string;
79
+ /** 受信任 Python/Gateway 基础地址;仅该来源允许回环地址媒体下载。 */
80
+ gatewayBaseUrl?: string;
81
+ /** 日志函数 */
82
+ log?: (...args: any[]) => void;
83
+ /** 错误日志函数 */
84
+ errorLog?: (...args: any[]) => void;
85
+ }
86
+ /** uploadAndSendMedia 的返回结果 */
87
+ export interface UploadAndSendMediaResult {
88
+ /** 是否发送成功 */
89
+ ok: boolean;
90
+ /** 发送后返回的 messageId */
91
+ messageId?: string;
92
+ /** 发送所用文件名 */
93
+ fileName?: string;
94
+ /** 最终的企微媒体类型 */
95
+ finalType?: WeComMediaType;
96
+ /** 是否被拒绝(文件过大) */
97
+ rejected?: boolean;
98
+ /** 拒绝原因 */
99
+ rejectReason?: string;
100
+ /** 是否发生了降级 */
101
+ downgraded?: boolean;
102
+ /** 降级说明 */
103
+ downgradeNote?: string;
104
+ /** 错误信息 */
105
+ error?: string;
106
+ }
107
+ /**
108
+ * 公共媒体上传+发送流程
109
+ *
110
+ * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → sendMediaMessage
111
+ * 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。
112
+ * channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。
113
+ */
114
+ export declare function uploadAndSendMedia(options: UploadAndSendMediaOptions): Promise<UploadAndSendMediaResult>;
115
+ /** uploadAndReplyMedia 的参数 */
116
+ export interface UploadAndReplyMediaOptions {
117
+ /** WSClient 实例 */
118
+ wsClient: WSClient;
119
+ /** 媒体文件的 URL 或本地路径 */
120
+ mediaUrl: string;
121
+ /** 原始 WebSocket 帧(用于 aibot_respond_msg 被动回复,携带 req_id) */
122
+ frame: WsFrameHeaders;
123
+ /** 允许读取本地文件的安全白名单目录 */
124
+ mediaLocalRoots?: readonly string[];
125
+ /** 下载受保护远程资源时使用的 gateway token(Bearer) */
126
+ gatewayToken?: string;
127
+ /** 受信任 Python/Gateway 基础地址;仅该来源允许回环地址媒体下载。 */
128
+ gatewayBaseUrl?: string;
129
+ /** 日志函数 */
130
+ log?: (...args: any[]) => void;
131
+ /** 错误日志函数 */
132
+ errorLog?: (...args: any[]) => void;
133
+ }
134
+ /**
135
+ * 被动回复媒体上传+发送流程
136
+ *
137
+ * 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia
138
+ * 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。
139
+ *
140
+ * 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。
141
+ */
142
+ export declare function uploadAndReplyMedia(options: UploadAndReplyMediaOptions): Promise<UploadAndSendMediaResult>;